Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query/Mutation receives args object with a null prototype #3149

Closed
5 tasks done
lookfirst opened this issue Aug 12, 2019 · 15 comments
Closed
5 tasks done

Query/Mutation receives args object with a null prototype #3149

lookfirst opened this issue Aug 12, 2019 · 15 comments

Comments

@lookfirst
Copy link

lookfirst commented Aug 12, 2019

This bug report should include:

  • A short, but descriptive title. The title doesn't need "Apollo" in it.
  • The package name and version of Apollo showing the problem.
  • [O] If applicable, the last version of Apollo where the problem did not occur.
  • The expected behavior.
  • The actual behavior.
  • A simple, runnable reproduction!

Version: latest apollo-server-core (2.8.1)

Related SO

https://stackoverflow.com/questions/53983315/is-there-a-way-to-get-rid-of-object-null-prototype-in-graphql

Actual behavior

This has been discussed on SO, but the solution is less than ideal. Serializing things through json is silly overhead. If anything it is confusing and undefined/undocumented behavior. I'm filing this as a bug since a mutation should get an object passed in with a prototype. A query receives an args object with a prototype. This behavior should at least be made consistent and ideally an object with a prototype.

Example code:

	Mutation: {
		create: async (_, args, context, info) => {
			console.log(args.thing);
		},
	},

Output:

[Object: null prototype] {
    id: '00112233445566'
}

Expected behavior

{
    id: '00112233445566'
}

Current workaround

console.log(JSON.parse(JSON.stringify(args.thing)))
@trevor-scheer
Copy link
Member

Hey @lookfirst, thanks for reporting an issue. If I understood correctly, this problem is experienced by non-AS users as well? This led me to believe it may be one of the underlying libraries instead (graphql-tools or graphql-js, most likely).

I put together a minimal reproduction to confirm my suspicion and found it to be true:
https://gist.github.com/trevor-scheer/e7f4022b5ce81ade7b79a206d16e9fd4

I was also surprised to find that the args for a query were also an object with a null prototype:

{ data: [Object: null prototype] { create: 'Blah' } }
{ data: [Object: null prototype] { get: 'Blah' } }

which is inconsistent with what you said unless I misunderstood:

A query receives an args object with a prototype. This behavior should at least be made consistent

I confirmed the behavior is consistent in both my gist and an ApolloServer-fied version of my gist.

I'm going to close this issue, since it's not ApolloServer's doing, but an underlying library. Please feel free to reference this issue elsewhere if you choose to confirm where this is specifically coming from.

Related: I'm unsure why this is an issue for a consumer. Is it problematic for logging purposes, or is this actually having an impact on the expected inputs? You should be able to access everything on the args object as expected, but please correct me if I'm overlooking something!

@lookfirst
Copy link
Author

Interesting. Thanks for reproducing outside of Apollo. It will help me dig deeper.

I did not notice this on the query because I was only defining Foo(id: ID!) and not a full object.

Passing these objects into firebase/firestore for mutation causes failure because obj.hasOwnProperty is missing.

@lookfirst
Copy link
Author

Related: graphql/express-graphql#177

@lookfirst lookfirst changed the title Mutation receives args object with a null prototype Query/Mutation receives args object with a null prototype Aug 14, 2019
@lookfirst
Copy link
Author

Google fixed this in firestore! googleapis/nodejs-firestore#736

@trevor-scheer
Copy link
Member

@lookfirst Hey, nice work - and thanks for the update. Glad it all worked out 😄

@danila-osin
Copy link

You can just distructure an object with [Object: null prototype]

@paulomcnally
Copy link

any solution for this?

@lookfirst
Copy link
Author

any solution for this?

#3149 (comment)

@quinten1333
Copy link

I made a simple library to fix this. The library is faster than going to json and back because it works in place. Link: https://www.npmjs.com/package/normalize-object-inheritance

@glasser
Copy link
Member

glasser commented May 5, 2022

FWIW, I'm pretty sure this is a feature. It means that you can have args with names like toString and constructor and such without them being treated specially.

@iambumblehead
Copy link

graphql should return plain data that can be used safely with other programs. For example, nodejs' native assert will fail when comparing graphql's [Object: null prototype] object with normal javascript objects used by other software,

import assert from 'node:assert/strict';
import gqlreq from './gqlreq.js';

const gqlres = await gqlreq();

console.log(gqlres);
// [Object: null prototype] {
//   user: [Object: null prototype] {
//     id: 'ywj--Y3SxiXk4TEa04Wz5'
//   }
// }

assert.deepEqual(gqlres, {
  user: {
    id: 'ywj--Y3SxiXk4TEa04Wz5'
  }
}); // ERR_ASSERTION, Expected values to be strictly deep-equal

@glasser
Copy link
Member

glasser commented Nov 28, 2022

FWIW this choice is made inside graphql-js, not apollo-server.

It's discussed at length in this PR: graphql/graphql-js#504 (comment)

As I commented there a few months ago, the biggest strangeness is that graphql-js makes an inconsistent choice between whether a prototype is applied between whether an object is literally part of the GraphQL language document (in which case there is no prototype) or whether it comes from the variables JSON chunk (in which case there is a prototype).

The issue with fields that have names like toString is a real one and in an ideal world graphql-js would use Maps instead of prototype-less objects here, but using a prototyped object doesn't seem like a great solution due to the conflict issue.

@iambumblehead
Copy link

FWIW this choice is made inside graphql-js, not apollo-server.

Acknowledged.

Here's little work-around that deep-recurses the graphql object to return a normal object that can be used with other tools like node's assert. If your objects aren't too deep, this might be OK for unit-tests

const ungqlval = (value, type = typeof value, f = null) => {
  if (value === f || /string|number|boolean/.test(type))
    f = value;
  else if (Array.isArray(value) || type === 'object')
    f = ungql(value);

  return f;
};

const ungql = data => Object.keys(data).reduce((prev, k) => {
  prev[k] = ungqlval(data[k]);

  return prev;
}, Array.isArray(data) ? [] : {});

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 19, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants