2015-06-12 13 views
6

Mam klasę Java, która jest modelem danych tabeli w DynamoDB. Chcę używać elementów od DynamoDBMapper do save i od Dynamo. Jednym z członków klasy jest List<MyObject>. Więc użyłem JsonMarshaller<List<MyObject>> do serializacji i odseparowania tego pola.DynamoDB JsonMarshaller nie może odserializować listy obiektów

Lista może być pomyślnie serializowana przez JsonMarshaller. Jednak gdy próbuję odzyskać wpis i odczytać listę, zgłasza wyjątek: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to MyObject. Wygląda na to, że JsonMarshaller rozszyfrowuje dane do LinkedHashMap zamiast MyObject. Jak mogę pozbyć się tego problemu?

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 
    } 
    } 
} 
+0

Jak to możliwe, że Marshall nie wdraża 'DynamoDBMarshaller'? Czy możesz podać [MCVE] (http://stackoverflow.com/help/mcve), aby odtworzyć wyjątek? – mkobit

+0

Dodano MCVE. Ponieważ '' JsonMarshall'' wprowadził '' DynamoDBMarshall'', nie musisz go ponownie wdrażać. –

Odpowiedz

7

Częścią problemu jest tutaj jak cały zestaw SDK DynamoDB Mapper radzi sobie z rodzajami generycznymi. interface DynamoDBMarshaller<T extends Object> ma metodę T unmarshall(Class<T> clazz, String obj), w której klasa do deserializacji do jest przekazywana jako parametr. Problem polega na tym, że istnieje type erasure, a SDK nie zapewnia łatwego w obsłudze tego rozwiązania. Jackson jest inteligentniejszy w niektórych przypadkach (JsonMarshaller używa Jackson), co wyjaśnia, dlaczego metoda serialize działa poprawnie.

Musisz zapewnić lepszą implementację deserializacji. Jednym ze sposobów, w jaki można to zrobić, byłoby implementowanie interfejsu DynamoDBMarshaller zamiast rozszerzania drugiego (moja opinia), aby mieć lepszą kontrolę nad serializacją typu.

Oto przykład, który jest w zasadzie kopiuj/wklej z JsonMarshaller z niewielkimi szczypie w deserializacji dla List dać wyobrażenie:

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); 
     } 
    } 
} 
+0

Tworzę własnego marshallera w projekcie i działa dobrze. Dzięki. –

+0

@mkobit Działa to świetnie, ale pojawia się błąd, ponieważ DynamoDBMarshaller jest przestarzały. – Jayendran

+0

Dzięki @Jayendran - Nie miałem czasu, aby naprawdę spojrzeć na to pytanie, ale mam nadzieję, że w końcu to zrobię! – mkobit

0

Interface DynamoDBMarshaller<T extends Object> jest nieaktualna już, wymiana jest Interface DynamoDBTypeConverter<S,T>.

Wewnątrz klasy modelu dodaj adnotację do listy obiektów.

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

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

A to jest implementacja 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; 
     } 
    } 
} 
4

W v2.3.7 to po prostu działa z:

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

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

zważywszy, że chodzi o to adnotated z:

@DynamoDBDocument 
public class Thing { 
} 
1

DynamoDBMarshaller jest teraz przestarzała ale mam dokładnie ten sam problem z DynamoDBTypeConvertedJson.Jeśli chcesz przechowywać kolekcję jako JSON w klasie DynamoDBMapper, użyj DynamoDBTypeConverted i napisz niestandardową klasę konwertera (nie używaj DynamoDBTypeConvertedJson, która nie zwróci Twojej kolekcji po konwersji).

Oto rozwiązanie przy użyciu 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; 
    } 
} 
0

Znalazłem że odpowiedź przez Aleris działa dobrze. W moim przykładzie, mam tabelę dynamo db, która zawiera dwie kolekcje, obie klasy niepochodzące z prymitywów.

Po wypróbowaniu różnych smaków DBTypeConverters (pobieranie {String, MyObject}, {Collection, Collection}, {String, Collection}), a także próba Set zamiast Collection, poprzez zwykłe opisanie klasy jako DynamoDBDocument, Mogłabym przekazać tablicę danych json dla tych klas potomnych, a dane były zachowywane poprawnie.

moja "klasa rodziców" wygląda tak (nazwiska zmienione w celu ochrony niewinnych);

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

Kod Type to wyliczenie. Klasy ImageRef, Presentation i Dimensions są oznaczone adnotacją DynamoDBDocument.

Powiązane problemy