Swift 5.1, Apollo 0.21.0
The keys and values in your dictionary need to adhere to the Apollo JSONEncodable protocol:
public protocol JSONEncodable: GraphQLInputValue {
var jsonValue: JSONValue { get }
}
You need to loop through your Dictionary and return each Object with a .jsonValue (JSONEncodable protocol).
[String : Any?] vs [String : String]
If you pass a Dictionary of [String : String] into Apollo, it will automagically work because String conforms to the JSONEncodable protocol. Both key and value are type String.
JSON is usually represented in Swift as [String : Any?] which means the key must be String, but the value can by Any object (Array, Bool, Double, Null, String, Dictionary).
Because Apollo doesn’t know what the Any object is, it will cause a SIGABRT. This is because the value could be a custom class you wrote that is not JSON compatible.
You must cast the Any object to a class that conforms to the JSONEncodable protocol.
Since [String : Any?] by default cannot define the Any objects, the Generic JSON library does this by creating a new class to represent the JSON data.
The example below extends the JSONEncodable protocol to the GenericJSON class to ensure the value adheres to the JSONEncodable protocol Apollo requires for a mutation.
Building a Dictionary that adheres to the JSONEncodable Protocol
- Add the Generic JSON library to your pod file:
https://github.com/zoul/generic-json-swift
pod 'GenericJSON'
- Import the GenericJSON library and create an alias for your custom JSON GraphQL scalar in some ApolloExtensions.swift file. This alias will map to the GenericJSON library:
import GenericJSON
// CUSTOM JSON SCALAR
public typealias MyJsonScalar = JSON
- In the ApolloExtensions.swift file, add a JSONEncodable extension for the GenericJSON JSON:
extension JSON: JSONEncodable {
public var jsonValue: JSONValue {
if self.objectValue != nil {
return jsonObject as JSONObject
}
if self.arrayValue != nil {
var array : Array<JSONEncodable> = []
for obj in self.arrayValue! {
if obj.arrayValue != nil {
array.append(obj.jsonValue as! Array<JSONEncodable>)
} else if obj.objectValue != nil {
array.append(obj.jsonValue as! JSONObject)
} else {
array.append(obj.jsonValue as! JSONEncodable)
}
}
return array as Array<JSONEncodable>
}
if self.stringValue != nil {
return self.stringValue! as String
}
if self.doubleValue != nil {
return self.doubleValue! as Double
}
if self.boolValue != nil {
return self.boolValue! as Bool
}
if self.isNull {
return "" as String
}
return "" as String
}
public var jsonObject: JSONObject {
var jsonObject : JSONObject = JSONObject(minimumCapacity: self.objectValue!.count)
for (key, value) in self.objectValue! {
if value.arrayValue != nil {
jsonObject[key] = value.jsonValue as! Array<JSONEncodable>
} else if value.objectValue != nil {
jsonObject[key] = value.jsonValue as! JSONObject
} else {
jsonObject[key] = value.jsonValue as! JSONEncodable
}
}
return jsonObject
}
}
- Create a JSON object from your dictionary and pass it into your GraphQL mutation:
func createJSONDictionary() {
let myDictionary : [String: Any?] = ["foo" : "foo", "bar" : 2]
do {
let jsonData : Data = try JSONSerialization.data(withJSONObject: myDictionary, options: [])
if let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String : Any?] {
let json: JSON = try JSON(jsonObject)
self.myGraphQLMutation(json: json)
} else {
// casting error
}
} catch {
// json error
}
}
func myGraphQLMutation(json: JSON) {
// apollo
let apollo : ApolloClient = ApolloHelper.shared.client
// myMutation
let myMutation = MyMutation(json: json)
// perform
apollo.perform(mutation: myMutation, queue: DispatchQueue.global()) { result in
switch result {
case .success(let graphQLResult):
// Deal with GraphQLResult and its data and/or errors properties here
break
case .failure(let error):
// deal with network errors here
return
}
}
}