Skip to content

Commit 3ce8b79

Browse files
authored
[RFC] Null value (#83)
This proposal adds a null literal to the GraphQL language and allows it to be provided to nullable typed arguments and input object fields. This presents an opportunity for a GraphQL service to interpret the explicitly provided null differently from the implicitly not-provided value, which may be especially useful when performing mutations using a per-field set API. For example, this query may represent the removal/clearing of the "bar" field from thing with id: 4. ``` mutation editThing { editThing(id: 4, edits: { foo: "added", bar: null }) { # ... } } ``` In addition to allowing `null` as a literal value, this also proposes interpretting the variables JSON to distinguish between explicit null and implicit not provided: ``` mutation editThing($edits: EditObj) { editThing(id: 4, edits: $edits) { # ... } } ``` This variables results in the unsetting of `bar` ``` { "edits": { "foo": "added", "bar": null } } ``` Finally, this proposes following the not-provided-ness of variables to their positions: ``` mutation editThing($editBaz: String) { editThing(id: 4, edits: { foo: "added", bar: null, baz: $editBaz }) { # ... } } ``` Such that the three variables are semantically different: * `{}` The "baz" input field is "not provided" * `{"editBaz": null}` The "baz" input field is `null` * `{"editBaz": "added"}` The "baz" input field is `"added"`
1 parent 13194df commit 3ce8b79

5 files changed

+94
-34
lines changed

spec/Appendix B -- Grammar Summary.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,15 @@ Value[Const] :
127127
- FloatValue
128128
- StringValue
129129
- BooleanValue
130+
- NullValue
130131
- EnumValue
131132
- ListValue[?Const]
132133
- ObjectValue[?Const]
133134

134135
BooleanValue : one of `true` `false`
135136

137+
NullValue : `null`
138+
136139
EnumValue : Name but not `true`, `false` or `null`
137140

138141
ListValue[Const] :

spec/Section 2 -- Language.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@ Value[Const] :
634634
- FloatValue
635635
- StringValue
636636
- BooleanValue
637+
- NullValue
637638
- EnumValue
638639
- ListValue[?Const]
639640
- ObjectValue[?Const]
@@ -736,6 +737,37 @@ StringCharacter :: \ EscapedCharacter
736737
* Return the character value of {EscapedCharacter}.
737738

738739

740+
### Null Value
741+
742+
NullValue : `null`
743+
744+
Null values are represented as the keyword {null}.
745+
746+
GraphQL has two semantically different ways to represent the lack of a value:
747+
748+
* Explicitly providing the literal value: {null}.
749+
* Implicitly not providing a value at all.
750+
751+
For example, these two field calls are similar, but are not identical:
752+
753+
```graphql
754+
{
755+
field(arg: null)
756+
field
757+
}
758+
```
759+
760+
The first has explictly provided {null} to the argument "arg", while the second
761+
has implicitly not provided a value to the argument "arg". These two forms may
762+
be interpreted differently. For example, a mutation representing deleting a
763+
field vs not altering a field, respectively. Niether form may be used for an
764+
input expecting a Non-Null type.
765+
766+
Note: The same two methods of representing the lack of a value are possible via
767+
variables by either providing the a variable value as {null} and not providing
768+
a variable value at all.
769+
770+
739771
### Enum Value
740772

741773
EnumValue : Name but not `true`, `false` or `null`
@@ -745,9 +777,6 @@ recommended that Enum values be "all caps". Enum values are only used in
745777
contexts where the precise enumeration type is known. Therefore it's not
746778
necessary to supply an enumeration type name in the literal.
747779

748-
An enum value cannot be "null" in order to avoid confusion. GraphQL
749-
does not supply a value literal to represent the concept {null}.
750-
751780

752781
### List Value
753782

spec/Section 3 -- Type System.md

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ and floating-point values, they are interpreted as an integer input value if
115115
they have an empty fractional part (ex. `1.0`) and otherwise as floating-point
116116
input value.
117117

118+
For all types below, with the exception of Non-Null, if the explicit value
119+
{null} is provided, then then the result of input coercion is {null}.
120+
118121
**Built-in Scalars**
119122

120123
GraphQL provides a basic set of well-defined Scalar types. A GraphQL server
@@ -809,16 +812,21 @@ input ExampleInputObject {
809812

810813
Original Value | Variables | Coerced Value
811814
-------------------------------------------------------------------------------
812-
`{ a: "abc", b: 123 }` | `{}` | `{ a: "abc", b: 123 }`
813-
`{ a: 123, b: "123" }` | `{}` | `{ a: "123", b: 123 }`
814-
`{ a: "abc" }` | `{}` | Error: Missing required field {b}
815+
`{ a: "abc", b: 123 }` | | `{ a: "abc", b: 123 }`
816+
`{ a: 123, b: "123" }` | | `{ a: "123", b: 123 }`
817+
`{ a: "abc" }` | | Error: Missing required field {b}
818+
`{ a: "abc", b: null }` | | Error: {b} must be non-null.
819+
`{ a: null, b: 1 }` | | `{ a: null, b: 1 }`
815820
`{ b: $var }` | `{ var: 123 }` | `{ b: 123 }`
821+
`{ b: $var }` | `{}` | Error: Missing required field {b}.
816822
`{ b: $var }` | `{ var: null }` | Error: {b} must be non-null.
817-
`{ b: $var }` | `{}` | Error: {b} must be non-null.
818-
`{ b: $var }` | `{}` | Error: {b} must be non-null.
819823
`{ a: $var, b: 1 }` | `{ var: null }` | `{ a: null, b: 1 }`
820824
`{ a: $var, b: 1 }` | `{}` | `{ b: 1 }`
821825

826+
Note: there is a semantic difference between the input value
827+
explicitly declaring an input field's value as the value {null} vs having not
828+
declared the input field at all.
829+
822830
#### Input Object type validation
823831

824832
1. An Input Object type must define one or more fields.
@@ -867,10 +875,21 @@ By default, all types in GraphQL are nullable; the {null} value is a valid
867875
response for all of the above types. To declare a type that disallows null,
868876
the GraphQL Non-Null type can be used. This type wraps an underlying type,
869877
and this type acts identically to that wrapped type, with the exception
870-
that `null` is not a valid response for the wrapping type. A trailing
878+
that {null} is not a valid response for the wrapping type. A trailing
871879
exclamation mark is used to denote a field that uses a Non-Null type like this:
872880
`name: String!`.
873881

882+
**Nullable vs. Optional**
883+
884+
Fields are *always* optional within the context of a query, a field may be
885+
omitted and the query is still valid. However fields that return Non-Null types
886+
will never return the value {null} if queried.
887+
888+
Inputs (such as field arguments), are always optional by default. However a
889+
non-null input type is required. In addition to not accepting the value {null},
890+
it also does not accept omission. For the sake of simplicity nullable types
891+
are always optional and non-null types are always required.
892+
874893
**Result Coercion**
875894

876895
In all of the above result coercions, {null} was considered a valid value.
@@ -881,43 +900,41 @@ must be raised.
881900

882901
**Input Coercion**
883902

884-
If the argument of a Non-Null type is not provided, a query error must
885-
be raised.
903+
If an argument or input-object field of a Non-Null type is not provided, is
904+
provided with the literal value {null}, or is provided with a variable that was
905+
either not provided a value at runtime, or was provided the value {null}, then
906+
a query error must be raised.
886907

887-
If an argument of a Non-Null type is provided with a literal value, it is
888-
coerced using the input coercion for the wrapped type.
908+
If the value provided to the Non-Null type is provided with a literal value
909+
other than {null}, or a Non-Null variable value, it is coerced using the input coercion for the wrapped type.
889910

890-
If the argument of a Non-Null is provided with a variable, a query error must be
891-
raised if the runtime provided value is not provided or is {null} in the
892-
provided representation (usually JSON). Otherwise, the coerced value is the
893-
result of using the input coercion for the wrapped type.
894-
895-
Note that `null` is not a value in GraphQL, so a query cannot look like:
911+
Example: A non-null argument cannot be omitted.
896912

897913
```!graphql
898914
{
899-
field(arg: null)
915+
fieldWithNonNullArg
900916
}
901917
```
902918

903-
to indicate that the argument is {null}. Instead, an argument would be {null}
904-
only if it is omitted:
919+
Example: The value {null} cannot be provided to a non-null argument.
905920

906-
```graphql
921+
```!graphql
907922
{
908-
field
923+
fieldWithNonNullArg(nonNullArg: null)
909924
}
910925
```
911926

912-
Or if passed a variable of a nullable type that at runtime was not provided
913-
a value:
927+
Example: A variable of a nullable type cannot be provided to a non-null argument.
914928

915929
```graphql
916930
query withNullableVariable($var: String) {
917-
field(arg: $var)
931+
fieldWithNonNullArg(nonNullArg: $var)
918932
}
919933
```
920934

935+
Note: The Validation section defines providing a nullable variable type to
936+
a non-null input type as invalid.
937+
921938
**Non-Null type validation**
922939

923940
1. A Non-Null type must not wrap another Non-Null type.

spec/Section 5 -- Validation.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -629,22 +629,26 @@ fragment stringIntoInt on Arguments {
629629
```
630630

631631

632-
#### Required Arguments
632+
#### Required Non-Null Arguments
633633

634634
* For each Field or Directive in the document.
635635
* Let {arguments} be the arguments provided by the Field or Directive.
636636
* Let {argumentDefinitions} be the set of argument definitions of that Field or Directive.
637-
* For each {definition} in {argumentDefinitions}
638-
* Let {type} be the expected type of {definition}
639-
* If {type} is Non-Null
640-
* Let {argumentName} be the name of {definition}
637+
* For each {definition} in {argumentDefinitions}:
638+
* Let {type} be the expected type of {definition}.
639+
* If {type} is Non-Null:
640+
* Let {argumentName} be the name of {definition}.
641641
* Let {argument} be the argument in {arguments} named {argumentName}
642642
* {argument} must exist.
643+
* Let {value} be the value of {argument}.
644+
* {value} must not be the {null} literal.
643645

644646
** Explanatory Text **
645647

646648
Arguments can be required. Arguments are required if the type of the argument
647-
is non-null. If it is not non-null, the argument is optional.
649+
is non-null. If it is not non-null, the argument is optional. When an argument
650+
type is non-null, and is required, the explicit value {null} may also not
651+
be provided.
648652

649653
For example the following are valid:
650654

@@ -676,6 +680,13 @@ fragment missingRequiredArg on Arguments {
676680
}
677681
```
678682

683+
Providing the explicit value {null} is also not valid.
684+
685+
```!graphql
686+
fragment missingRequiredArg on Arguments {
687+
notNullBooleanArgField(nonNullBooleanArg: null)
688+
}
689+
```
679690

680691
## Fragments
681692

spec/Section 6 -- Execution.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ the field returned {null}, and an error must be added to the {"errors"} list
535535
in the response.
536536

537537
If the result of resolving a field is {null} (either because the function to
538-
resolve the field returned `null` or because an error occurred), and that
538+
resolve the field returned {null} or because an error occurred), and that
539539
field is of a `Non-Null` type, then a field error is thrown. The
540540
error must be added to the {"errors"} list in the response.
541541

0 commit comments

Comments
 (0)