Welcome to the new series "GraphQL deep dive" where we will explore advanced or unknown GraphQL topics. The plan is to discuss things mostly in a language and implementation neutral way, even if it is hosted on graphql-java.com.
Merged Fields
First thing we are looking at is "merged fields".
GraphQL allows for a field to be declared multiple times in a query as long as it can be merged.
Valid GraphQL queries are:
{
foo
foo
}
{
foo(id: "123")
foo(id: "123")
foo(id: "123")
}
{
foo(id: "123") {
id
}
foo(id: "123") {
name
}
foo(id: "123") {
id
name
}
}
Each of these queries will result in a result with just one "foo" key, not two or three.
Invalid Queries are:
{
foo
foo(id: "123")
}
{
foo(id: "123")
foo(id: "456", id2: "123")
}
{
foo(id: "123")
foo: foo2
}
The reason why they are not valid, is because the fields are different: in the first two examples the arguments differ and the third query actually has two different fields under the same key.
Motivation
The examples so far don't seem really useful, but it all makes sense when you add fragments:
{
...myFragment1
...myFragment2
}
fragment myFragment1 on Query {
foo(id: "123") {
name
}
}
fragment myFragment2 on Query {
foo(id: "123") {
url
}
}
Fragments are designed to be written by different parties (for example different components in a UI) which should not know anything about each other. Requiring that every field can only be declared once would make this objective unfeasible.
But by allowing fields merging, as long as the fields are the same, allows fragments to be authored in an independent way from each other.
Rules when fields can be merged
The specific details when fields can be merged are written down in Field Selection Merging in the spec.
The rules are what you would expect in general and they basically say that fields must be the same. The following examples are taken from the spec and they are all valid:
fragment mergeIdenticalFields on Dog {
name
name
}
fragment mergeIdenticalAliasesAndFields on Dog {
otherName: name
otherName: name
}
fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: SIT)
}
fragment mergeIdenticalFieldsWithIdenticalValues on Dog {
doesKnowCommand(dogCommand: $dogCommand)
doesKnowCommand(dogCommand: $dogCommand)
}
The most complex case happens when you have fields in fragments on different types:
fragment safeDifferingFields on Pet {
... on Dog {
volume: barkVolume
}
... on Cat {
volume: meowVolume
}
}
This is normally invalid because volume
is an alias for two different fields barkVolume
and meowVolume
but because only one of the some are actually resolved and they both return a value of the same type (we assume here that barkVolume
and meowVolume
are both of the same type) it is valid.
fragment safeDifferingArgs on Pet {
... on Dog {
doesKnowCommand(dogCommand: SIT)
}
... on Cat {
doesKnowCommand(catCommand: JUMP)
}
}
This is again a valid case because even if the first doesKnowCommand
has a different argument than the second doesKnowCommand
only one of them is actually resolved.
In the next example someValue
has different types (we assume that nickname
is a String
and meowVolume
is a Int
) and therefore the query is not valid:
fragment conflictingDifferingResponses on Pet {
... on Dog {
someValue: nickname
}
... on Cat {
someValue: meowVolume
}
}
Sub selections and directives
One thing to keep in my mind is that the sub selections of fields are merged together. For example here foo
is resolved once and than id
and name
is resolved.
{
foo(id: "123") {
id
}
foo(id: "123") {
name
}
}
This query is the same as:
{
foo(id: "123") {
id
name
}
}
The second thing to keep in mind is that different directives can be on each field:
{
foo(id: "123") @myDirective {
id
}
foo(id: "123") @myOtherDirective {
name
}
}
So if you want to know all directives for the current field you are resolving you actually need to look at all of the merged fields from the query.
Merged fields in graphql-js and GraphQL Java
In graphql-js merged fields are relevant when you implement a resolver and you need access to the specific ast field of the query. The info
objects has a property fieldNodes
which gives you access to all ast fields which are merged together.
In GraphQL Java depending on the version you are running you have List<Field> getFields()
in the DataFetcherEnvironment
or for GraphQL Java newer than 12.0
you have also MergedField getMergedField()
which is the recommend way to access all merged fields.