英文:
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:
- 创建原始构建器的包装器:
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 {
}
- 如果您直接调用对象映射器,您可以尝试以下小技巧:
val builder = mapper.readerForUpdating(AttributeValue.builder())
val value = builder.readValue<AttributeValue.Builder>(jsonData).build()
英文:
Tricky indeed. I can think of two solutions:
- 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 {
}
- 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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论