Any recommendations for handling a GraphQL query t...
# apollo-kotlin
j
Any recommendations for handling a GraphQL query that returns a response field with type of
GraphQLJSONObject
from https://github.com/taion/graphql-type-json? Is it just a case of defining a custom type adapter here? If so, are they any known adapters baked already for turning this into a map of primitives?
m
Hi šŸ‘‹ Unfortunately we do not support mapping custom scalar to generic types (see https://github.com/apollographql/apollo-kotlin/issues/3243).
2 solutions: 1. you can wrap your map into a custom class:
Copy code
class MyJsonObject(val fields: Map<String, Any?>)
register the mapping:
Copy code
apollo {
  mapScalar("JSONObject", "com.example.MyJsonObject")
}
register the adapter:
Copy code
apolloClientBuilder.addCustomScalarAdapter(JSONObject.type, object : Adapter<MyJsonObject> {
  override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): MyJsonObject {
    return MyJsonObject(reader.readAny() as Map<String, Any?>)
  }

  override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: MyJsonObject) {
    writer.writeAny(value.fields)
  }
})
2. you can use the builtin
AnyAdapter
:
Copy code
apollo {
  mapScalarToKotlinAny("JSONObject")
}
2. is simpler but will requires casting to
Map<String, Any?>
when using such fields
s
These are the available adapters right now if youā€™re curious. (Which note that I only found in the migration file in the docs. Maybe they deserve a spot in the normal docs as well for someone who isnā€™t migrating from 2.x. I couldnā€™t find them mentioned there, did I miss it?) We have the exact same use case and weā€™re doing the mapping to and from a JSONObject from a scalar we call JSONString like this. Then at least we get to work with JSONObject, which I personally donā€™t love but it seems to work for us. Hereā€™s a test too.
m
The doc for Apollo provided adapters is there. Moving forward, we'll certainly want to encourage using the Gradle options directly (
mapScalarToKotlinAny()
, etc...)
Copy code
return JSONObject(reader.nextString()!!)
This only works if the Json is encoded as a Json string, right? (so there is some wrapping). I though the GraphQL
JSONObject
didn't require wrapping?
So for a schema like this:
Copy code
scalar JSONObject
type Query {
  json: JSONObject
}
This is a valid response:
Copy code
{
  "json": {
    "key": "value"
  }
}
But not this:
Copy code
{
  "json": "{\"key\": \"value\"}"
}
s
Thanks, I completely missed it! What do you mean by using the gradle options directly? To provide a custom adapter youā€™d still have to register the adapter in code, and sometimes the existing ones (like mapScalarToKotlinAny() etc) donā€™t suffice for our custom scalars. I probably misunderstood something.
This only works if the Json is encoded as a Json string, right?
Yes our JSONString scalar is used exactly like this, the backend sends a ā€œproperā€ json string in there. It seems to work when thereā€™s no indentation either here. What am I misunderstanding this time? šŸ˜„
m
sometimes the existing ones (like mapScalarToKotlinAny() etc) donā€™t suffice for our custom scalars
It does now šŸ™‚ . @bod added the possibility to pass the constructor/object adapter to
mapScalar
so that it is "baked" in the codegen:
Copy code
fun mapScalar(graphQLName: String, targetName: String, expression: String)
Can be used like so:
Copy code
mapScalar("Date", "com.example.Date", "com.example.DateAdapter")
where
DateAdapter
is an object implementing
Adapter<Date>
mapScalarToKotlinAny()
will "bake" the builtin
AnyAdapter
in the codegen. Obviously, you still need
apollo-adapters
in the classpath (we could add it automatically but we have decided not to touch the dependencies for
apollo-api
so it might be weird to do it for
apollo-adapters
)
Yes our JSONString scalar is used exactly like this, the backend sends a ā€œproperā€ json string in there.
This is very fine but I think this is not what https://github.com/taion/graphql-type-json is about. I might be mistaken though. Because there's no real spec for this, different implementations do things differently
s
One thing I was wondering about providing the path to the adapter in the gradle file to avoid submitting it in runtime was what happens when these adapters are in a different module. Currently we have one module which simply contains the .graphql[s] files and the apollo {} configuration in its gradle config. With that said however, I guess I could move the adapters in that module and itā€™d work. Hmm probably is a good idea.
m
Yea, they need to be available when compiling the models
s
Yeah maybe a bit weird to then have to add Adyen as a dependency in our apollo-only module in order to get the adapter baked in without having to provide it as a custom scalar adapter. Since weā€™re also serializing/deserializing an object coming from their SDK here. But at the same time, maybe this is fine, since we want apollo to deal with that type, itā€™s fine for the module to depend on that sdk just to bring in that type.
About that graphql-type-json spec, yeah I wasnā€™t even aware it exists, and itā€™s possible my backend team didnā€™t either and we are doing our own thing for this problem šŸ˜„
m
On the plus side, you save a HashMap lookup for each custom scalar field by "baking" them. We have never really benchmarked this but that could be an argument
šŸ‘Œ 2
j
Thankyou both! Awesome discussion to wake up to. Iā€™ve opted to go for your wrapped solution
class MyJsonObject(val fields: Map<String, Any?>)
for now and it works a treat. Glad I posted!
šŸŽ‰ 2
541 Views