jackson-module-kotlin icon indicating copy to clipboard operation
jackson-module-kotlin copied to clipboard

Invalid JSON input: Cannot deserialize value of type

Open mehmetbebek opened this issue 5 years ago • 7 comments

I have a data class as following:

@Json
data class ControlTag(val tagSource: TagSource, val tag: TagType)

TagSource class:

@Json
enum class TagSource {

  EXISTING, USER
}

TagType is an interface:

interface TagType {
  fun getLabel(): String
  fun getValue(): String
}

I have several enum classes using this TagType interface:

enum class TagInformation : TagType {
  TAG_INFO_YES {
    override fun getLabel(): String {
      return "Tag Info - Yes"
    }

    override fun getValue(): String {
      return "t"
    }
  },
  TAG_INFO_NO {
    override fun getLabel(): String {
      return "Tag Info - No"
    }

    override fun getValue(): String {
      return "f"
    }
  };

  @JsonValue
  fun value(): String {
    return getLabel()
  }
}

Everything is ok at backend side, but when I call from ui it gives jackson error:

[
....
{tagSource: "EXISTING", tag: "Tag Info - Yes"}
{tagSource: "USER", tag: "X Enum Info - No"}
{tagSource: "EXISTING", tag: "Y Enum Info - Yes"}
....
]

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of ...control_tag.TagType (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: (PushbackInputStream); line: 1, column: 378] (through reference chain:

I have fixed above issue with adding below

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(value = TagInformation ::class, name = "TagInformation ")
)
interface TagType {

But now I have mapping issue:

{ tagSource: 'EXISTING', tag: { type: 'TagInformation', value: 'Tag - Yes' } }

Error is:

2020-04-24 18:10:50.147 WARN 26184 --- [nio-8093-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Invalid JSON input: Cannot deserialize value of type .control_tag.TagInformation from String "value": not one of the values accepted for Enum class: [Tag - Yes, Tag - No];

mehmetbebek avatar Apr 24 '20 17:04 mehmetbebek

Couple of things: from data class, this seems to be for language other than Java (Kotlin?), so may need to move.

But aside from that, what is needed is little bit code to show exactly how content is deserialized -- that is, show how ObjectMapper.readValue() (or ObjectReader.readValue()) is called. That detail is often important (esp. regarding target type used; sometimes configuration of mapper itself) in reproducing the issue.

I will move this to Kotlin repo, and add tag to indicate bit more info needed (unit test ideal, but simple reproduction code works as well).

cowtowncoder avatar Apr 24 '20 17:04 cowtowncoder

I have a spring boot app with kotlin, I am using vuejs for front end side. This error occurs when I can rest endpoint from ui.

Also following small code piece gives same error

fun main() {
  val json = "{\"value\":\"Tag Info - Yes\",  \"type\":\"TagInfo\"}"
  val result = ObjectMapper().readValue(json, TagType::class.java)
  
  println(result)

}

mehmetbebek avatar Apr 24 '20 17:04 mehmetbebek

@mgnfcnt thank you for the reproduction: that should be helpful in figuring this out. It may even be possible to distill this into Java-only test eventually.

One last (I hope :) ) question: which version of Jackson (esp jackson-module-kotlin) is this with? 2.10.3?

cowtowncoder avatar Apr 24 '20 17:04 cowtowncoder

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.10.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jdk8</artifactId>
      <version>2.10.2</version>
      <scope>compile</scope>
    </dependency>

Actually I didnt add it. It comes with default spring boot web dependency

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

mehmetbebek avatar Apr 24 '20 18:04 mehmetbebek

Ok. Close enough: I don't think there are many fixes between 2.10.2 and 2.10.3.

cowtowncoder avatar Apr 24 '20 18:04 cowtowncoder

@cowtowncoder I have upgraded spring version to

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
</parent>

My new versions are:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.10.3</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jdk8</artifactId>
  <version>2.10.3</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.10.3</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-parameter-names</artifactId>
  <version>2.10.3</version>
  <scope>compile</scope>
</dependency>

But still same error

mehmetbebek avatar Apr 24 '20 18:04 mehmetbebek

Update: Following code doesn't complain,

companion object {
  @JvmStatic
  @JsonCreator
  fun fromTag(tag: String): TagInfo? {
    return getEnum(tag)
  }

  fun getEnum(requestedValue: String?): TagInfo? {
    for (value in values()) {
      if (value.getLabel() == requestedValue)
        return value
    }

    return null
  }
}

But It passes the param as what I provided at key instead value

  val json = "{\"tag\":\"Tag - Yes\",  \"type\":\"TagInfo\"}"
  val result = ObjectMapper().readValue(json, TagType::class.java)

So because of there is no value as "tag" it returns null,

Following form is working as desired(for now) but it is not proper key/value for json.(Provided value as key) val json = "{\"Tag - Yes\":\"Tag - Yes\", \"type\":\"TagInfo\"}"

mehmetbebek avatar Apr 24 '20 20:04 mehmetbebek