Search code examples
javajsonkotlinjacksonamazon-dynamodb

Using Jackson with immutable AWS V2 DDB AttributeValue with private Builder?


I'm trying to serialize/deserialize the DynamoDB V2 AttributeValue class using Jackson.

It is setup as an immutable class with a Builder and the builder has a private constructor. In order to create a builder, you need to call AttributeValue.builder().

I have no control over this class, so I want to use Jackson mixins.

I've used the @JsonDeserialize(builder = AttributeValue.Builder::class) and registered the mixin:

@JsonDeserialize(builder = AttributeValue.Builder::class)
interface AttributeValueMixin {
}

private val mapper = jacksonObjectMapper()
    .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
    .addMixIn(AttributeValue::class.java, AttributeValueMixin::class.java)

However, Jackson is trying to use the default constructor of the AttributeValue.Builder and it can't since it doesn't have one.

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of software.amazon.awssdk.services.dynamodb.model.AttributeValue$Builder (no Creators, like default construct, exist)

How can I get Jackson to use the AttributeValue.builder() factory function? Or any other ideas on how to use Jackson to serialize/deserialize this AttributeValue class?


Solution

  • So this feels totally jankey, but it works, so... ¯\(ツ)

    For my case, I needed to seralize/deseralize a Map<String, AttributeValue>, so I used a technique of stuffing the JSON version of the map into a "map" attribute, deserializing, and then extracting the "map" value:

    import com.fasterxml.jackson.databind.ObjectMapper
    
    // the Jackson mapper
    val mapper = ObjectMapper()
        // I don't remember if this was needed, but I assume so...
        .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
        // This is probably just to make the JSON smaller
        .setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
    
    // to JSON
    val dynamoAttributes: Map<String, AttributeValue> = ... 
    val attributesAsJson = mapper.writeValueAsString(dynamoAttributes)
    
    // read back from JSON
    val result: Map<String, AttributeValue> =
        mapper.readValue(
            """{"m":$attributesAsJson}""", // stuff the JSON into a map field
            AttributeValue.serializableBuilderClass())
        .build()
        .m() // extract the "strongly" typed map