Skip to main content

Default value coercion rules

At a glance​

Timeline​


Coercing Field Arguments states:

5.c. Let defaultValue be the default value for argumentDefinition. [...] 5.h. If hasValue is not true and defaultValue exists (including null): 5.h.i. Add an entry to coercedValues named argumentName with the value defaultValue. [...] 5.j.iii.1. If value cannot be coerced according to the input coercion rules of argumentType, throw a field error. 5.j.iii.2. Let coercedValue be the result of coercing value according to the input coercion rules of argumentType. 5.j.iii.3. Add an entry to coercedValues named argumentName with the value coercedValue.

Here we note that there is no run-time coercion of defaultValue, which makes sense (why do at runtime that which can be done at build time?). However, there doesn't seem to be a rule that specifies that defaultValue must be coerced at all, which leads to consequences:

  1. you could use any value for defaultValue and it could break the type safety guarantees of GraphQL
  2. nested defaultValues are not applied

When building the following GraphQL schema programmatically (code below) with GraphQL.js:

type Query {
example(inputObject: ExampleInputObject! = {}): Int
}

input ExampleInputObject {
number: Int! = 3
}

And a resolver for Query.example:

resolve(source, args) {
return args.inputObject.number;
}

You might expect the following queries to all gave the same result:

query A {
example
}
query B {
example(inputObject: {})
}
query C {
example(inputObject: { number: 3 })
}
query D($inputObject: ExampleInputObject! = {}) {
example(inputObject: $inputObject)
}

However, it turns out that query A's result differs:

{"example":null}
{"example":3}
{"example":3}
{"example":3}

This is because defaultValue for Query.example(inputObject:) was not coerced, so none of the defaultValues of ExampleInputObject were applied.

This is extremely unexpected, because looking at the GraphQL schema definition it looks like there's no circumstance under which ExampleInputObject may not have number as an integer; however when the defaultValue of Query.example(inputObject:) is used, the value of number is undefined.

Example runnable with GraphQL.js
const {
graphqlSync,
printSchema,
GraphQLSchema,
GraphQLObjectType,
GraphQLInputObjectType,
GraphQLInt,
GraphQLNonNull,
} = require("graphql");

const ExampleInputObject = new GraphQLInputObjectType({
name: "ExampleInputObject",
fields: {
number: {
type: new GraphQLNonNull(GraphQLInt),
defaultValue: 3,
},
},
});

const Query = new GraphQLObjectType({
name: "Query",
fields: {
example: {
args: {
inputObject: {
type: new GraphQLNonNull(ExampleInputObject),
defaultValue: {},
},
},
type: GraphQLInt,
resolve(source, args) {
return args.inputObject.number;
},
},
},
});

const schema = new GraphQLSchema({
query: Query,
});

console.log(printSchema(schema));

// All four of these should be equivalent?
const source = /* GraphQL */ `
query A {
example
}
query B {
example(inputObject: {})
}
query C {
example(inputObject: { number: 3 })
}
query D($inputObject: ExampleInputObject! = {}) {
example(inputObject: $inputObject)
}
`;
const result1 = graphqlSync({ schema, source, operationName: "A" });
const result2 = graphqlSync({ schema, source, operationName: "B" });
const result3 = graphqlSync({ schema, source, operationName: "C" });
const result4 = graphqlSync({ schema, source, operationName: "D" });

console.log(JSON.stringify(result1.data));
console.log(JSON.stringify(result2.data));
console.log(JSON.stringify(result3.data));
console.log(JSON.stringify(result4.data));

This was raised against GraphQL.js back in 2016, but @IvanGoncharov closed it early last year stating that GraphQL.js conforms to the GraphQL Spec in this regard.

My proposal is that when a defaultValue is specified, the GraphQL implementation should coerce it to conform to the relevant type just like it does for runtime values as specified in Coercing Variable Values and Coercing Field Arguments.


This is validated for query documents (and schema defined as SDL), because:

Literal values must be compatible with the type expected in the position they are found as per the coercion rules defined in the Type System chapter. -- http://spec.graphql.org/draft/#sel-FALXDFDDAACFAhuF

But there doesn't seem to be any such assertion for GraphQL schemas defined in code.