DynamoDB JsonMarshaller 无法反序列化对象列表
DynamoDB JsonMarshaller cannot Deserialize List of Object
我有一个 Java class,它是 DynamoDB 中 table 的数据模型。我想使用 Dynamo 中的 DynamoDBMapper
到 save
和 load
项目。 class 的一名成员是 List<MyObject>
。所以我使用 JsonMarshaller<List<MyObject>>
来序列化和反序列化这个字段。
列表可以通过 JsonMarshaller
成功序列化。但是,当我尝试取回条目并读取列表时,它会抛出一个异常:java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to MyObject
。看起来 JsonMarshaller
将数据反序列化为 LinkedHashMap
而不是 MyObject
。我怎样才能摆脱这个问题?
MCVE:
// Model.java
@DynamoDBTable(tableName = "...")
public class Model {
private String id;
private List<MyObject> objects;
public Model(String id, List<MyObject> objects) {
this.id = id;
this.objects = objects;
}
@DynamoDBHashKey(attributeName = "id")
public String getId() { return this.id; }
public void setId(String id) { this.id = id; }
@DynamoDBMarshalling(marshallerClass = ObjectListMarshaller.class)
public List<MyObject> getObjects() { return this.objects; }
public void setObjects(List<MyObject> objects) { this.objects = objects; }
}
// MyObject.java
public class MyObject {
private String name;
private String property;
public MyObject() { }
public MyObject(String name, String property) {
this.name = name;
this.property = property;
}
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public String getProperty() { return this.property; }
public void setProperty(String property) { this.property = property; }
}
// ObjectListMarshaller.java
public class ObjectListMarshaller extends JsonMarshaller<List<MyObject>> {}
// Test.java
public class Test {
private static DynamoDBMapper mapper;
static {
AmazonDynamoDBClient client = new AmazonDynamoDBClient(new ProfileCredentialsProvider()
mapper = new DynamoDBMapper(client);
}
public static void main(String[] args) {
MyObject obj1 = new MyObject("name1", "property1");
MyObject obj2 = new MyObject("name2", "property2");
List<MyObject> objs = Arrays.asList(obj1, obj2);
Model model = new Model("id1", objs);
mapper.save(model); // success
Model retrieved = mapper.load(Model.class, "id1");
for (MyObject obj : retrieved.getObjects()) { // exception
}
}
}
这里的部分问题在于整个 DynamoDB Mapper SDK 如何处理泛型。 interface DynamoDBMarshaller<T extends Object>
有一个方法 T unmarshall(Class<T> clazz, String obj)
,其中要反序列化的 class 作为参数传递。问题是有 type erasure,SDK 没有提供简单的处理方法。 Jackson 在某些情况下更聪明(JsonMarshaller
使用 Jackson),这解释了为什么 serialize
方法可以正常工作。
您需要为反序列化提供更好的实现。您可以这样做的一种方法是实现 DynamoDBMarshaller
接口而不是扩展另一个接口(我的意见),这样您就可以更好地控制类型的序列化方式。
这里有一个示例,它本质上是 JsonMarshaller
的 copy/paste,对 List
的反序列化进行了细微的调整,以便您了解:
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMarshaller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.util.List;
import static com.amazonaws.util.Throwables.failure;
public class MyCustomMarshaller implements DynamoDBMarshaller<List<MyObject>> {
private static final ObjectMapper mapper = new ObjectMapper();
private static final ObjectWriter writer = mapper.writer();
@Override
public String marshall(List<MyObject> obj) {
try {
return writer.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw failure(e,
"Unable to marshall the instance of " + obj.getClass()
+ "into a string");
}
}
@Override
public List<MyObject> unmarshall(Class<List<MyObject>> clazz, String json) {
final CollectionType
type =
mapper.getTypeFactory().constructCollectionType(List.class, MyObject.class);
try {
return mapper.readValue(json, type);
} catch (Exception e) {
throw failure(e, "Unable to unmarshall the string " + json
+ "into " + clazz);
}
}
}
Interface DynamoDBMarshaller<T extends Object>
已弃用,替换为 Interface DynamoDBTypeConverter<S,T>
.
在您的模型中 class,将注释添加到您的对象列表中。
@DynamoDBTypeConverted(converter = PhoneNumberConverter.class)
public List<MyObject> getObjects() { return this.objects; }
public void setObjects(列表对象){ this.objects = 对象; }
这是DynamoDBTypeConverter
的实现。
public class PhoneNumberConverter implements DynamoDBTypeConverter<String, PhoneNumber>
{
private static final ObjectMapper mapper = new ObjectMapper();
private static final ObjectWriter writer = mapper.writerWithType(new TypeReference<List<MyObject>>(){});
@Override
public String convert(List<MyObject> obj) {
try {
return writer.writeValueAsString(obj);
} catch (JsonProcessingException e) {
System.out.println(
"Unable to marshall the instance of " + obj.getClass()
+ "into a string");
return null;
}
}
@Override
public List<MyObject> unconvert(String s) {
TypeReference<List<MyObject>> type = new TypeReference<List<MyObject>>() {};
try {
List<MyObject> list = mapper.readValue(s, type);
return list;
} catch (Exception e) {
System.out.println("Unable to unmarshall the string " + s
+ "into " + s);
return null;
}
}
}
在较新的版本中仅适用于:
@DynamoDBAttribute(attributeName = "things")
public List<Thing> getThings() {
return things;
}
public void setThings(final List<Thing> things) {
this.things = things;
}
假设 Thing 被注释为:
@DynamoDBDocument
public class Thing {
}
DynamoDBMarshaller 现已弃用,但我在使用 DynamoDBTypeConvertedJson 时遇到了完全相同的问题。如果您想在 DynamoDBMapper class 中将集合存储为 JSON,请使用 DynamoDBTypeConverted 并编写自定义转换器 class(不要使用 DynamoDBTypeConvertedJson,它不会 return 您的集合取消转换)。
这是使用 DynamoDBTypeConverted 的解决方案
// Model.java
@DynamoDBTable(tableName = "...")
public class Model {
private String id;
private List<MyObject> objects;
public Model(String id, List<MyObject> objects) {
this.id = id;
this.objects = objects;
}
@DynamoDBHashKey(attributeName = "id")
public String getId() { return this.id; }
public void setId(String id) { this.id = id; }
@DynamoDBTypeConverted(converter = MyObjectConverter.class)
public List<MyObject> getObjects() { return this.objects; }
public void setObjects(List<MyObject> objects) { this.objects = objects; }
}
-
public class MyObjectConverter implements DynamoDBTypeConverter<String, List<MyObject>> {
@Override
public String convert(List<Object> objects) {
//Jackson object mapper
ObjectMapper objectMapper = new ObjectMapper();
try {
String objectsString = objectMapper.writeValueAsString(objects);
return objectsString;
} catch (JsonProcessingException e) {
//do something
}
return null;
}
@Override
public List<Object> unconvert(String objectssString) {
ObjectMapper objectMapper = new ObjectMapper();
try {
List<Object> objects = objectMapper.readValue(objectsString, new TypeReference<List<Object>>(){});
return objects;
} catch (JsonParseException e) {
//do something
} catch (JsonMappingException e) {
//do something
} catch (IOException e) {
//do something
}
return null;
}
}
我发现 Aleris 的响应很好。在我的例子中,我有一个发电机数据库 table 包含两个集合,都是非原始 classes.
在尝试了各种类型的 DBTypeConverters(采用 {String, MyObject}、{Collection, Collection}、{String, Collection})并尝试了 Set 而不是 Collection 之后,只需注释所引用的 class作为 DynamoDBDocument,我可以为那些子 classes 传递一个 json 数据数组,并且数据被正确保存。
我的 "parent class" 看起来像这样(更改名称以保护无辜者);
@DynamoDBTable(tableName = "SomeTable")
public class ParentClass {
@NotNull(message = "Key must be specified")
@Size(min = 12, max = 20)
@DynamoDBHashKey
private String key;
private String type;
@NotNull(message = "name must be specified.")
private String name;
@NotNull(message = "Type code must be specified")
@DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.S)
private TypeCode typeCode;
private List<ImageRef> images;
/**
* Optional physical dimensions
*/
private Dimensions productDimensions;
/**
* Optional presentations.
*/
private Set<Presentation> presentations;
}
TypeCode 是一个枚举。
ImageRef、Presentation 和 Dimensions classes 都带有 DynamoDBDocument 注释。
今天刚遇到同样的问题并解决了它。它可以通过以下方式修复。这些可能还有很多其他的。
方法 1: 使用 @DynamoDBTypeConvertedJson。它将整个 List<Object>
存储为单个字符串并将其检索为 List<Object>
。将此注释添加到您的字段 DynamoDB 将处理转换。
@DynamoDBAttribute(attributeName = "userDetails")
@DynamoDBTypeConvertedJson
public List<UserDetail> getUserDetails() {
return userDetails;
}
方法 2: 使用自定义转换器。这将 List<Object>
作为 List<String>
存储在数据库中,并将它们检索为 List<Object>
@DynamoDBAttribute(attributeName = "userDetails")
@DynamoDBTypeConverted(converter = UserDetailConverter.class)
public List<UserDetail> getUserDetails() {
return userDetails;
}
public class UserDetailConverter implements DynamoDBTypeConverter<List<String>, List<UserDetail>> {
private static final ObjectMapper mapper = new ObjectMapper();
static {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Override
public List<String> convert(List<UserDetail> object) {
List<String> details = new ArrayList<>();
for (UserDetail string : object) {
try {
details.add(mapper.writeValueAsString(string));
} catch (IOException e) {
throw new UncheckedIOException("Unable to serialize object", e);
}
}
return details;
}
@Override
public List<UserDetail> unconvert(List<String> object) {
List<UserDetail> details = new ArrayList<>();
for (String string : object) {
UserDetail detail;
try {
detail = mapper.readValue(string, UserDetail.class);
details.add(detail);
} catch (IOException e) {
throw new UncheckedIOException("Unable to serialize object", e);
}
}
return details;
}
希望对大家有所帮助。
我有一个 Java class,它是 DynamoDB 中 table 的数据模型。我想使用 Dynamo 中的 DynamoDBMapper
到 save
和 load
项目。 class 的一名成员是 List<MyObject>
。所以我使用 JsonMarshaller<List<MyObject>>
来序列化和反序列化这个字段。
列表可以通过 JsonMarshaller
成功序列化。但是,当我尝试取回条目并读取列表时,它会抛出一个异常:java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to MyObject
。看起来 JsonMarshaller
将数据反序列化为 LinkedHashMap
而不是 MyObject
。我怎样才能摆脱这个问题?
MCVE:
// Model.java
@DynamoDBTable(tableName = "...")
public class Model {
private String id;
private List<MyObject> objects;
public Model(String id, List<MyObject> objects) {
this.id = id;
this.objects = objects;
}
@DynamoDBHashKey(attributeName = "id")
public String getId() { return this.id; }
public void setId(String id) { this.id = id; }
@DynamoDBMarshalling(marshallerClass = ObjectListMarshaller.class)
public List<MyObject> getObjects() { return this.objects; }
public void setObjects(List<MyObject> objects) { this.objects = objects; }
}
// MyObject.java
public class MyObject {
private String name;
private String property;
public MyObject() { }
public MyObject(String name, String property) {
this.name = name;
this.property = property;
}
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public String getProperty() { return this.property; }
public void setProperty(String property) { this.property = property; }
}
// ObjectListMarshaller.java
public class ObjectListMarshaller extends JsonMarshaller<List<MyObject>> {}
// Test.java
public class Test {
private static DynamoDBMapper mapper;
static {
AmazonDynamoDBClient client = new AmazonDynamoDBClient(new ProfileCredentialsProvider()
mapper = new DynamoDBMapper(client);
}
public static void main(String[] args) {
MyObject obj1 = new MyObject("name1", "property1");
MyObject obj2 = new MyObject("name2", "property2");
List<MyObject> objs = Arrays.asList(obj1, obj2);
Model model = new Model("id1", objs);
mapper.save(model); // success
Model retrieved = mapper.load(Model.class, "id1");
for (MyObject obj : retrieved.getObjects()) { // exception
}
}
}
这里的部分问题在于整个 DynamoDB Mapper SDK 如何处理泛型。 interface DynamoDBMarshaller<T extends Object>
有一个方法 T unmarshall(Class<T> clazz, String obj)
,其中要反序列化的 class 作为参数传递。问题是有 type erasure,SDK 没有提供简单的处理方法。 Jackson 在某些情况下更聪明(JsonMarshaller
使用 Jackson),这解释了为什么 serialize
方法可以正常工作。
您需要为反序列化提供更好的实现。您可以这样做的一种方法是实现 DynamoDBMarshaller
接口而不是扩展另一个接口(我的意见),这样您就可以更好地控制类型的序列化方式。
这里有一个示例,它本质上是 JsonMarshaller
的 copy/paste,对 List
的反序列化进行了细微的调整,以便您了解:
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMarshaller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.util.List;
import static com.amazonaws.util.Throwables.failure;
public class MyCustomMarshaller implements DynamoDBMarshaller<List<MyObject>> {
private static final ObjectMapper mapper = new ObjectMapper();
private static final ObjectWriter writer = mapper.writer();
@Override
public String marshall(List<MyObject> obj) {
try {
return writer.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw failure(e,
"Unable to marshall the instance of " + obj.getClass()
+ "into a string");
}
}
@Override
public List<MyObject> unmarshall(Class<List<MyObject>> clazz, String json) {
final CollectionType
type =
mapper.getTypeFactory().constructCollectionType(List.class, MyObject.class);
try {
return mapper.readValue(json, type);
} catch (Exception e) {
throw failure(e, "Unable to unmarshall the string " + json
+ "into " + clazz);
}
}
}
Interface DynamoDBMarshaller<T extends Object>
已弃用,替换为 Interface DynamoDBTypeConverter<S,T>
.
在您的模型中 class,将注释添加到您的对象列表中。
@DynamoDBTypeConverted(converter = PhoneNumberConverter.class)
public List<MyObject> getObjects() { return this.objects; }
public void setObjects(列表对象){ this.objects = 对象; }
这是DynamoDBTypeConverter
的实现。
public class PhoneNumberConverter implements DynamoDBTypeConverter<String, PhoneNumber>
{
private static final ObjectMapper mapper = new ObjectMapper();
private static final ObjectWriter writer = mapper.writerWithType(new TypeReference<List<MyObject>>(){});
@Override
public String convert(List<MyObject> obj) {
try {
return writer.writeValueAsString(obj);
} catch (JsonProcessingException e) {
System.out.println(
"Unable to marshall the instance of " + obj.getClass()
+ "into a string");
return null;
}
}
@Override
public List<MyObject> unconvert(String s) {
TypeReference<List<MyObject>> type = new TypeReference<List<MyObject>>() {};
try {
List<MyObject> list = mapper.readValue(s, type);
return list;
} catch (Exception e) {
System.out.println("Unable to unmarshall the string " + s
+ "into " + s);
return null;
}
}
}
在较新的版本中仅适用于:
@DynamoDBAttribute(attributeName = "things")
public List<Thing> getThings() {
return things;
}
public void setThings(final List<Thing> things) {
this.things = things;
}
假设 Thing 被注释为:
@DynamoDBDocument
public class Thing {
}
DynamoDBMarshaller 现已弃用,但我在使用 DynamoDBTypeConvertedJson 时遇到了完全相同的问题。如果您想在 DynamoDBMapper class 中将集合存储为 JSON,请使用 DynamoDBTypeConverted 并编写自定义转换器 class(不要使用 DynamoDBTypeConvertedJson,它不会 return 您的集合取消转换)。
这是使用 DynamoDBTypeConverted 的解决方案
// Model.java
@DynamoDBTable(tableName = "...")
public class Model {
private String id;
private List<MyObject> objects;
public Model(String id, List<MyObject> objects) {
this.id = id;
this.objects = objects;
}
@DynamoDBHashKey(attributeName = "id")
public String getId() { return this.id; }
public void setId(String id) { this.id = id; }
@DynamoDBTypeConverted(converter = MyObjectConverter.class)
public List<MyObject> getObjects() { return this.objects; }
public void setObjects(List<MyObject> objects) { this.objects = objects; }
}
-
public class MyObjectConverter implements DynamoDBTypeConverter<String, List<MyObject>> {
@Override
public String convert(List<Object> objects) {
//Jackson object mapper
ObjectMapper objectMapper = new ObjectMapper();
try {
String objectsString = objectMapper.writeValueAsString(objects);
return objectsString;
} catch (JsonProcessingException e) {
//do something
}
return null;
}
@Override
public List<Object> unconvert(String objectssString) {
ObjectMapper objectMapper = new ObjectMapper();
try {
List<Object> objects = objectMapper.readValue(objectsString, new TypeReference<List<Object>>(){});
return objects;
} catch (JsonParseException e) {
//do something
} catch (JsonMappingException e) {
//do something
} catch (IOException e) {
//do something
}
return null;
}
}
我发现 Aleris 的响应很好。在我的例子中,我有一个发电机数据库 table 包含两个集合,都是非原始 classes.
在尝试了各种类型的 DBTypeConverters(采用 {String, MyObject}、{Collection, Collection}、{String, Collection})并尝试了 Set 而不是 Collection 之后,只需注释所引用的 class作为 DynamoDBDocument,我可以为那些子 classes 传递一个 json 数据数组,并且数据被正确保存。
我的 "parent class" 看起来像这样(更改名称以保护无辜者);
@DynamoDBTable(tableName = "SomeTable")
public class ParentClass {
@NotNull(message = "Key must be specified")
@Size(min = 12, max = 20)
@DynamoDBHashKey
private String key;
private String type;
@NotNull(message = "name must be specified.")
private String name;
@NotNull(message = "Type code must be specified")
@DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.S)
private TypeCode typeCode;
private List<ImageRef> images;
/**
* Optional physical dimensions
*/
private Dimensions productDimensions;
/**
* Optional presentations.
*/
private Set<Presentation> presentations;
}
TypeCode 是一个枚举。 ImageRef、Presentation 和 Dimensions classes 都带有 DynamoDBDocument 注释。
今天刚遇到同样的问题并解决了它。它可以通过以下方式修复。这些可能还有很多其他的。
方法 1: 使用 @DynamoDBTypeConvertedJson。它将整个 List<Object>
存储为单个字符串并将其检索为 List<Object>
。将此注释添加到您的字段 DynamoDB 将处理转换。
@DynamoDBAttribute(attributeName = "userDetails")
@DynamoDBTypeConvertedJson
public List<UserDetail> getUserDetails() {
return userDetails;
}
方法 2: 使用自定义转换器。这将 List<Object>
作为 List<String>
存储在数据库中,并将它们检索为 List<Object>
@DynamoDBAttribute(attributeName = "userDetails")
@DynamoDBTypeConverted(converter = UserDetailConverter.class)
public List<UserDetail> getUserDetails() {
return userDetails;
}
public class UserDetailConverter implements DynamoDBTypeConverter<List<String>, List<UserDetail>> {
private static final ObjectMapper mapper = new ObjectMapper();
static {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Override
public List<String> convert(List<UserDetail> object) {
List<String> details = new ArrayList<>();
for (UserDetail string : object) {
try {
details.add(mapper.writeValueAsString(string));
} catch (IOException e) {
throw new UncheckedIOException("Unable to serialize object", e);
}
}
return details;
}
@Override
public List<UserDetail> unconvert(List<String> object) {
List<UserDetail> details = new ArrayList<>();
for (String string : object) {
UserDetail detail;
try {
detail = mapper.readValue(string, UserDetail.class);
details.add(detail);
} catch (IOException e) {
throw new UncheckedIOException("Unable to serialize object", e);
}
}
return details;
}
希望对大家有所帮助。