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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With