DynamoDB JsonMarshaller无法反序列化对象列表

时间:2015-06-12 00:19:12

标签: java jackson amazon-dynamodb

我有一个Java类,它是DynamoDB中表的数据模型。我想使用Dynamo中的DynamoDBMappersaveload项。该班级的一名成员是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
    }
  }
}

6 个答案:

答案 0 :(得分:8)

在较新的版本中,只需使用:

@DynamoDBAttribute(attributeName = "things")
public List<Thing> getThings() {
    return things;
}

public void setThings(final List<Thing> things) {
    this.things = things;
}

鉴于Thing的注释:

@DynamoDBDocument
public class Thing {
}

答案 1 :(得分:7)

这里的部分问题是整个DynamoDB Mapper SDK如何处理泛型。 interface DynamoDBMarshaller<T extends Object>有一个方法T unmarshall(Class<T> clazz, String obj),其中反序列化的类作为参数传递。问题是存在type erasure,并且SDK无法轻松解决此问题。杰克逊在某些情况下更聪明(JsonMarshaller使用杰克逊),这解释了为什么serialize方法正常工作。

您需要为反序列化提供更好的实现。你可以这样做的一种方法是实现DynamoDBMarshaller接口,而不是扩展另一个接口(我的意见),这样你就可以更好地控制类型的序列化方式。

以下示例基本上是JsonMarshaller的复制/粘贴,在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);
        }
    }
}

答案 2 :(得分:2)

DynamoDBMarshaller现已弃用,但我遇到了与DynamoDBTypeConvertedJson完全相同的问题。如果要在DynamoDBMapper类中将集合存储为JSON,请使用DynamoDBTypeConverted并编写自定义转换器类(不要使用不会在非转换时返回集合的DynamoDBTypeConvertedJson)。

以下是使用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;
    }
}

答案 3 :(得分:1)

今天刚遇到同样的问题并修复它。它可以通过以下方式修复。这些可能还有很多其他的。

方法 1: 使用 @DynamoDBTypeConvertedJson。它将整个 List<Object> 存储为单个 String 并将其检索为 List<Object>。将此注释添加到您的字段中,DynamoDB 将处理转换。

@DynamoDBAttribute(attributeName = "userDetails")
@DynamoDBTypeConvertedJson
public List<UserDetail> getUserDetails() {
    return userDetails;
}

方法 2: 使用自定义转换器。这将 List<Object> 作为 List<String> 存储在 DB 中并将它们检索为 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;
}

希望对某人有所帮助。

答案 4 :(得分:0)

Interface DynamoDBMarshaller<T extends Object>已弃用,替换为Interface DynamoDBTypeConverter<S,T>

在模型类中,将注释添加到对象列表中。

@DynamoDBTypeConverted(converter = PhoneNumberConverter.class)
   public List<MyObject> getObjects() { return this.objects; }

public void setObjects(List objects){this.objects = objects; }

这是DynamoDBTypeConverter的实现。

public class PhoneNumberConverterimplements 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;
        }
    }  
}

答案 5 :(得分:0)

我发现Aleris的反应很好。在我的例子中,我有一个dynamo db表,它包含两个非原始类的集合。

尝试各种风格的DBTypeConverters(采用{String,MyObject},{Collection,Collection},{String,Collection})并尝试Set而不是Collection,只需将引用的类注释为DynamoDBDocument,即表示我可以传递这些子类的json数据数组,并且数据保持正确。

我的“父类”看起来像这样(改名以保护无辜者);

@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类都使用DynamoDBDocument注释进行标记。