In Kotlin, Jackson Deserialization error with data

2019-06-19 06:45发布

问题:

Not really sure how best to explain the problem so I whipped up the same code as both java and kotlin to better demonstration.

When I read JSON, it appears that it is forcing a data beans value to be NULL even though the parameter is not even part of the json to start with and the data bean defaults the value of the missing field. In java it works correctly never attempting to nullify the value that was never provided to start with. In Kotlin it seems to break because it tries to nullify a non-nullable field.

In Kotlin

data class Pojo(val name: String, val age: Int, val list: List<String> = emptyList(), val ts: Date = Date())

private val mapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule())
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)

fun main(args: Array<String>) {
    mapper.readValue("""{"name": "John Doe", "list": ["yellow", "green"], "age": 42}""", Pojo::class.java)
}

Which throws an exception of

java.lang.IllegalArgumentException: Parameter specified as non-null is null: method Pojo.<init>, parameter ts

In Java (everything works fine)

public class Transform {
  private static ObjectMapper mapper = new ObjectMapper()
        .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

  public static class Pojo {
    private String name;
    private int age;
    private List<String> list;
    private Date ts = new Date();
    <getters and setters for all>
  }

  public static void main(String[] args) throws IOException {
    String json = "{\"name\": \"John Doe\", \"list\": [\"yellow\", \"green\"], \"age\": 42}";
    Pojo p = mapper.readValue(json, Pojo.class);
    System.out.printf("Bean: name=%s, age=%s, list=%s, ts=%s\n", p.name, p.age, p.list, p.ts);
  }
}

Even if I make it a class instead of a data class in kotlin, it still errors out the same way.

My question is, how can I get the Jackson deserialization to work in Kotlin with my POJO's. The expected behavior is that it "should" break if a null/incorrect value is passed in for something where null is not allowed. But in the scenario above where no attempt at all was made to change the ts field to a null, it should have used the default value like it does with java.

The only thing that crosses my mind that seems to work is to not use the concept of the data bean at all and to write my beans like

class Pojo(val name: String, val age: Int) {
    var list: List<String> = emptyList()
    var ts: Date = Date()
}

But then my .equals does not work and it allows others downstream to manipulate the contents of the list and ts properties when I want them to be read-only.

回答1:

With the 2.8.0 release of jackson-kotlin-module it:

now supports using default values in constructor and creator methods

The following example depicts the feature:

data class Question(val title: String = "Is programming hard?", val answer: String)

val q = mapper.readValue<Question>("""{"answer": "Sure it can be"}""")
println(q) //-> Question(title=Is programming hard?, answer=Sure it can be)


回答2:

I think one of your incoming properties are null, so you going to need to place ? after jsonProperty that could be null(in you case this is your date field). You can also use raw String data type for your testing with triple quotes """\unescaped string""" so it should be like this val ts: Date? inside of your data class. And you don't have to redeclare the type. So final answer would be : data class Pojo(val name: String, val age: Int, val list: List<String>, val ts: Date?)