自定义标量
除了其内置标量类型内建类型 (Int
, String
, 等.), GraphQL 支持自定义类型。例如,您的模式可能提供用于 Date
、UUID
或 GeoLocation
自定义类型最初是在模式中定义的。要使用自定义类型与模式交互,您的客户端必须为每个自定义类型定义一个Swift类型。
例如,Apollo iOS会自动为所有内建类型定义Swift类型:
GraphQL类型 | Swift类型 |
---|---|
Int | Int |
Float | Double |
Boolean | 布尔值 |
字符串 | 字符串 |
标识符 | 字符串 |
生成自定义标量类型
scalar UUID
public extension MySchema {typealias UUID = String}
注意:自定义标量可以由字段在GraphQL类型中引用,或作为输入参数的值,或引用输入对象。因为自定义标量仅在它们被您的操作引用时生成,因此它们可以在代码生成的任何未来执行中添加到项目中,而不仅仅是初始执行。
定义自定义标量类型
您可以通过创建一个新类型,或通过将类型别名指向另一个现有类型来定义自定义标量类型的类型。
您的自定义标量类型必须符合CustomScalarType
协议。这需要您实现自定义标量类型的JSON序列化功能。
要实现CustomScalarType
协议:
1. 实现属性 _jsonValue
。
这个属性将标量值转换为可以序列化为JSON字典的JSONValue。属性_jsonValue
的值将被存储为自定义标量的缓存中的JSON值。
通常,这应该与表示自定义标量的网络响应值相同。
2. 实现初始化器 init(_jsonValue:)
。
此初始化器用于从JSONValue
构建自定义标量值。当从网络响应构建标量时,_jsonValue
参数将是网络响应中的值。当从缓存值构建标量时,这将是在_jsonValue
属性中提供的值。
如果提供的值未被识别,您应该从这个函数中抛出一个错误。Apollo iOS在JSONDecodingError
库中提供了JSONDecodingError
,但您可以抛出任何您希望抛出的自定义错误。
3. 如有需要,实现Hashable
和Equatable
协议。
如果你的自定义标量类型尚未符合Hashable
和Equatable
,你需要实现hash(into:)
和==
操作符,分别符合这两个协议。
示例:UUID
例如,你可以将typealias
将UUID
指向Foundation.UUID
类型:
import Foundationpublic 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}}
示例:GeoPoint
或者,你可以创建自己的自定义标量类型。在这种情况下,将typealias
替换为新类型。
例如,一个自定义标量 namedGeoPoint,其JSON表示为"100.0,10.0"
可以实现如下:
import Foundationextension MySchema {public struct GeoPoint: CustomScalarType, Hashable {let x: Floatlet y: Floatpublic 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))"}}}
该GeoPoint
结构体符合CustomScalarType
和Hashable
。你必须显式声明符合Hashable
,它继承了Equatable
的符合性。因为Swift可以在这里合成Hashable
和Equatable
的符合性,所以你不需要实现它们。
该init(_jsonValue:)
初始化器将JSONValue
转换为String
并将其分离为两个坐标。它使用Float(_jsonValue:)
将这些坐标转换为浮点数,这是Apollo提供的。每个内建标量类型都支持JSON序列化,你可以在自定义标量实现中使用。
为了确保序列化JSON的一致性,的_jsonValue
函数确保使用String(format:,_:)
将坐标格式化为使用单个小数点。
具有多个返回类型的JSON和其他自定义标量
一些自定义标量可能在运行时返回多个类型。这并不理想,因为它会失去类型安全性,但如果你使用了一个你无法控制的API,通常没有更好的替代方案。
当发生这种情况时,因为你不知道传入的类型,所以你不能为这个标量设置一个单一的typealias
。相反,你需要定义另一种方式来实例化你的自定义标量对象。
这种情况最常见于JSON,它可以返回数组或字典。以下是一个例如何使用枚举来实现有限的动态类型解析(使用CustomJSON
作为占位符类型名):
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)}}}