Custom ScalarTypes using already defined JSON serializers

552 views
Skip to first unread message

Tal Gendler

unread,
Mar 8, 2017, 1:41:17 AM3/8/17
to sangria-graphql
Hi Oleg, 
First of all great project. I'm wondering if it's possible to use an already defined serializer/deserializer for JSON ?
Let's take for example this class:

case class Person(_id: ObjectId, name: String)

We need to define GraphQL output type for ObjectId which can be achieved with something like this:

case object ObjectIdCoercion extends ValueCoercionViolation("ObjectId value expected")
implicit val ObjectIdType = ScalarType[ObjectId]("ObjectId",
 coerceOutput
= (objId, _) => objId.toString,
 coerceUserInput
= {
 
case s: String => Right(new ObjectId(s))
 
case _ => Left(ObjectIdCoercion)
 
},
 coerceInput
= {
 
case StringValue(s, _, _) => Right(new ObjectId(s))
 
case _ => Left(ObjectIdCoercion)
 
})

val
PersonType = deriveObjectType[Unit, Person](
 
ObjectTypeDescription("Person description")
)


We need to do it for every custom type we have however we already did this for Json4s in our case, can they be reused ?

Oleg Ilyenko

unread,
Mar 8, 2017, 4:03:09 AM3/8/17
to sangria-graphql
Hi Tal,

Unfortunately, you can't really use json4s for output types, but they would be used for input types. I currently working on scalar aliases feature:


I think it can be quite helpful in your case as well. Would be great if you could check it out and maybe comment/review it.

Cheers,
Oleg

Tal Gendler

unread,
Mar 8, 2017, 5:24:50 AM3/8/17
to sangria-graphql
I looked at it and it's great, but it solves a different kind of problem. Another use case I didn't mention are different Enum implementations using Enumeratum or custom ones using a Trait and different kind case classes that extend it. All of which can eventually be seriallized/deseriallized to String/Int but they don't necessary encapsulate them. 

Tal Gendler

unread,
Mar 9, 2017, 2:01:27 AM3/9/17
to sangria-graphql
Update:
I think _Scalar Alias_ will actually do the trick for *Input*. However consider this, from what I saw you check if the alias(or any other type) can be marshaled used known marshaler. If that's the case is it possible to extend/rewrite `Json4sNativeResultMarshaller` and `Json4sNativeInputUnmarshaller` to handle every type known to Json4s ? You can look [here](https://github.com/json4s/json4s/blob/3.6/mongo/src/main/scala/org/json4s/mongo/JObjectParser.scala) to understand the idea. To sum it up - once you have proper de/serializers:
Given `Scala<->Json4sAST` and `Json4sAST<->MongoDBObject` you can do this `Scala<->MongoDBObject` 
I'm mentioned Json4s project but it' also true to any other framework.

Oleg Ilyenko

unread,
Mar 10, 2017, 4:58:39 PM3/10/17
to sangria-graphql
I think there are several things involved here:

JSON serializer/deserializer is just a function `JSON ⇒ DomainClass` and vice versa. It does not have any information about the structure of JSON object or a domain model. This greatly diminishes their usefulness in this context. In contrast to traditional RESTfil approach, GraphQL is statically typed which means that it needs to know the structure of your the domain model upfront. This means it is not possible to reuse existing infrastructure since it does not contain enough information to create input/output GraphQL types. The only place where deserializer can be useful is when you are defining an input object. input objects are always fully defined in GraphQL query (in contrast to output objects). This means that we can use it to deserialize JSON (variables) and/or GraphQL input value into a case class. Still, it is not enough, because as I mentioned it does not contain all information about the structure of this input value (which you cal also access via introspection API). This is why sangria requires you to define JSON deserializer (deserialization logic) and `InputObject[Person]` (structure) when you are defining a field `Argument` of type `Person`

>  If that's the case is it possible to extend/rewrite `Json4sNativeResultMarshaller` and `Json4sNativeInputUnmarshaller` to handle every type known to Json4s

This is already implemented for all JSON libraries that support it via `FromInput`/`ToInput` type classes (json4s does not support it though, so this feature is not available for it). Here is an example how they are defined for circe (which supports this feature via `Encoder`/`Decoder` type classes):


For any type `T` that has an `Encoder`/`Decoder` it provides `FromInput`/`ToInput`
Reply all
Reply to author
Forward
0 new messages