Vincent F
Vincent F

Reputation: 7331

Deserialize a nested json field with Jackson in Kotlin

I've already deserialized some nested field in the past in Java, following instructions from https://www.baeldung.com/jackson-nested-values (section 5) :

@JsonProperty("brand")
private void unpackNested(Map<String,Object> brand) {
    this.brandName = (String)brand.get("name");
    Map<String,String> owner = (Map<String,String>)brand.get("owner");
    this.ownerName = owner.get("name");
}

ownerName being a field in the bean.

Now, I need to do something similar in Kotlin, but I am not happy with what I have so far. Assuming I have a MyPojo class that has a createdAt field, but in the JSON that represents it, the field is nested under a metadata attribute:

data class MyPojo(var createdAt: LocalDateTime = LocalDateTime.MIN) {

    @JsonProperty("metadata")
    private fun unpackNested(metadata: Map<String, Any>) {

        var createdAtAsString = metadata["createdAt"] as String

        this.createdAt = LocalDateTime.parse(createdAtAsString,DateTimeFormatter.ISO_DATE_TIME)
    }
}

One of the thing I don't like here is that I am forced to make createdAt a var, not a val.

Is there a Kotlin trick to make things overall better here?

Upvotes: 0

Views: 3083

Answers (3)

eksd
eksd

Reputation: 63

You can try to do this:

data class MyPojo(
    var createdAt: LocalDateTime = LocalDateTime.MIN,
    @JsonProperty("metadata")
    private val metadata: Map<String, Any>
) {
    init {
        val createdAtAsString = metadata["createdAt"] as String
        createdAt = LocalDateTime.parse(createdAtAsString, DateTimeFormatter.ISO_DATE_TIME)
    }
}

or if you want createAt to be val, try this:

data class MyPojo(
    @JsonProperty("metadata")
    private val metadata: Map<String, Any>,
    val createdAt: LocalDateTime = run {
        val createdAtAsString = metadata["createdAt"] as String
        LocalDateTime.parse(createdAtAsString, DateTimeFormatter.ISO_DATE_TIME)
    }
)

Upvotes: 0

L. Stoller
L. Stoller

Reputation: 1

Pls check if this works for you

class JsonData() {
 var createdAt by Delegates.notNull<Int>()

 @JsonProperty("metadata")
 private fun unpackNested(metadata: Map<String, Any>) {
     createdAt =  metadata["createdAt"] as Int
 }
}

Upvotes: 0

Willi Mentzel
Willi Mentzel

Reputation: 29844

For the sake of simplicity, I used Int as type for createdAt.

You could do it like this:

class JsonData(createdAt: Int = 0) {

    private var _createdAt: Int = createdAt

    val createdAt: Int
        get() = _createdAt

    @JsonProperty("metadata")
    private fun unpackNested(metadata: Map<String, Any>) {
        _createdAt =  metadata["createdAt"] as Int
    }
}

createdAt will be a parameter with a default value. Since a data classe's constructor can only have properties (var/val) you will loose the advantages of a data class (toString() out of the box etc.).

You will assign this parameter to a private var _createdAt when the class is instantiated.

The only thing that will be exposed to the outside is a property without a backing field createAt (just a getter in Java terms). So, _createdAt cannot be changed after instantiation.

There are two cases now:

  1. If you instantiate the class, _createdAt will be set to the value you specify.
  2. If Jackson instantiates the class the value of _createdAt will be overwritten by the unpackNested call.

Here is an example:

val jsonStr = """{
    "metadata": {
        "createdAt": 1
    }
}
""".trimIndent()

fun main() {
    val objectMapper = ObjectMapper()

    // Jackson does instantiation
    val jsonData = objectMapper.readValue(jsonStr, JsonData::class.java)

    // you do it directly
    JsonData(5)
}

Upvotes: 2

Related Questions