Docs
Try Apollo Studio

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:

StarWarsSchema.graphql
type Query {
hero: Character!
}
interface Character {
name: String!
}
type Human implements Character {
name: String!
}
type Droid implements Character {
name: String!
primaryFunction: String!
}

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:

HeroAsDroidQuery.graphql
query HeroAsDroid {
hero {
name
primaryFunction # Field "primaryFunction" does not exist on type "Character"
}
}

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

Inline Fragment
query HeroAsDroid {
hero {
name
... on Droid {
primaryFunction
}
}
}
Named Fragment
query HeroAsDroid {
hero {
name
...DroidDetails
}
}
fragment DroidDetails on Droid {
name
primaryFunction
}

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:

HeroAsDroidQuery.graphql.swift
class HeroAsDroidQuery: GraphQLQuery {
struct Data: SelectionSet {
let hero: Hero
struct Hero: SelectionSet {
let name: String
var asDroid: AsDroid? { ... }
// Type Condition Declaration
struct AsDroid: InlineFragment {
let name: String
let primaryFunction: String
}
}
}
}

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.

let 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:

Query & Fragment Definitions
query HeroAsDroid {
hero {
name
...DroidDetails
}
}
fragment DroidDetails on Droid {
name
primaryFunction
}
Generated Model
class HeroAsDroidQuery: GraphQLQuery {
struct Data: SelectionSet {
let hero: Hero
struct Hero: SelectionSet {
let name: String
var asDroid: AsDroid? { ... }
// Type Condition Declaration
struct AsDroid: InlineFragment {
let name: String
let primaryFunction: String
// Fragment Conversion Declaration
var fragments: Fragments
struct Fragments {
var droidDetails: DroidDetails { ... }
}
}
}
}
}

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.

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

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

Edit on GitHub
Previous
Error Handling
Next
Custom Scalars