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:
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:
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.
1query HeroAsDroid {
2 hero {
3 name
4 ... on Droid {
5 primaryFunction
6 }
7 }
8}
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:
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.
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
:
1query HeroAsDroid {
2 hero {
3 name
4 ...DroidDetails
5 }
6}
7
8fragment DroidDetails on Droid {
9 name
10 primaryFunction
11}
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.
1let droidDetails: DroidDetails? = hero.asDroid?.fragments.droidDetails
Learn more about how to use named fragments to generate better models in our Fragments documentation.