Skip to content

Add support for ScalarConverter from other types than String #816

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
stengvac opened this issue Jul 28, 2020 · 4 comments · Fixed by #819
Closed

Add support for ScalarConverter from other types than String #816

stengvac opened this issue Jul 28, 2020 · 4 comments · Fixed by #819
Labels
type: bug Something isn't working
Milestone

Comments

@stengvac
Copy link

stengvac commented Jul 28, 2020

Hello,

Using gradle plugin id("com.expediagroup.graphql") version "3.4.2" and implementation("com.expediagroup:graphql-kotlin-client:3.4.2"), kotlin 1.3.72, jackson 2.11.0

Our graphql schema contains types UUID, Long, BigDecimal. UUID, Long can be handled as is shown in example. But this approach does not work for me in case of BigDecimal (I don`t get why it works for Long).

Converters

import java.math.BigDecimal

class LongScalarConverter : ScalarConverter<Long> {
    override fun toScalar(rawValue: String): Long = rawValue.toLong()
    override fun toJson(value: Long): String = value.toString()
}

class BigDecimalScalarConverter : ScalarConverter<BigDecimal> {
    override fun toScalar(rawValue: String): BigDecimal = BigDecimal(rawValue)
    override fun toJson(value: BigDecimal): String = value.toString()
}

Generated classes for those registered converters

 /**
   * Long
   */
  data class Long(
    val value: kotlin.Long
  ) {
    @JsonValue
    fun rawValue() = converter.toJson(value)

    companion object {
      val converter: LongScalarConverter = LongScalarConverter()

      @JsonCreator
      @JvmStatic
      fun create(rawValue: String) = Long(converter.toScalar(rawValue))
    }
  }

  /**
   * BigDecimal
   */
  data class BigDecimal(
    val value: java.math.BigDecimal
  ) {
    @JsonValue
    fun rawValue() = converter.toJson(value)

    companion object {
      val converter: BigDecimalScalarConverter = BigDecimalScalarConverter()

      @JsonCreator
      @JvmStatic
      fun create(rawValue: String) = BigDecimal(converter.toScalar(rawValue))
    }
  }

fail with exception

 val objectMapper = jacksonObjectMapper()
    val inJson = """
        0
    """.trimIndent()
//works
    val longRes = objectMapper.readValue(inJson, CreateCardMutation.Long::class.java)
//works
    val javaBigDecimal = objectMapper.readValue(inJson, BigDecimal::class.java)
    // this one throw ex
    val bigDecimalRes = objectMapper.readValue(inJson, CreateCardMutation.BigDecimal::class.java)
Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `SomeMutation$BigDecimal` (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (0)
 at [Source: (String)"0"; line: 1, column: 1]
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1455)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1081)
	at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromInt(ValueInstantiator.java:262)
	at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromInt(StdValueInstantiator.java:356)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromNumber(BeanDeserializerBase.java:1359)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:178)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:166)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3434)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3402)
	at ApplicationKt.main(Application.kt:23)

Works fine with changed generated class to

data class BigDecimal(
        val value: java.math.BigDecimal
    ) {
        @JsonCreator
        constructor(rawValue: Int) : this(java.math.BigDecimal(rawValue))
    }

Any ideas why Long works and BigDecimal not?

Edit: Older jackson versions 2.9.10 throw different ex

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `SomeMutation$BigDecimal` (no Creators, like default constructor, exist): no int/Int-argument constructor/factory method to deserialize from Number value (0)
 at [Source: (String)"0"; line: 1, column: 1]
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1615)
	at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1077)
	at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromInt(ValueInstantiator.java:262)
	at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromInt(StdValueInstantiator.java:356)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromNumber(BeanDeserializerBase.java:1359)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:178)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:166)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3434)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3402)
	at ApplicationKt.main(Application.kt:26)

@stengvac stengvac added the type: bug Something isn't working label Jul 28, 2020
@dariuszkuc
Copy link
Collaborator

dariuszkuc commented Jul 29, 2020

Scalar converter signature accepts Strings as custom GraphQL scalars are serialized as Strings. I just run the example with BigDecimal and it seems to work fine

https://github.com/dariuszkuc/graphql-kotlin/tree/big_decimal

@stengvac
Copy link
Author

stengvac commented Jul 29, 2020

It works, because server does send BigDecimal as String in provided example.

Is there official GraphQL documentation, which state, than Custom Scalar MUST be serialized as String? We do use on server side other framework, which use jackson ObjectMapper for mapping responses and BigDecimal/Long is serialized as number which is I would say correct behaviour.

In this doc is send timestamp as number marked as custom scalar. I have not found better documentation source about custom scalars.

@dariuszkuc
Copy link
Collaborator

I was looking through the spec (https://spec.graphql.org/June2018/#sec-Scalars) and indeed it just specifies that scalars are representable as String but doesn't specify that they have to be serialized as String (as opposed to Number, etc). I'll update the ScalarConverter to use Any instead of a String.

@stengvac
Copy link
Author

stengvac commented Aug 3, 2020

Thx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug Something isn't working
Development

Successfully merging a pull request may close this issue.

3 participants