【问题标题】:DynamoDB JsonMarshaller cannot Deserialize List of ObjectDynamoDB JsonMarshaller 无法反序列化对象列表
【发布时间】:2021-07-16 22:50:22
【问题描述】:

我有一个 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
    }
  }
}

【问题讨论】:

  • 你的编组器怎么没有实现DynamoDBMarshaller?你能提供一个MCVE 来重现异常吗?
  • 添加了 MCVE。既然JsonMarshaller已经实现了DynamoDBMarshaller,就不需要再实现了。

标签: java jackson amazon-dynamodb


【解决方案1】:

在较新的版本中仅适用于:

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

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

鉴于 Thing 带有以下注释:

@DynamoDBDocument
public class Thing {
}

【讨论】:

  • 谢谢。我刚刚用通常的 getter/setter 实现了“Thing”,除了添加 @DynamoDBDocument 之外不需要其他任何东西,它就可以工作了。最佳答案 imo。
  • 哪个库的版本是 2.3.7? aws-java-sdk-dynamodb?请提及对您有用的库名称。
  • 原来是aws-android-sdk-apigateway-core,好像和上面的信息没有直接关系。
  • 是否有文档/指南解释了我们究竟在哪里需要转换器?我必须通过反复试验来做到这一点。我读到这个:docs.aws.amazon.com/amazondynamodb/latest/developerguide/…,但它提供的信息很少..
  • 事物类如何为通用json添加属性...?
【解决方案2】:

这里的部分问题是整个 DynamoDB Mapper SDK 如何处理泛型。 interface DynamoDBMarshaller&lt;T extends Object&gt; 有一个方法T unmarshall(Class&lt;T&gt; clazz, String obj),其中要反序列化的类作为参数传递。问题是有type erasure,SDK 没有提供一个简单的处理方法。 Jackson 在某些情况下更聪明(JsonMarshaller 使用 Jackson),这就解释了为什么 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);
        }
    }
}

【讨论】:

  • 我在项目中创建了自己的编组器,它运行良好。谢谢。
  • @mkobit 这很好用,但我收到了错误,因为 DynamoDBMarshaller 已被弃用..
  • 谢谢@Jayendran - 我没有时间真正回顾这个问题,但我希望我最终会!
  • @mkobit :我已将此作为一个新问题 (stackoverflow.com/questions/48688807/…) 发布,但我忘记了答案已在下面发布 (stackoverflow.com/a/47153880/7073340)
【解决方案3】:

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;
    }
}

【讨论】:

    【解决方案4】:

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

    方法 1: 使用 @DynamoDBTypeConvertedJson。它将整个List&lt;Object&gt; 存储为单个字符串并将其检索为List&lt;Object&gt;。将此注释添加到您的字段 DynamoDB 将处理转换。

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

    方法 2: 使用自定义转换器。这会将List&lt;Object&gt; 存储为List&lt;String&gt; 在数据库中并将它们检索为List&lt;Object&gt;

    @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;
    }
    

    希望对某人有所帮助。

    【讨论】:

      【解决方案5】:

      Interface DynamoDBMarshaller&lt;T extends Object&gt; 已被弃用,替换为 Interface DynamoDBTypeConverter&lt;S,T&gt;

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

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

      public void setObjects(List objects) { this.objects = 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;
              }
          }  
      }
      

      【讨论】:

        【解决方案6】:

        我发现 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 注释进行标记。

        【讨论】:

          猜你喜欢
          • 2016-12-30
          • 1970-01-01
          • 2014-08-04
          • 2015-05-29
          • 1970-01-01
          • 2021-02-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多