This article is about fixing Cannot deserialize wrapper for simple types if their value property is public in FasterXML jackson-module-kotlin
  • 06-Feb-2023
Lightrun Team
Author Lightrun Team
Share
This article is about fixing Cannot deserialize wrapper for simple types if their value property is public in FasterXML jackson-module-kotlin

Cannot deserialize wrapper for simple types if their value property is public in FasterXML jackson-module-kotlin

Lightrun Team
Lightrun Team
06-Feb-2023

Explanation of the problem

When using single-property wrapper classes, such as BigDecimal or Double, to deserialize JSON values, the property must be declared private, otherwise an exception will be thrown. This issue occurs when using Jackson library with Kotlin.

The following code block demonstrates the issue:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import java.math.BigDecimal

data class DtoWithPublicProperty(
    val foo: WrapperWithPublicValue
)

data class WrapperWithPublicValue(val value: BigDecimal)

val objectMapper = jacksonObjectMapper()
val json = """{"foo":42.123}"""

objectMapper.readValue<DtoWithPublicProperty>(json)

This code will throw an exception like:

com.fasterxml.jackson.databind.exc.MismatchedInputException: 
Cannot construct instance of [..] WrapperWithPublicValue 
(although at least one Creator exists): 
no double/Double-argument constructor/factory method to deserialize 
from Number value (42.123)  
at [Source: (String)"{"foo":42.123}"; line: 1, column: 8]

Other details:

Troubleshooting with the Lightrun Developer Observability Platform

Getting a sense of what’s actually happening inside a live application is a frustrating experience, one that relies mostly on querying and observing whatever logs were written during development.
Lightrun is a Developer Observability Platform, allowing developers to add telemetry to live applications in real-time, on-demand, and right from the IDE.

  • Instantly add logs to, set metrics in, and take snapshots of live applications
  • Insights delivered straight to your IDE or CLI
  • Works where you do: dev, QA, staging, CI/CD, and production

Start for free today

Problem solution for Cannot deserialize wrapper for simple types if their value property is public in FasterXML jackson-module-kotlin

To solve this issue with FasterXML jackson-module-kotlin, you can follow these steps:

  1. Use a constructor or factory method to create the wrapper object instead of using the public value property.
  2. Annotate the value property with @JsonValue which will tell Jackson to use it as the single representation of the object during serialization and deserialization.
  3. If you cannot change the code of the class, you can write a custom serializer/deserializer for the class.

Example with @JsonValue:

data class Wrapper(val value: String) {
    @JsonValue
    fun getValue() = value
}

Example with custom serializer/deserializer:

class WrapperSerializer : JsonSerializer<Wrapper> {
    override fun serialize(wrapper: Wrapper, gen: JsonGenerator, serializers: SerializerProvider) {
        gen.writeString(wrapper.value)
    }
}

class WrapperDeserializer : JsonDeserializer<Wrapper> {
    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Wrapper {
        return Wrapper(p.text)
    }
}

object WrapperModule : SimpleModule() {
    init {
        addSerializer(Wrapper::class.java, WrapperSerializer())
        addDeserializer(Wrapper::class.java, WrapperDeserializer())
    }
}

And finally register the module with ObjectMapper:

val mapper = ObjectMapper().registerModule(WrapperModule)

Other popular problems with jackson-module-kotlin

Problem: Serializing Null Values

By default, FasterXML Jackson omits properties with null values during serialization. This can lead to unexpected behavior, especially when working with REST APIs.

Solution:

To force Jackson to include null values in serialized JSON, you can set the SerializationFeature.WRITE_NULL_MAP_VALUES feature to true on the ObjectMapper used for serialization.

val mapper = ObjectMapper().apply {
    enable(SerializationFeature.WRITE_NULL_MAP_VALUES)
}

Problem: Handling Polymorphic Types

When working with polymorphic types (i.e. classes that have multiple subclasses), you need to specify how Jackson should determine the correct type to use during serialization and deserialization.

Solution:

To handle polymorphic types with Jackson, you can use the @JsonTypeInfo and @JsonSubTypes annotations to specify the type information to include in the serialized JSON, and how to map the type information to the corresponding class.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
    JsonSubTypes.Type(value = Square::class, name = "square"),
    JsonSubTypes.Type(value = Circle::class, name = "circle")
)
sealed class Shape

data class Square(val sideLength: Double) : Shape()

data class Circle(val radius: Double) : Shape()

Problem: Handling Dates and Times

By default, FasterXML Jackson uses the default java.util.Date type to represent dates and times, which can lead to issues with time zones and format.

Solution:

To handle dates and times in a more robust manner, you can use the java.time package introduced in Java 8 and configure Jackson to use these classes for serialization and deserialization.

You can use the JSR310Module from the jackson-datatype-jsr310 module to register the Java 8 date and time classes with the ObjectMapper:

val mapper = ObjectMapper().apply {
    registerModule(JavaTimeModule())
}

And then, annotate the date and time properties in your class with the appropriate Jackson annotations, such as @JsonFormat to specify the format of the serialized string:

data class Event(
    @get:JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    val timestamp: LocalDateTime
)

A brief introduction to jackson-module-kotlin

FasterXML jackson-module-kotlin is a Java library for processing JSON data. It is a module for FasterXML Jackson, a widely used and high-performance JSON processing library. The jackson-module-kotlin module provides specific support for the Kotlin programming language, making it easy to serialize and deserialize Kotlin objects to and from JSON.

FasterXML jackson-module-kotlin provides a variety of features to make it easy to work with JSON data in a Kotlin environment. For example, it supports the serialization and deserialization of Nullable types, using the ? operator in Kotlin. It also provides support for the serialization and deserialization of data classes, making it easy to serialize and deserialize complex data structures. Additionally, the library provides support for custom serialization and deserialization logic, so you can define custom behavior for serializing and deserializing specific types or properties.

Most popular use cases for jackson-module-kotlin

  1. JSON Processing: FasterXML jackson-module-kotlin can be used to parse and generate JSON data. It provides a simple and efficient API for converting between JSON data and Kotlin objects.
val mapper = ObjectMapper()
val jsonString = """
    {
        "name": "John Doe",
        "age": 35
    }
"""
val user = mapper.readValue(jsonString, User::class.java)
  1. Data Binding: FasterXML jackson-module-kotlin can be used to bind JSON data to Kotlin objects and vice versa. The library provides support for a wide range of data types, including nullables, arrays, and complex data structures.
data class User(val name: String, val age: Int)

val mapper = ObjectMapper()
val user = User("John Doe", 35)
val json = mapper.writeValueAsString(user)
  1. Custom Serialization and Deserialization: FasterXML jackson-module-kotlin can be used to customize the serialization and deserialization process for specific types and properties. This allows you to define custom behavior for serializing and deserializing specific types or properties, for example, to handle custom date formats or to map JSON properties to different Kotlin properties.
data class User(
    @get:JsonProperty("full_name") val name: String,
    val age: Int
)
Share

It’s Really not that Complicated.

You can actually understand what’s going on inside your live applications.

Try Lightrun’s Playground

Lets Talk!

Looking for more information about Lightrun and debugging?
We’d love to hear from you!
Drop us a line and we’ll get back to you shortly.

By submitting this form, I agree to Lightrun’s Privacy Policy and Terms of Use.