Normalized GraphQL Documents

State of this document

This specification shall be considered a working draft. The authors of this document reserve the right to make changes of any kind without notice. This includes changes that are not backward compattible.

In the future, there will exists ratified versions of this specification. Each version will be immutable and won’t receive any further changes.

Changes between versions may not always be backwards compatible. However, it is the intent of the authors of this specification to minimize the amount of changes that are not backward compatible.

Introduction

GraphQL as a language is intentionally designed to give maximum flexibility to API consumers. This means that there are infinite possible ways for the same execution result to be produced by different query documents, given the same variables as inputs.

A similar thing can be said about the type system definition language defined by the GraphQL specification. A single GraphQL schema can be defined using infinite different representation of that language.

This specification intends to formalize a method of normalizing any GraphQL document. This includes both executable documents as well as type system documents. Two documents that generate the same execution result or that describe the same GraphQL schema will generate the same output when following the normalization procedures defined by this specification.

Usage

The primary motivation for writing this specifications is persisted operations, where instead of sending the full document to a GraphQL server for execution, the server instead receives a hash that can be uniquely mapped to a certain operation. For efficiency reasons, two operations should produce the same hash if they are semantically equivalent. Without being able to normalize GraphQL documents, the server needs to store multiple hashes for equivalent operations.

For example, the following two documents would produce the same execution result, yet their stringified versions are different, hence they would result in different values when being hashes.

Example № 1{
  user(id: 4) {
    name
  }
}
Example № 2{
  user(id: 4) {
    ...UserData
  }
}

fragment UserData on User {
  name
}

There are likely more use cases for normalizing GraphQL documents which this preface might include in the future.

1Overview

Defining normalization for GraphQL documents only refers to the textual representation or the AST derived from the textual representation. This specification makes no assumptions of how GraphQL server implementations should handle documents internally.

All the following sections define a set of rules. A GraphQL document is considered normalized if any only if it adheres to all these rules.

1.1Printing Normalized GraphQL Documents

The stringified display of normalized GraphQL documents optimizes for string length as a primary goal and legibility as secondary goal. The string must use UTF-8 for encoding.

1.1.1Minimize ignored tokens

To meet the primary goal of small length, a normalized GraphQL document in stringified form must not contain excessive ignored tokens. The only non-excessive ignored token is a single space character (U+0020) that must be inserted between two tokens for which ShouldSeparateWithSpace returns true.

ShouldSeparateWithSpace(firstToken, secondToken)
  1. If secondToken is of the kind Punctuator and is equal to the tripple-dot (...):
    1. If firstToken is a lexical token and not of the kind Punctuator:
      1. Return true.
    2. Return false.
  2. If firstToken is a lexical token and not of the kind Punctuator:
    1. If secondToken is a lexical token and not of the kind Punctuator:
      1. Return true.
  3. Return false.
Note The definition of ShouldSeparateWithSpace intentionally adds ignored tokens in some places where they would not strictly be necessary in order to produce a string that can be parsed as GraphQL document. This is to satisfy the secondary goal of legibility.
Example

Consider the following document string that contains excessive ingored tokens.

Example № 3{
  add(numbers: [1, -2]) {
    __typename
    ... on Success {
      result
    }
    ... on Error {
      message
      code
    }
  }
}

The string correctly representing the normalized GraphQL document looks like this.

Example № 4{add(numbers:[1 -2]){__typename ...on Success{result}...on Error{message code}}}

There are two space tokens in this string which are not strictly necessary to preserve the ability of the string to be parsed as GraphQL document:

  • The space between 1 and -2 in the integer array
  • The space between __typename and the tripple-dot punctuator ...

In other words, the following string contains the actually minimal amount of ignored tokens and would parse to the same GraphQL document like the one above.

Counter Example № 5{add(numbers:[1-2]){__typename...on Success{result}...on Error{message code}}}

The second string suffers significantly in terms of legibility. For a human, the token 1-2 might not be understood as two separate integers. Similarly, having the tripple-dot punctuator join two Name tokens might be considered ambiguous or might be perceived as a single Name token.

1.1.2Printing strings

There are two ways of representing a StringValue in GraphQL: Wrapping the string with two single-quotation-marks, and wrapping the string with two tripple-quotation-marks. The latter are known as block strings. We will refer to the former as regular strings.

When displaying a normalized GraphQL document in stringified form, all StringValue tokens must be printed as regular strings.

Note The reason for using regular strings over block strings is to meet the primary goal of minimized overall string length. A block string requires four additional quotation marks. Line breaks could be inserted more efficiently using block strings (using U+000A as single character), however this conflicts with the secondary goal of legibility (see below).

The following control characters must be printed using their escape sequence:

  • U+0008 must be printed as \b
  • U+0009 must be printed as \t
  • U+000A must be printed as \n
  • U+000C must be printed as \f
  • U+000D must be printed as \r

All control characters other than the five mentioned above must be printed using their escaped unicode sequence. This includes:

  • All characters from U+0000 up to and including U+0007
  • The character U+000B
  • All characters from U+000E up to and including U+001F
  • All characters from U+007F up to and including U+009F

The quote character U+0022 must be printed with a leading backslash (\").

The backslash character U+005C must be printed with a leading backslash (\\).

All other characters must be printed using their unicode character.

2Executable Documents

This section contains normalization rules specifically for ExecutableDocuments. Each rule provides a non-normalized GraphQL document as counter-example and shows the normalized version of that document.

Note Like described in the section about Printing Normalized GraphQL Documents, normalized GraphQL documents in textual representation must not contain excessive ignored tokens. However, to increase legibility, the examples of this section use a “prettified” textual representation that may add ignored tokens such as whitespace, line terminators, and commatas.

This specification only applies to executable documents that would produce no validation errors in the context of a given GraphQL schema.

2.1Selections

2.1.1No Redundant Field Alias

A normalized GraphQL document must not contain a Field with an Alias that has the same name as the field.

Example
Counter Example № 6{
  user(id: 4) {
    name: name
  }
}
Example № 7{
  user(id: 4) {
    name
  }
}

2.1.2No Duplicate Selections

A normalized GraphQL document must not contain two equivalent Selections. Two Selections selectionA and selectionB are considered equivalent if SelectionsAreEquivalent(selectionA, selectionB) is equal to true.

Note Within the process of normalizing a GraphQL document and removing duplicate selections, it is important to preserve the given field ordering. This implies that when finding two equivalent selections, then the one specified second should be removed in favor of the one specified first.
SelectionsAreEquivalent(selectionA, selectionB)
  1. If selectionA is a Field:
    1. If selectionB is not a Field:
      1. Return false.
    2. Otherwise, return FieldsAreEquivalent(selectionA, selectionB).
  2. If selectionA is an InlineFragment:
    1. If selectionB is not an InlineFragment:
      1. Return false.
    2. Otherwise, return InlineFragmentsAreEquivalent(selectionA, selectionB).
  3. If selectionA is a FragmentSpread:
    1. If selectionB is not a FragmentSpread:
      1. Return false.
    2. Otherwise, return FragmentSpreadsAreEqual(selectionA, selectionB).
FieldsAreEquivalent(fieldA, fieldB)
  1. Let responseKeyA be the response key of fieldA (Alias if exists, otherwise Field name), and let responseKeyB be the response key of fieldB.
  2. If responseKeyA is not equal to responseKeyB:
    1. Return false.
  3. Let argumentsA be the unordered set of Arguments passed to fieldA, and let argumentsB be the unordered set of Arguments passed to fieldB.
  4. If ArgumentsAreEquivalent(argumentsA, argumentsB) is equal to false:
    1. Return false.
  5. Let directivesA be the ordered list of Directives passed to fieldA, and let directivesB be the ordered list of Directives passed to fieldB.
  6. If DirectivesAreEquivalent(directivesA, directivesB) is equal to false:
    1. Return false.
  7. Return true.
InlineFragmentsAreEquivalent(inlineFragmentA, inlineFragmentB)
  1. Let typeConditionA be the TypeCondition of inlineFragmentA, and let typeConditionB be the TypeCondition of inlineFragmentB.
  2. If typeConditionA does not exist:
    1. If typeConditionB exists:
      1. Return false.
  3. If typeConditionA exists:
    1. If typeConditionB does not exist:
      1. Return false.
    2. Let typeA be the name of the type from typeConditionA, and let typeB be the name of the type from typeConditionB.
    3. If typeA is not equal to typeB:
      1. Return false.
  4. Let directivesA be the ordered list of Directives passed to inlineFragmentA, and let directivesB be the ordered list of Directives passed to inlineFragmentB.
  5. If DirectivesAreEquivalent(directivesA, directivesB) is equal to false:
    1. Return false.
  6. Return true.
FragmentSpreadsAreEqual(fragmentSpreadA, fragmentSpreadB)
  1. Let fragmentNameA be the fragment name of fragmentSpreadA, and let fragmentNameB be the fragment name of fragmentSpreadB.
  2. If fragmentNameA is not equal to fragmentNameB:
    1. Return false.
  3. Let directivesA be the ordered list of Directives passed to fragmentSpreadA, and let directivesB be the ordered list of Directives passed to fragmentSpreadB.
  4. If DirectivesAreEquivalent(directivesA, directivesB) is equal to false:
    1. Return false.
  5. Return true.
DirectivesAreEquivalent(directivesA, directivesB)
  1. Let lengthA be the length of the ordered list directivesA, and let lengthB be the length of the ordered list directivesB.
  2. If lengthA does not equal lengthB:
    1. Return false.
  3. For each directiveA in directivesA:
    1. Let directiveB be the list item in directivesB with the same index as directiveA in directivesA.
    2. Let directiveNameA be the name of directiveA, and let directiveNameB be the name of directiveB.
    3. If directiveNameA is not equal to directiveNameB:
      1. Return false.
    4. Let argumentsA be the unordered set of Arguments passed to directiveA, and let argumentsB be the unordered set of Arguments passed to directiveB.
    5. If ArgumentsAreEquivalent(argumentsA, argumentsB) is equal to false:
      1. Return false.
  4. Return true.
ArgumentsAreEquivalent(argumentsA, argumentsB)
  1. Let sizeA be the size of the unordered set argumentsA, and let sizeB be the size of the unordered set argumentsB.
  2. If sizeA does not equal sizeB:
    1. Return false.
  3. For each argumentA in argumentsA:
    1. Let argumentB be the set item from argumentsB where argumentB has the same name as argumentA.
    2. If argumentB does not exist:
      1. Return false.
    3. Let valueA be the Value of argumentA, and let valueB be the Value of argumentB.
    4. If ValuesAreEquivalent(valueA, valueB) is equal to false:
      1. Return false.
  4. Return true.
ValuesAreEquivalent(valueA, valueB)
  1. If both valueA and valueB are a Variable :
    1. Return true if the name of valueA is equal to the name of valueB, otherwise return false.
  2. If both valueA and valueB are an IntValue:
    1. Return true if the integer represented by valueA is equal to the integer represented by valueB, otherwise return false.
  3. If both valueA and valueB are a FloatValue:
    1. Return true if the float value represented by valueA is equal to the float value represented by valueB, otherwise return false.
  4. If both valueA and valueB are a StringValue:
    1. Return true if the string value represented by valueA is equal to the string value represented by valueB, otherwise return false.
  5. If both valueA and valueB are a BooleanValue:
    1. Return true if the boolean value represented by valueA is equal to the boolean value represented by valueB, otherwise return false.
  6. If both valueA and valueB are a NullValue:
    1. Return true.
  7. If both valueA and valueB are a EnumValue:
    1. Return true if the name of valueA is equal to the name of valueB, otherwise return false.
  8. If both valueA and valueB are a ListValue:
    1. Let lengthA be the length of valueA, and let lengthB be the length of valueB.
    2. If lengthA is not equal to lengthB:
      1. Return false.
    3. For each listItemA in valueA:
      1. Let listItemB be the list item in valueB with the same index as listItemA in valueA.
      2. If ValuesAreEquivalent(listItemA, listItemB) is equal to false:
        1. Return false.
    4. Return true.
  9. If both valueA and valueB are an ObjectValue:
    1. Let sizeA be the size of valueA, and let sizeB be the size of valueB.
    2. If sizeA is not equal to sizeB:
      1. Return false.
    3. For each objectFieldA in valueA:
      1. Let objectFieldB be the ObjectField from valueB where objectFieldB has the same name as objectFieldA.
      2. If objectFieldB does not exist:
        1. Return false.
      3. Let objectValueA be the Value of objectFieldA, and let objectValueB be the Value of objectFieldB.
      4. If ValuesAreEquivalent(objectValueA, objectValueB) is equal to false:
        1. Return false.
    4. Return true.
  10. Return false.
Example
Counter Example № 8{
  user(id: 4) {
    name
    friends {
      name
    }
    name
    friends {
      birthday
      name @uppercase
    }
    nameWithAlias: name
  }
}
Example № 9{
  user(id: 4) {
    name
    friends {
      name
      birthday
      name @uppercase
    }
    nameWithAlias: name
  }
}

2.1.3No Fragment Definitions

A normalized GraphQL document must not contain any FragmentDefinition.

Note Given the requirement that the executable document does not produce any validation errors, the absense of any FragmentDefinition also implies the absense of any FragmentSpread.
Example
Counter Example № 10{
  user(id: 4) {
    ...UserData
  }
}

fragment UserData on User {
  name
}
Example № 11{
  user(id: 4) {
    name
  }
}

2.1.4No Inline Fragments With Redundant Type Condition

A normalized GraphQL document must not contain any InlineFragment that does not have any Directives and that has a TypeCondition defined on the same type as the surrounding selection set.

Example

This example assumes that that the return type of the user field is User.

Counter Example № 12{
  user(id: 4) {
    ... on User {
      name
    }
  }
}
Example № 13{
  user(id: 4) {
    name
  }
}

2.1.5No Inline Fragments Without Context

A normalized GraphQL document must not contain any InlineFragment that does not have a TypeCondition and that does not have any Directives.

Example
Counter Example № 14{
  user(id: 4) {
    ... {
      name
    }
  }
}
Example № 15{
  user(id: 4) {
    name
  }
}

2.1.6No Leading Redundant Interface Selections

A normalized GraphQL document must not contain any leading redundant Selection inside an InlineFragment defined inside a selection set of a parent field that returns an interface type, where an equal selection is already defined in said selection set somewhere before the InlineFragment.

Formally, a selection is considered leading redundant if SelectionIsLeadingRedundant returns true.

SelectionIsLeadingRedundant(selection)
  1. Let selectionSet be the selection set in which selection is defined.
  2. Let parentNode be the node that contains selectionSet.
  3. If parentNode is not an InlineFragments:
    1. Return false.
  4. Let parentSelectionSet be the selection set that contains parentNode.
  5. Let parentType be the result of TypeForSelectionSet(parentSelectionSet).
  6. If parentType is not an interface type:
    1. Return false.
  7. Let previousSelections be the list of selection in parentSelectionSet before parentNode.
  8. If previousSelections is empty:
    1. Return false.
  9. For each previousSelection in previousSelections:
    1. If SelectionsAreEqual(selection, previousSelection) is equal to true:
      1. Return true.
  10. Return false.
TypeForSelectionSet(selectionSet)
  1. Let parentNode be the node that contains selectionSet.
  2. If parentNode is an OperationDefinition:
    1. Let operationType be the operation type of parentNode.
    2. Let rootOperationType be the named type defined in the RootOperationTypeDefinition for operationType.
    3. Return rootOperationType.
  3. If parentNode is a Field:
    1. Let fieldDefinition be the FieldDefinition that defines the field parentNode.
    2. Let outputType be the type of fieldDefinition.
    3. Let unwrappedType be the unwrapped type of outputType.
    4. Return unwrappedType.
  4. If parentNode is an InlineFragment:
    1. Let typeCondition be the TypeCondition of parentNode.
    2. If typeCondition exists:
      1. Let type be the type of typeCondition.
      2. Return type.
    3. If typeCondition does not exist:
      1. Let parentSelectionSet be the selection set that contains parentNode.
      2. Return TypeForSelectionSet(parentSelectionSet).
  5. If parentNode is a FragmentSpread:
    1. Let typeCondition be the TypeCondition of parentNode.
    2. Let type be the type of typeCondition.
    3. Return type.
SelectionsAreEqual(selectionA, selectionB)
  1. If both selectionA and selectionB are Fields:
    1. Return FieldsAreEqual(selectionA, selectionB)
  2. If both selectionA and selectionB are InlineFragments:
    1. Return InlineFragmentsAreEqual(selectionA, selectionB)
  3. If both selectionA and selectionB are FragmentSpreads:
    1. Return FragmentSpreadsAreEqual(selectionA, selectionB)
  4. Return false.
FieldsAreEqual(fieldA, fieldB)
  1. If FieldsAreEquivalent(fieldA, fieldB) is equal to false:
    1. Return false.
  2. Let selectionSetA be the selection set of fieldA, and let selectionSetB be the selection set of fieldB.
  3. If selectionSetA does not exist:
    1. Return true if selectionSetB does not exist, otherwise return false.
  4. If selectionSetB does not exist:
    1. Return false.
  5. Return SelectionSetsAreEqual(selectionSetA, selectionSetB).
InlineFragmentsAreEqual(inlineFragmentA, inlineFragmentB)
  1. If InlineFragmentsAreEquivalent(inlineFragmentA, inlineFragmentB) is equal to false:
    1. Return false.
  2. Let selectionSetA be the selection set of inlineFragmentA, and let selectionSetB be the selection set of inlineFragmentB.
  3. Return SelectionSetsAreEqual(selectionSetA, selectionSetB).
SelectionSetsAreEqual(selectionSetA, selectionSetB)
  1. If the count of selections in selectionSetA is not equal to the count of selections in selectionSetB:
    1. Return false.
  2. For each Selection selectionA in selectionSetA:
    1. Let selectionB be the Selection from selectionSetB at the same index as selectionA in selectionSetA.
    2. If SelectionsAreEqual(selectionA, selectionB) is equal to false:
      1. Return false.
  3. Return true.
Example
Counter Example № 16{
  profile(id: 4) {
    handle
    ... on User {
      handle
      name
    }
  }
}
Example № 17{
  profile(id: 4) {
    handle
    ... on User {
      name
    }
  }
}

2.1.7No Lagging Redundant Interface Selections

A normalized GraphQL document must not contain any lagging redundant Selection that is the first selection inside an InlineFragment defined inside a selection set of a parent field that returns an interface type, where an equal selection is already defined in said selection set immediately after the InlineFragment.

A lagging redundant selection should be replaced by only specifying the selection once immediately before said InlineFragment.

Formally, a selection is considered lagging redundant if SelectionIsLaggingRedundant returns true.

SelectionIsLaggingRedundant(selection)
  1. Let selectionSet be the selection set in which selection is defined.
  2. If selection is not the first Selection in selectionSet:
    1. Return false.
  3. Let parentNode be the node that contains selectionSet.
  4. If parentNode is not an InlineFragments:
    1. Return false.
  5. Let parentSelectionSet be the selection set that contains parentNode.
  6. Let parentType be the result of TypeForSelectionSet(parentSelectionSet).
  7. If parentType is not an interface type:
    1. Return false.
  8. Let nextSelection be the selection in parentSelectionSet after parentNode.
  9. If nextSelection does not exist:
    1. Return false.
  10. Return SelectionsAreEqual(selection, nextSelection).
Example
Counter Example № 18{
  profile(id: 4) {
    ... on User {
      handle
      friends {
        name
      }
    }
    handle
  }
}
Example № 19{
  profile(id: 4) {
    handle
    ... on User {
      friends {
        name
      }
    }
  }
}

2.1.8No Lagging Redundant Interface Selection List

A normalized GraphQL document must not contain any lagging redundant Selection list that is located at the end of a selection set inside an InlineFragment defined inside a selection set of a parent field that returns an interface type, where an equal list of selections is already defined in said selection set immediately after the InlineFragment.

Formally, a list of consecutive selections is considered lagging redundant if SelectionListIsLaggingRedundant returns true.

SelectionListIsLaggingRedundant(selections)
  1. Let selectionSet be the selection set in which the Selections from selections are defined.
  2. If there exists one ore more Selections in selectionSet after selections:
    1. Return false.
  3. Let parentNode be the node that contains selectionSet.
  4. If parentNode is not an InlineFragments:
    1. Return false.
  5. Let parentSelectionSet be the selection set that contains parentNode.
  6. Let parentType be the result of TypeForSelectionSet(parentSelectionSet).
  7. If parentType is not an interface type:
    1. Return false.
  8. Let nextSelections be the list of selections in parentSelectionSet after parentNode with the same length as selections.
  9. If the length of nextSelections is smaller than the length of selections:
    1. Return false.
  10. For each selection in selections:
    1. Let nextSelection be the selection from nextSelections with the same index as selection in selections.
    2. If SelectionsAreEqual(selection, nextSelection) is equal to false:
      1. Return false.
  11. Return true.
Example
Counter Example № 20{
  profile(id: 4) {
    ... on User {
      friends {
        name
      }
      __typename
      handle
    }
    __typename
    handle
  }
}
Example № 21{
  profile(id: 4) {
    ... on User {
      friends {
        name
      }
    }
    __typename
    handle
  }
}

2.1.9No Repeated Interface Selections in Exhaustive Fragment List

A normalized GraphQL document must not contain any lists of adjacent and exhaustive InlineFragments inside of a selection set of a field that returns an interface type where all of the following conditions hold:

Instead, such a Selection identified by the above conditions should only be specified once, namely before (in case it’s the first field in all InlineFragments) or after (in case it’s the last field in all InlineFragments) the list of adjacent and exhaustive InlineFragments.

Given a selection set for a field that returns an interface type interface and a list of adjacent InlineFragments fragments inside this selection set, the list fragments is considered exhaustive if FragmentsAreExhaustive(fragments, interface) returns true.

FragmentsAreExhaustive(fragments, interface)
  1. Let fragmentTypes be an empty set.
  2. For each fragment in fragments:
    1. Let typeCondition be the type condition of fragment.
    2. If typeCondition does not exist:
      1. Continue to the next fragment.
    3. Let type be the name of the type from typeCondition.
    4. If type is an object type:
      1. Add type to fragmentTypes.
    5. If type is an interface type:
      1. Let implementors be the result of Implementors(type).
      2. Add all set items of implementors to fragmentTypes.
  3. Let interfaceImplementors be the result of Implementors(interfaceType).
  4. For each implementor in interfaceImplementors:
    1. If fragmentTypes does not contain implementor:
      1. Return false.
  5. Return true.
Implementors(interfaceType)
  1. Let objectImplementors be the set of object types that implement the interface type interfaceType.
  2. Let interfaceImplementors be the set of interface types that implement the interface type interfaceType.
  3. For each interface in interfaceImplementors:
    1. Let objects be the result of calling Implementors(interface).
    2. Add all entries of the set objects to the set objectImplementors.
  4. Return objectImplementors.
Example

For this example, assume that the field profile returns an interface type Profile, and that there exist exactly two object types User and Organization that implement this interface type. Furthermore, assume that the Profile interface defines a field named handle.

Counter Example № 22{
  profile(id: 4) {
    ... on Organization {
      handle
      members {
        name
      }
    }
    ... on User {
      handle
      name
    }
  }
}
Example № 23{
  profile(id: 4) {
    handle
    ... on Organization {
      members {
        name
      }
    }
    ... on User {
      name
    }
  }
}
Note Assume there would exist a third object type Influencer that also implements the interface Profile. Then the counter example above would actually be normalized because the list of inline fragments would not be exhaustive, and there does not exist a selection for the field handle for the type Influencer.

2.1.10No Constant @skip Directive

A normalized GraphQL document must not contain any @skip directive where the Value passed to the if argument is a BooleanValue.

Example
Counter Example № 24{
  user(id: 4) {
    name
    ... @skip(if: true) {
      birthday
    }
    ... @skip(if: false) {
      friends {
        name
      }
    }
  }
}
Example № 25{
  user(id: 4) {
    name
    friends {
      name
    }
  }
}

2.1.11No Constant @include Directive

A normalized GraphQL document must not contain any @include directive where the Value passed to the if argument is a BooleanValue.

Example
Counter Example № 26{
  user(id: 4) {
    name
    ... @include(if: true) {
      birthday
    }
    ... @include(if: false) {
      friends {
        name
      }
    }
  }
}
Example № 27{
  user(id: 4) {
    name
    birthday
  }
}

2.2Ordering

In the GraphQL query language there exist unordered sets of items where the order in textual representation does not affect the semantics of the set. All such sets must be ordered alphabetically by some identifier according to the rules in this section.

2.2.1Ordered Definitions

In order to be normalized, all OperationDefinitions in a GraphQL document must be ordered alphabetically by their name. An OperationDefinition without a name must be the first definition in the GraphQL document. All Definitions that are not an OperationDefinition must be ordered after all OperationDefinitions.

Example
Counter Example № 28query User {
  user(id: 4) {
    name
  }
}

query Profile {
  profile(userId: 4) {
    handle
  }
}

{
  user(id: 5) {
    birthday
  }
}
Example № 29{
  user(id: 5) {
    birthday
  }
}

query Profile {
  profile(userId: 4) {
    handle
  }
}

query User {
  user(id: 4) {
    name
  }
}

2.2.2Ordered Variable Definitions

In order to be normalized, all VariableDefinitions in a GraphQL document must be ordered alphabetically by Variable name.

Example
Counter Example № 30query ($id: Int, $friendName: String) {
  user(id: $id) {
    friend(name: $friendName) {
      birthday
    }
  }
}
Example № 31query ($friendName: String, $id: Int) {
  user(id: $id) {
    friend(name: $friendName) {
      birthday
    }
  }
}

2.2.3Ordered Arguments

In order to be normalized, all Arguments in a GraphQL document must be ordered alphabetically by Argument name.

Example
Counter Example № 32{
  user(name: "Bill", birthday: "1955-10-28") {
    name
  }
}
Example № 33{
  user(birthday: "1955-10-28", name: "Bill") {
    name
  }
}

2.2.4Ordered Input Object Values

In order to be normalized, all ObjectValue in a GraphQL document must be ordered alphabetically by ObjectField name.

Example
Counter Example № 34{
  user(input: { name: "Bill", birthday: "1955-10-28" }) {
    name
  }
}
Example № 35{
  user(input: { birthday: "1955-10-28", name: "Bill" }) {
    name
  }
}

2.2.5Ordered Non-Overlapping Inline Fragments

In order to be normalized, any two adjacent InlineFragments in a GraphQL document that are non-overlapping and that don’t have any custom directive applied must be ordered alphabetically by the type name of their TypeCondition. Two InlineFragments are considered non-overlapping if InlineFragmentsOverlap returns false.

Note This specification assumes that any custom directive may influence the execution of the given document. Hence, this rule is formulated defensively and explicitly excludes inline fragments annotated with a custom directive.
InlineFragmentsOverlap(inlineFragmentA, inlineFragmentB)
  1. Let typeConditionA be the type condition of inlineFragmentA, and let typeConditionB be the type condition of inlineFragmentB.
  2. If typeConditionA does not exist:
    1. Return true.
  3. If typeConditionB does not exist:
    1. Return true.
  4. Let typeNameA be the name of the type from typeConditionA, and let typeNameB be the name of the type from typeConditionB.
  5. Let typeA be the schema type with the name typeNameA, and let typeB be the schema type with the name typeNameB.
  6. Return TypesOverlap(typeA, typeB).
TypesOverlap(typeA, typeB)
  1. If both typeA and typeB are object types:
    1. Return true if typeA is equal to typeB, otherwise return false.
  2. If both typeA and typeB are interface types:
    1. Let implementorsA be the result of Implementors(typeA), and let implementorsB be the result of Implementors(typeB).
    2. Return true if the intersection of the sets implementorsA and implementorsB is not an empty set, otherwise return false.
  3. If both typeA and typeB are union types:
    1. Let memberTypesA be the set of union member types of typeA, and let memberTypesB be the set of union member types of typeB.
    2. Return true if the intersection of the sets memberTypesA and memberTypesB is not an empty set, otherwise return false.
  4. If one of typeA and typeB is an object type, and one of typeA and typeB is an interface type:
    1. Let objectType be the one type of typeA and typeB that is an object type.
    2. Let interfaceType be the one type of typeA and typeB that is an interface type.
    3. Let implementors be the result of calling Implementors(interfaceType).
    4. Return true if implementors contains objectType, otherwise return false.
  5. If one of typeA and typeB is an object type, and one of typeA and typeB is a union type:
    1. Let objectType be the one type of typeA and typeB that is an object type.
    2. Let unionType be the one type of typeA and typeB that is an union type.
    3. Let memberTypes be the set of union member types of unionType.
    4. Return true if memberTypes contains objectType, otherwise return false.
  6. If one of typeA and typeB is an interface type, and one of typeA and typeB is a union type:
    1. Let interfaceType be the one type of typeA and typeB that is an interface type.
    2. Let unionType be the one type of typeA and typeB that is an union type.
    3. Let implementors be the result of Implementors(interfaceType).
    4. Let memberTypes be the set of union member types of unionType.
    5. Return true if the intersection of the sets implementors and memberTypes is not an empty set, otherwise return false.
  7. Return false.
Example for Interface Types

For this example, assume that the field profile returns an interface type Profile, and that there exist two object types User and Organization that implement this interface type.

Counter Example № 36{
  profile(id: 4) {
    handle
    ... on User {
      name
    }
    ... on Organization {
      members {
        name
      }
    }
  }
}
Example № 37{
  profile(id: 4) {
    handle
    ... on Organization {
      members {
        name
      }
    }
    ... on User {
      name
    }
  }
}
Example for Union Types

For this example, assume that the field userResult returns a union type with two member types User and Error.

Counter Example № 38{
  userResult(id: 4) {
    ... on User {
      name
    }
    ... on Error {
      message
    }
  }
}
Example № 39{
  userResult(id: 4) {
    ... on Error {
      message
    }
    ... on User {
      name
    }
  }
}
Note Since only object types are valid union member types, two fragments with different type conditions inside a selection set for a union type can never overlap.
Example for Nested Interface Types

For this example, assume the following GraphQL schema.

type Query {
  node(id: ID): Node
}

interface Node {
  id: ID
}

interface InterfaceA implements Node {
  id: ID
  fieldA: String
}

interface InterfaceB implements Node {
  id: ID
  fieldB: String
}

type ObjectA implements Node & InterfaceA {
  id: ID
  fieldA: String
}

type ObjectB implements Node & InterfaceB {
  id: ID
  fieldB: String
}

type ObjectAB implements Node & InterfaceA & InterfaceB {
  id: ID
  fieldA: String
  fieldB: String
}

Then the following query is normalized.

Example № 40{
  node(id: 4) {
    ... on InterfaceB {
      fieldB
    }
    ... on InterfaceA {
      fieldA
    }
  }
}

The two inline fragments cannot be reordered without potentially changing the order of fields in the execution result. If the returned node if of type ObjectAB then both type conditions of the two inline fragments will match, and the fields are returned in the order in which the inline fragments are included in the selection set.

Note If the object type ObjectAB would not exist, then the example query above would not be normalized, because InterfaceA and InterfaceB would not overlap anymore. In other words, for all possible object types returned by the node field only one of the inline fragments could be applied, but never both.

3Type System Documents

basically also just sort things

§Index

  1. ArgumentsAreEquivalent
  2. DirectivesAreEquivalent
  3. FieldsAreEqual
  4. FieldsAreEquivalent
  5. FragmentsAreExhaustive
  6. FragmentSpreadsAreEqual
  7. Implementors
  8. InlineFragmentsAreEqual
  9. InlineFragmentsAreEquivalent
  10. InlineFragmentsOverlap
  11. SelectionIsLaggingRedundant
  12. SelectionIsLeadingRedundant
  13. SelectionListIsLaggingRedundant
  14. SelectionsAreEqual
  15. SelectionsAreEquivalent
  16. SelectionSetsAreEqual
  17. ShouldSeparateWithSpace
  18. TypeForSelectionSet
  19. TypesOverlap
  20. ValuesAreEquivalent
  1. 1Overview
    1. 1.1Printing Normalized GraphQL Documents
      1. 1.1.1Minimize ignored tokens
      2. 1.1.2Printing strings
  2. 2Executable Documents
    1. 2.1Selections
      1. 2.1.1No Redundant Field Alias
      2. 2.1.2No Duplicate Selections
      3. 2.1.3No Fragment Definitions
      4. 2.1.4No Inline Fragments With Redundant Type Condition
      5. 2.1.5No Inline Fragments Without Context
      6. 2.1.6No Leading Redundant Interface Selections
      7. 2.1.7No Lagging Redundant Interface Selections
      8. 2.1.8No Lagging Redundant Interface Selection List
      9. 2.1.9No Repeated Interface Selections in Exhaustive Fragment List
      10. 2.1.10No Constant @skip Directive
      11. 2.1.11No Constant @include Directive
    2. 2.2Ordering
      1. 2.2.1Ordered Definitions
      2. 2.2.2Ordered Variable Definitions
      3. 2.2.3Ordered Arguments
      4. 2.2.4Ordered Input Object Values
      5. 2.2.5Ordered Non-Overlapping Inline Fragments
  3. 3Type System Documents
  4. §Index