Open hmolinari-attentive opened 2 years ago
Jackson needs to be told explicitly what concrete type to deserialize to for interfaces and abstract types such as PageInfo
and ConnectionCursor
. This can be done by registering a custom module and calling addAbstractTypeMapping
. Jackson would still have trouble deserializing to DefaultPageInfo
, so you would need a ValueInstantiator
or Deserializer
to handle that.
import com.fasterxml.jackson.databind.DeserializationConfig
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.PropertyMetadata
import com.fasterxml.jackson.databind.PropertyName
import com.fasterxml.jackson.databind.deser.CreatorProperty
import com.fasterxml.jackson.databind.deser.SettableBeanProperty
import com.fasterxml.jackson.databind.deser.ValueInstantiator
import graphql.relay.ConnectionCursor
import graphql.relay.DefaultPageInfo
class DefaultPageInfoValueInstantiator : ValueInstantiator.Base(DefaultPageInfo::class.java) {
override fun canCreateFromObjectWith(): Boolean {
return true
}
override fun getFromObjectArguments(config: DeserializationConfig): Array<SettableBeanProperty> {
val connectionCursorType = config.constructType(ConnectionCursor::class.java)
val booleanType = config.constructType(Boolean::class.java)
return arrayOf(
creatorProp("startCursor", connectionCursorType, 0),
creatorProp("endCursor", connectionCursorType, 1),
creatorProp("hasPrevious", booleanType, 2),
creatorProp("hasNext", booleanType, 3)
)
}
override fun createFromObjectWith(ctxt: DeserializationContext, args: Array<out Any>): Any {
return DefaultPageInfo(args[0] as ConnectionCursor, args[1] as ConnectionCursor, args[2] as Boolean, args[3] as Boolean)
}
private fun creatorProp(name: String, type: JavaType, index: Int): CreatorProperty {
return CreatorProperty.construct(PropertyName.construct(name), type, null, null, null, null, index, null, PropertyMetadata.STD_OPTIONAL)
}
}
And the Jackson module would look like this:
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
import graphql.relay.ConnectionCursor
import graphql.relay.DefaultConnectionCursor
import graphql.relay.DefaultPageInfo
import graphql.relay.PageInfo
class GraphQLModule : SimpleModule() {
init {
addAbstractTypeMapping(PageInfo::class.java, DefaultPageInfo::class.java)
addAbstractTypeMapping(ConnectionCursor::class.java, DefaultConnectionCursor::class.java)
addSerializer(DefaultConnectionCursor::class.java, ToStringSerializer.instance)
addValueInstantiator(DefaultPageInfo::class.java, DefaultPageInfoValueInstantiator())
}
}
Example usage:
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import graphql.relay.PageInfo
data class Response(val pageInfo: PageInfo)
val objectMapper = jacksonObjectMapper()
.registerModule(GraphQLModule())
val response = objectMapper.readValue<Response>("""{"pageInfo": {"endCursor": "bar", "hasPrevious": true, "hasNext": true}}""")
println(response)
// prints Response(pageInfo=DefaultPageInfo{ startCursor=null, endCursor=bar, hasPreviousPage=true, hasNextPage=true})
println("json = " + objectMapper.writeValueAsString(response))
// print json = {"pageInfo":{"startCursor":null,"endCursor":"bar","hasPreviousPage":true,"hasNextPage":true}}
I have actually run into the same issue, particularly when writing tests for server-side responses. Now whether this is something that belongs in dgs-codegen (or elsewhere?), I am not sure. @berngp thoughts?
Thanks @kilink. I think it makes sense to handle this in dgs-codegen since it is a well known and supported type.
On Apr 14, 2022, at 11:12 AM, Patrick Strawderman @.***> wrote:
Jackson needs to be told explicitly what concrete type to deserialize to for interfaces and abstract types such as PageInfo and ConnectionCursor. This can be done by registering a custom module and calling addAbstractTypeMapping. Jackson would still have trouble deserializing to DefaultPageInfo, so you would need a ValueInstantiator or Deserializer to handle that.
import com.fasterxml.jackson.databind.DeserializationConfig import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.PropertyMetadata import com.fasterxml.jackson.databind.PropertyName import com.fasterxml.jackson.databind.deser.CreatorProperty import com.fasterxml.jackson.databind.deser.SettableBeanProperty import com.fasterxml.jackson.databind.deser.ValueInstantiator import graphql.relay.ConnectionCursor import graphql.relay.DefaultPageInfo
class DefaultPageInfoValueInstantiator : ValueInstantiator.Base(DefaultPageInfo::class.java) { override fun canCreateFromObjectWith(): Boolean { return true }
override fun getFromObjectArguments(config: DeserializationConfig): Array<SettableBeanProperty> { val connectionCursorType = config.constructType(ConnectionCursor::class.java) val booleanType = config.constructType(Boolean::class.java) return arrayOf( creatorProp("startCursor", connectionCursorType, 0), creatorProp("endCursor", connectionCursorType, 1), creatorProp("hasPrevious", booleanType, 2), creatorProp("hasNext", booleanType, 3) ) } override fun createFromObjectWith(ctxt: DeserializationContext, args: Array<out Any>): Any { return DefaultPageInfo(args[0] as ConnectionCursor, args[1] as ConnectionCursor, args[2] as Boolean, args[3] as Boolean) } private fun creatorProp(name: String, type: JavaType, index: Int): CreatorProperty { return CreatorProperty.construct(PropertyName.construct(name), type, null, null, null, null, index, null, PropertyMetadata.STD_OPTIONAL) }
} And the Jackson module would look like this:
import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.ser.std.ToStringSerializer import graphql.relay.ConnectionCursor import graphql.relay.DefaultConnectionCursor import graphql.relay.DefaultPageInfo import graphql.relay.PageInfo
class GraphQLModule : SimpleModule() { init { addAbstractTypeMapping(PageInfo::class.java, DefaultPageInfo::class.java) addAbstractTypeMapping(ConnectionCursor::class.java, DefaultConnectionCursor::class.java)
addSerializer(DefaultConnectionCursor::class.java, ToStringSerializer.instance) addValueInstantiator(DefaultPageInfo::class.java, DefaultPageInfoValueInstantiator()) }
} Example usage:
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import graphql.relay.PageInfo
data class Response(val pageInfo: PageInfo)
val objectMapper = jacksonObjectMapper() .registerModule(GraphQLModule())
val response = objectMapper.readValue
("""{"pageInfo": {"endCursor": "bar", "hasPrevious": true, "hasNext": true}}""") println(response) // prints Response(pageInfo=DefaultPageInfo{ startCursor=null, endCursor=bar, hasPreviousPage=true, hasNextPage=true}) println("json = " + objectMapper.writeValueAsString(response)) // print json = {"pageInfo":{"startCursor":null,"endCursor":"bar","hasPreviousPage":true,"hasNextPage":true}} I have actually run into the same issue, particularly when writing tests for server-side responses. Now whether this is something that belongs in dgs-codegen (or elsewhere?), I am not sure. @berngp thoughts? — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you are subscribed to this thread.
Using the schema found here (also attached as txt) schema.txt I've generated a query API.
I make the following query (generated by the query API):
I get this response back:
Then when I attempt to parse the
site
object into a type generated by the codegen plugin...I get this
This is my codegen configuration, via the community maven port
and these are the versions of the dgs libraries I'm using