From 3aef662373d07500e20709a410ed25840e6d5985 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Fri, 12 Oct 2018 00:16:14 +0300 Subject: [PATCH] Call 'toJSON' if present for ID and String serialize (#1520) Fixes #1518 --- src/type/__tests__/serialization-test.js | 22 +++++++-- src/type/scalars.js | 59 +++++++++++++++--------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/type/__tests__/serialization-test.js b/src/type/__tests__/serialization-test.js index a496c8c381..ebb878be0c 100644 --- a/src/type/__tests__/serialization-test.js +++ b/src/type/__tests__/serialization-test.js @@ -113,11 +113,19 @@ describe('Type System: Scalar coercion', () => { const stringableObjValue = { valueOf() { - return 'something useful'; + return 'valueOf string'; + }, + toJSON() { + return 'toJSON string'; }, }; expect(GraphQLString.serialize(stringableObjValue)).to.equal( - 'something useful', + 'valueOf string', + ); + + delete stringableObjValue.valueOf; + expect(GraphQLString.serialize(stringableObjValue)).to.equal( + 'toJSON string', ); expect(() => GraphQLString.serialize(NaN)).to.throw( @@ -165,13 +173,19 @@ describe('Type System: Scalar coercion', () => { expect(GraphQLID.serialize(0)).to.equal('0'); expect(GraphQLID.serialize(-1)).to.equal('-1'); - const objValue = { + const serializableObjValue = { _id: 123, valueOf() { return this._id; }, + toJSON() { + return `ID:${this._id}`; + }, }; - expect(GraphQLID.serialize(objValue)).to.equal('123'); + expect(GraphQLID.serialize(serializableObjValue)).to.equal('123'); + + delete serializableObjValue.valueOf; + expect(GraphQLID.serialize(serializableObjValue)).to.equal('ID:123'); const badObjValue = { _id: false, diff --git a/src/type/scalars.js b/src/type/scalars.js index 89f6ed252b..ada7b21061 100644 --- a/src/type/scalars.js +++ b/src/type/scalars.js @@ -117,24 +117,39 @@ export const GraphQLFloat = new GraphQLScalarType({ }, }); -function serializeString(value: mixed): string { - // Support serializing objects with custom valueOf() functions - a common way - // to represent an complex value which can be represented as a string - // (ex: MongoDB id objects). - const result = - value && typeof value.valueOf === 'function' ? value.valueOf() : value; +// Support serializing objects with custom valueOf() or toJSON() functions - +// a common way to represent a complex value which can be represented as +// a string (ex: MongoDB id objects). +function serializeObject(value: mixed): mixed { + if (typeof value === 'object' && value !== null) { + if (typeof value.valueOf === 'function') { + const valueOfResult = value.valueOf(); + if (typeof valueOfResult !== 'object') { + return valueOfResult; + } + } + if (typeof value.toJSON === 'function') { + return value.toJSON(); + } + } + return value; +} + +function serializeString(rawValue: mixed): string { + const value = serializeObject(rawValue); + // Serialize string, boolean and number values to a string, but do not // attempt to coerce object, function, symbol, or other types as strings. - if (typeof result === 'string') { - return result; + if (typeof value === 'string') { + return value; } - if (typeof result === 'boolean') { - return result ? 'true' : 'false'; + if (typeof value === 'boolean') { + return value ? 'true' : 'false'; } - if (isFinite(result)) { - return result.toString(); + if (isFinite(value)) { + return value.toString(); } - throw new TypeError(`String cannot represent value: ${inspect(value)}`); + throw new TypeError(`String cannot represent value: ${inspect(rawValue)}`); } function coerceString(value: mixed): string { @@ -190,18 +205,16 @@ export const GraphQLBoolean = new GraphQLScalarType({ }, }); -function serializeID(value: mixed): string { - // Support serializing objects with custom valueOf() functions - a common way - // to represent an object identifier (ex. MongoDB). - const result = - value && typeof value.valueOf === 'function' ? value.valueOf() : value; - if (typeof result === 'string') { - return result; +function serializeID(rawValue: mixed): string { + const value = serializeObject(rawValue); + + if (typeof value === 'string') { + return value; } - if (isInteger(result)) { - return String(result); + if (isInteger(value)) { + return String(value); } - throw new TypeError(`ID cannot represent value: ${inspect(value)}`); + throw new TypeError(`ID cannot represent value: ${inspect(rawValue)}`); } function coerceID(value: mixed): string {