Reputation: 3400
I'd like to make a query with lot of input parameter:
apollo_client.mutate({mutation: gql
`mutation mutation($title: String!, $caption: String!, $id: Int!){
mutatePmaHome(pmaData: {id:$id, title: $title, caption: $caption}) {
pma{
title
caption
}
}
}`,
variables: {
title: this.state.title,
caption: this.state.caption,
id: this.state.id
},
}).then(console.log);
This is how look my query when I pass simple datastructure (Int, String) But my question is: How can I pass a dictionnary or object in the mutation query. I don't have any idea how can I type it.
The doc from graphQL say I must create an input object. But here I'm on js with apollo, so how can I pass a dictionnary on my query in order to not type all data (here 3, but it can be 10 to 20). I'd like a cleaner approach with an input type field, but I can't find a single example.
Thanks
Upvotes: 2
Views: 2692
Reputation: 563
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
https://github.com/zoul/generic-json-swift
pod 'GenericJSON'
import GenericJSON
// CUSTOM JSON SCALAR
public typealias MyJsonScalar = 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
}
}
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
}
}
}
Upvotes: 3
Reputation: 3615
The way I see it, there are two approaches you can take.
The safer approach is to create a type containing a key and a value. Then, your dictionary would be a list of keys and values. Instead of accessing a property as let value = dictionary[key]
, you would then need to use let entry = dictionary.find((entry) => entry.key === key); let value = entry && entry.value
.
// Before
{
red: 1,
blue: 2,
green: 3,
}
// After
[
{key: 'red', value: 1},
{key: 'blue', value: 2},
{key: 'green', value: 3},
]
Typedefs:
type IntegerProperty {
key: ID!
value: Int
}
type IntegerDictionary {
values: [IntegerProperty!]!
}
A quicker but less typesafe approach is to use something like graphql-type-json
or graphql-json-object-type
. These permit you to include arbitrary JSON in your GraphQL query; however, you then lose the guarantees that GraphQL makes on the format of your data.
Upvotes: 1