使用Jackson与不可变的AWS V2 DDB AttributeValue和私有Builder?

huangapple go评论73阅读模式
英文:

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 set up 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 an 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?

英文:

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?

答案1

得分: 3

Here are the translated parts:

  1. 创建原始构建器的包装器:
class BuilderDelegate {

    var field1 : String? = null
    var field2 : String? = null
    ...

    fun build() = AttributeValue.builder().also {
        it.field1 = field1
        it.field2 = field2
        ...
    }.build()
}

@JsonDeserialize(builder = BuilderDelegate::class)
interface AttributeValueMixin {
}
  1. 如果您直接调用对象映射器,您可以尝试以下小技巧:
val builder = mapper.readerForUpdating(AttributeValue.builder())
val value = builder.readValue<AttributeValue.Builder>(jsonData).build()
英文:

Tricky indeed. I can think of two solutions:

  1. Creating a wrapper around the original builder:
class BuilderDelegate {

    var field1 : String? = null
    var field2 : String? = null
    ...

    fun build() = AttributeValue.builder().also {
        it.field1 = field1
        it.field2 = field2
        ...
    }.build()
}

@JsonDeserialize(builder = BuilderDelegate::class)
interface AttributeValueMixin {
}
  1. If you are calling object mapper directly, you can try the following hack:
val builder = mapper.readerForUpdating(AttributeValue.builder())
val value = builder.readValue<AttributeValue.Builder>(jsonData).build()

答案2

得分: 1

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

For my case, I needed to serialize/deserialize 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
英文:

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

答案3

得分: -1

以下是要翻译的内容:

摘要

首先是一个辅助方法:

static ValueInstantiator createDefaultValueInstantiator(DeserializationConfig config, JavaType valueType, Supplier<?> creator) {
    class Instantiator extends StdValueInstantiator {
        public Instantiator(DeserializationConfig config, JavaType valueType) {
            super(config, valueType);
        }

        @Override
        public boolean canCreateUsingDefault() {
            return true;
        }

        @Override
        public Object createUsingDefault(DeserializationContext ctxt) {
            return creator.get();
        }
    }

    return new Instantiator(config, valueType);
}

然后为您的类添加ValueInstantiator

var mapper = new ObjectMapper();
var module = new SimpleModule()
        .addValueInstantiator(
                AttributeValue.Builder.class,
                createDefaultValueInstantiator(
                        mapper.getDeserializationConfig(),
                        mapper.getTypeFactory().constructType(AttributeValue.Builder.class),
                        AttributeValue::builder
                )
        );

mapper.registerModule(module);

现在,Jackson 将能够实例化一个 AttributeValue.Builder

英文:

See my answer for this question: https://stackoverflow.com/a/65603336/2288986

Summary

First a helper method:

static ValueInstantiator createDefaultValueInstantiator(DeserializationConfig config, JavaType valueType, Supplier<?> creator) {
    class Instantiator extends StdValueInstantiator {
        public Instantiator(DeserializationConfig config, JavaType valueType) {
            super(config, valueType);
        }

        @Override
        public boolean canCreateUsingDefault() {
            return true;
        }

        @Override
        public Object createUsingDefault(DeserializationContext ctxt) {
            return creator.get();
        }
    }

    return new Instantiator(config, valueType);
}

Then add the ValueInstantiator for your class:

var mapper = new ObjectMapper();
var module = new SimpleModule()
        .addValueInstantiator(
                AttributeValue.Builder.class,
                createDefaultValueInstantiator(
                        mapper.getDeserializationConfig(),
                        mapper.getTypeFactory().constructType(AttributeValue.Builder.class),
                        AttributeValue::builder
                )
        );

mapper.registerModule(module);

Now Jackson will be able to instantiate an AttributeValue.Builder.

huangapple
  • 本文由 发表于 2020年8月14日 01:28:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/63400344.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定