Type Conditions


The GraphQL type system includes interfaces and unions as abstract types that object types can conform to. A field in a GraphQL schema with an abstract return type may return any concrete type that can be converted to the abstract type.

Consider the following schema:

GraphQL
StarWarsSchema.graphql
1type Query {
2  hero: Character!
3}
4
5interface Character {
6  name: String!
7}
8
9type Human implements Character {
10  name: String!
11}
12
13type Droid implements Character {
14  name: String!
15  primaryFunction: String!
16}

Since both the Human and Droid types implement the Character interface, they can both be converted into the abstract type Character. That means the result of a field of the Character type (ie. hero), the can be either a Human or a Droid. When we query the hero field, the server doesn't return a response object of the Character type, it returns either a concrete type that implements the Character interface. In this case, that is either a Human or a Droid.

Type conversion

In order to know if the type of an object in a response can be converted to another type, we have to know the concrete type of the object. We can request that the server provides us the concrete type of the object by fetching the __typename metadata field.

Apollo iOS automatically augments your queries to add the __typename field to every object in your operations. This is primarily to support conditional type conversion, but it means a __typename property is always defined and can be used to differentiate between object types manually if needed.

An object can be converted to an another type based on the following rules, where the "target type" is the type that we are attempting to convert to:

  • If the target type is a concrete object type:

    • If the object's type is exactly the target type.

  • If the target type is an interface:

    • If the object's type implements the target interface type.

  • If the target type is a union:

    • If the target union type's set of possible types includes the object's type.

Querying with type conditions

Whenever we want to query type-specific fields on an object with an abstract type, we have to use a type condition.

In this example, we want to query the primaryFunction on the hero field, but we can only query the primaryFunction field if the returned hero is a Droid type. The following query definition is invalid and does not compile:

GraphQL
HeroAsDroidQuery.graphql
1query HeroAsDroid {
2  hero {
3    name
4    primaryFunction # Field "primaryFunction" does not exist on type "Character"
5  }
6}

We can query the primaryFunction field using a fragment on the Droid type. This can be either a named fragment or an inline fragment.

GraphQL
Inline Fragment
1query HeroAsDroid {
2  hero {
3    name
4    ... on Droid {
5      primaryFunction
6    }
7  }
8}
GraphQL
Named Fragment
1query HeroAsDroid {
2  hero {
3    name
4    ...DroidDetails
5  }
6}
7
8fragment DroidDetails on Droid {
9  name
10  primaryFunction
11}

Accessing conditional response data

Using a fragment on another type creates a type condition in the generated model objects.

In order to access the primaryFunction on the Hero model, we need to know if the hero is a droid. Apollo iOS will generate a Hero model with an optional asDroid property:

Swift
HeroAsDroidQuery.graphql.swift
1class HeroAsDroidQuery: GraphQLQuery {
2  struct Data: SelectionSet {
3    let hero: Hero
4
5    struct Hero: SelectionSet {
6      let name: String
7      var asDroid: AsDroid? { ... }
8
9      // Type Condition Declaration
10      struct AsDroid: InlineFragment {
11        let name: String
12        let primaryFunction: String
13      }
14    }
15  }
16}

The hero.asDroid property is optional, and will only return a value if the type of the hero object in the response data is a droid. We can now conditionally convert the Hero to an AsDroid model and access the primaryFunction property.

Swift
1let primaryFunction: String? = hero.asDroid?.primaryFunction

Converting to a conditional named fragment

If the type condition is created by a named fragment, the asDroid object can also be used to perform fragment conversion.

Here we declare the type condition with a fragment named DroidDetails:

GraphQL
Query & Fragment Definitions
1query HeroAsDroid {
2  hero {
3    name
4    ...DroidDetails
5  }
6}
7
8fragment DroidDetails on Droid {
9  name
10  primaryFunction
11}
Swift
Generated Model
1class HeroAsDroidQuery: GraphQLQuery {
2  struct Data: SelectionSet {
3    let hero: Hero
4
5    struct Hero: SelectionSet {
6      let name: String
7
8      var asDroid: AsDroid? { ... }
9
10      // Type Condition Declaration
11      struct AsDroid: InlineFragment {
12        let name: String
13        let primaryFunction: String
14
15        // Fragment Conversion Declaration
16        var fragments: Fragments
17        struct Fragments {
18          var droidDetails: DroidDetails { ... }
19        }
20      }
21    }
22  }
23}

In order to convert the Hero into a DroidDetails fragment, we need to know if the hero is a droid. With a named fragment, Apollo iOS generates a Hero.AsDroid model that includes a conversion to DroidDetails. We can now convert to an optional DroidDetails fragment.

Swift
1let droidDetails: DroidDetails? = hero.asDroid?.fragments.droidDetails

Learn more about how to use named fragments to generate better models in our Fragments documentation.

Feedback

Edit on GitHub

Forums