Docs
Launch GraphOS Studio

Custom Scalars


In addition to its built-in scalar types (Int, String, etc.), GraphQL supports defining custom scalars. For example, your might provide a custom for Date, UUID, or GeoLocation.

Custom s are initially defined as part of a . To interact with a schema using custom scalars, your client must define a Swift type to use for each custom scalar.

Apollo iOS automatically defines Swift types for all of the built-in scalar types:

GraphQL TypeSwift Type
IntInt
FloatDouble
BooleanBool
StringString
IDString

By default, each custom is treated as a Swift String, but you can customize the type of all of your custom s with Apollo iOS!

Generating custom scalar types

If any part of your GraphQL application references a custom defined by the , a file for it will be generated in your generated schema output. This file can be used to define the Swift type for the custom scalar.

For example, in a that defines a custom :

MySchema.graphqls
scalar UUID

Apollo iOS generates a UUID custom type in your generated output. This generated file defines UUID as a String by default.

MySchema/CustomScalars/UUID.swift
public extension MySchema {
typealias UUID = String
}

The MySchema.UUID type will be referenced in any other generated objects that reference the UUID .

Because custom files are only generated once, they can then be edited, and your changes will never be overwritten by subsequent code generation execution.

You can edit this file to define a different type for the UUID type.

Note: Custom s may be referenced by s on GraphQL types, values for input parameters, or on input objects. Because custom scalars are only generated when they are referenced by your s, they may be added to your project during any future execution of code generation, not just on the initial execution.

Defining a custom scalar type

You can define the type for a custom by creating a new type, or by pointing the type to another existing type.

The type for your custom must conform to the CustomScalarType protocol. This requires you to implement the JSON serialization functionality for your custom type.


To implement the CustomScalarType protocol:

1. Implement the _jsonValue property.

This converts the value into a JSONValue that can be serialized into a JSON dictionary. The value of the _jsonValue property will be stored in the cache as the JSON value for the custom .

Usually, this should be identical to the value received from the network response to represent the custom .

2. Implement the init(_jsonValue:) initializer.

This initializer is used to construct the custom value from a JSONValue. When constructing the from a network response, the value of the _jsonValue parameter will be the value from the network response. When constructing the from a cached value, this will be the value provided in the _jsonValue property.

If the value provided is unrecognized, you should throw an error from this function. Apollo iOS provides JSONDecodingError in the ApolloAPI library, but you may throw any custom error you wish.

3. Implement the Hashable and Equatable protocols if needed.

If your custom type does not already conform to Hashable and Equatable, you will need to implement hash(into:) and the == operator to conform to each of these protocols respectively.

Example: UUID

For example, you could point the typealias for UUID to the Foundation.UUID type:

MySchema/CustomScalars/UUID.swift
import Foundation
public extension MySchema {
typealias UUID = Foundation.UUID
}
extension Foundation.UUID: CustomScalarType {
public init (_jsonValue value: JSONValue) throws {
guard let uuidString = value as? String,
let uuid = UUID(uuidString: uuidString) else {
throw JSONDecodingError.couldNotConvert(value: value, to: Foundation.UUID.self)
}
self = uuid
}
public var _jsonValue: JSONValue {
uuidString
}
}

Example: GeoPoint

Alternatively, you could create your own custom type. In this case, replace the typealias with the new type.

For example, a custom GeoPoint that has a JSON representation of "100.0,10.0" could be implemented like this:

MySchema/CustomScalars/GeoPoint.swift
import Foundation
extension MySchema {
public struct GeoPoint: CustomScalarType, Hashable {
let x: Float
let y: Float
public init (_jsonValue value: JSONValue) throws {
let coordinates = try (value as? String)?
.components(separatedBy: ",")
.map { try Float(_jsonValue: $0) }
guard let coordinates, coordinates.count == 2 else {
throw JSONDecodingError.couldNotConvert(value: value, to: GeoPoint.self)
}
self.x = coordinates[0]
self.y = coordinates[1]
}
public var _jsonValue: JSONValue {
"\(String(format: "%.1f", x)),\(String(format: "%.1f", y))"
}
}
}

The GeoPoint struct conforms to both CustomScalarType and Hashable. You must explicitly declare the conformance to Hashable, which inherits Equatable conformance. Because Swift can synthesize the Hashable and Equatable conformancs here, you do not need to implement them.

The init(_jsonValue:) initializer casts the JSONValue as a String and separates it into two coordinates. It converts those coordinates using Float(_jsonValue:) which is provided by Apollo. Each of the built-in scalar types has JSON serialization support that you can use within your custom implementations.

To ensure consistency of the serialized JSON, the _jsonValue function ensures that the coordinates are formatted with a single decimal point using String(format:,_:).

JSON and other custom scalars with multiple return types

Some custom s are set up to potentially return multiple types at runtime. This is not ideal since you lose type safety, but if you're using an API you don't have control over, there's often not a great alternative to this.

When this happens, because you don't know the type that's coming in, you can't set up a single typealias for that . Instead, you need to define some other way of instantiating your custom scalar object.

This happens most often with JSON, which can return either an array or a dictionary. Here's an example of how you can use an enum to allow dynamic-but-limited types to parse (with CustomJSON as a placeholder type name`):

MySchema/CustomScalars/CustomJSON.swift
extension MySchema {
public enum CustomJSON: CustomScalarType, Hashable {
case dictionary([String: AnyHashable])
case array([AnyHashable])
public init(_jsonValue value: JSONValue) throws {
if let dict = value as? [String: AnyHashable] {
self = .dictionary(dict)
} else if let array = value as? [AnyHashable] {
self = .array(array)
} else {
throw JSONDecodingError.couldNotConvert(value: value, to: CustomJSON.self)
}
}
public var _jsonValue: JSONValue {
switch self {
case let .dictionary(json as AnyHashable),
let .array(json as AnyHashable):
return json
}
}
public static func == (lhs: CustomJSON, rhs: CustomJSON) -> Bool {
lhs._jsonValue == rhs._jsonValue
}
public func hash(into hasher: inout Hasher) {
hasher.combine(_jsonValue)
}
}
}
Previous
Type Conditions
Next
Persisted Queries
Edit on GitHubEditForumsDiscord