jsonkotlinjacksonkotlin-delegate

(de)serializing kotlin delegate properties with jackson


How can I (de)serialize kotlin delegate properties with jackson. I have a class like this

class MyClass {
    var a: Int = 42
        set(value) {
            val changed = field != value
            field = value
            if (changed) notifyListeners()
        }

    ... and a dozen other properties that all follow this pattern ...
}

I wanted to simplify that by using

class MyClass {
    var a: Int by NotifyUiOnChange(42)

    ...

    private inner class NotifyUiOnChange<T>(initialValue: T) : ObservableProperty<T>(initialValue) {
        override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
            notifyUiListeners()
        }
    }
}

but then Jackson will ignore that property.
How can I tell Jackson to serialize and deserialize that property anyway? And how do I then apply @JsonIgnore annotations (or something comparable)?


Solution

  • You must use outdated version on Jackson (or maybe a version for Java, not Kotlin?). I've checked this using "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.+" (resolved to 2.10.1).

    I've declared two classes:

    class MyClass {
        var a: Int = 42
            set(value) {
                val changed = field != value
                field = value
                if (changed) notifyListener(field)
            }
    
        private fun notifyListener(field: Any?) {
            println("changed: $field")
        }
    }
    
    class MyDelegatedClass {
        var a: Int by NotifyUi(42)
    
        private inner class NotifyUi<T>(initialValue: T) : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
                notifyListener(newValue)
            }
        }
    
        private fun notifyListener(field: Any?) {
            println("changed: $field")
        }
    }
    
    

    My main function:

    fun main() {
        val noDelegate = MyClass()
        val delegated = MyDelegatedClass()
    
        val mapper = ObjectMapper().registerKotlinModule()
    
        // Deserialization 
        val noDelegateValue = mapper.writeValueAsString(noDelegate)
        val delegatedValue = mapper.writeValueAsString(delegated)
    
        println("No delegate:\t$noDelegateValue")
        println("With delegate\t$delegatedValue")
    
        // Serialization
        val noDelegateObject = mapper.readValue<MyClass>("{\"a\":42}".trimIndent())
        val delegateObject = mapper.readValue<MyDelegatedClass>("{\"a\":42}".trimIndent())
    
    }
    

    Output:

    No delegate:    {"a":42}
    With delegate   {"a":42}
    changed: 42
    

    We even can see output on delegate when we use delegate property :) (I believe it's a side-effect that should be consider as bug actually)

    So, handling delegates is out of the box feature in jackson (I am not sure since when, but I used lazy delegate with jackson in older project I used to participate and there was no problems with delegates).

    How to ignore delegated property?

    So, you cannot apply JsonIgnore annotation to delegated field, because you will get This annotation is not applicable to target 'member property with delegate'. But, you can define the scope that annotation should be applied. Example below:

    class MyDelegateClass {
        @get:JsonIgnore // or set:
        val a: Int by NotifyUi(42)
    }
    

    Unfortunately, seems that it's kind of broken, because you can use get: or set: and it's not apply to getter or setter only, but for both.