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

Call 'toJSON' if present for ID and String serialize #1520

Merged
merged 1 commit into from
Oct 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/type/__tests__/serialization-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,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(
Expand Down Expand Up @@ -163,13 +171,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,
Expand Down
59 changes: 36 additions & 23 deletions src/type/scalars.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down