2

I really need Your help, I'm trying to create simple kitchen management application. I decided to use AWS with DynamoDB, I stucked with CRUD methods: add and delete methods seems to work fine, but I have big problems with editing database records.

Error message in log file:

"Can not deserialize instance of java.util.ArrayList out of START_OBJECT token "

This is how my model class looks like:

@DynamoDBTable(tableName = "product")
public class Product extends Request {

/**
 * Id of kitchen content
 */
private String id;
/**
 * Name of product
 */
private String name;

/**
 * Calories in 100g
 */
private Integer calories;

/**
 * Fat in 100g
 */
private Double fat;

/**
 * Total carbo in 100g
 */
private Double carbo;

/**
 * Total Protein in 100g
 */
private Double protein;

/**
 * Labels of product for example gluten fee product
 */
private List<ProductKind> productKinds;

/**
 * Author of content.
 */
private Author author;

/**
 * Address of content image.
 */
private Media media;

private Boolean approved;

@DynamoDBHashKey(attributeName = "id")
@DynamoDBAutoGeneratedKey
public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

@DynamoDBAttribute(attributeName = "Name")
public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

@DynamoDBAttribute(attributeName = "Calories")
public Integer getCalories() {
    return calories;
}

public void setCalories(Integer calories) {
    this.calories = calories;
}

@DynamoDBAttribute(attributeName = "Fat")
public Double getFat() {
    return fat;
}

public void setFat(Double fat) {
    this.fat = fat;
}

@DynamoDBAttribute(attributeName = "Carbo")
public Double getCarbo() {
    return carbo;
}

public void setCarbo(Double carbo) {
    this.carbo = carbo;
}

@DynamoDBAttribute(attributeName = "Protein")
public Double getProtein() {
    return protein;
}

public void setProtein(Double protein) {
    this.protein = protein;
}

@DynamoDBTypeConverted(converter = EnumConverter.class)
@DynamoDBAttribute(attributeName = "ProductKinds")
public List<ProductKind> getProductKinds() {
    return productKinds;
}

public void setProductKinds(List<ProductKind> productKinds) {
    this.productKinds = productKinds;
}

@DynamoDBTypeConverted(converter = ObjectConverter.class)
@DynamoDBAttribute(attributeName = "Author")
public Author getAuthor() {
    return author;
}

public void setAuthor(Author author) {
    this.author = author;
}

@DynamoDBTypeConverted(converter = ObjectConverter.class)
@DynamoDBAttribute(attributeName = "Media")
public Media getMedia() {
    return media;
}

public void setMedia(Media media) {
    this.media = media;
}

@DynamoDBAttribute(attributeName = "Approved")
public Boolean getApproved() {
    return approved;
}

public void setApproved(Boolean approved) {
    this.approved = approved;
}


@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Product product = (Product) o;

    if (name != null ? !name.equals(product.name) : product.name != null) return false;
    if (calories != null ? !calories.equals(product.calories) : product.calories != null) return false;
    if (fat != null ? !fat.equals(product.fat) : product.fat != null) return false;
    if (carbo != null ? !carbo.equals(product.carbo) : product.carbo != null) return false;
    if (protein != null ? !protein.equals(product.protein) : product.protein != null) return false;
    if (productKinds != null ? !productKinds.equals(product.productKinds) : product.productKinds != null)
        return false;
    if (author != null ? !author.equals(product.author) : product.author != null) return false;
    if (media != null ? !media.equals(product.media) : product.media != null) return false;
    return approved != null ? approved.equals(product.approved) : product.approved == null;
}

@Override
public int hashCode() {
    int result = id != null ? id.hashCode() : 0;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    result = 31 * result + (calories != null ? calories.hashCode() : 0);
    result = 31 * result + (fat != null ? fat.hashCode() : 0);
    result = 31 * result + (carbo != null ? carbo.hashCode() : 0);
    result = 31 * result + (protein != null ? protein.hashCode() : 0);
    result = 31 * result + (productKinds != null ? productKinds.hashCode() : 0);
    result = 31 * result + (author != null ? author.hashCode() : 0);
    result = 31 * result + (media != null ? media.hashCode() : 0);
    result = 31 * result + (approved != null ? approved.hashCode() : 0);
    return result;
}


@Override
public String toString() {
    return "Product{" +
            "id='" + id + '\'' +
            ", name='" + name + '\'' +
            ", calories=" + calories +
            ", fat=" + fat +
            ", carbo=" + carbo +
            ", protein=" + protein +
            ", productKinds=" + productKinds +
            ", author=" + author +
            ", media=" + media +
            ", approved=" + approved +
            '}';
}

And this is how my service method looks:

public class KitchenService {

public KitchenService(DatabaseController databaseController, LambdaLogger logger) {
    this.databaseController = databaseController;
    this.logger = logger;
}


private DatabaseController databaseController;
private LambdaLogger logger;

public Product addProduct(Product content) {

    databaseController.saveTest(content);
    logger.log("Product created");

    return content;

}

public Product editProduct(Product product) {
    Product databaseProduct = databaseController.get(Product.class, product.getId());
    if (databaseProduct != null) {
        logger.log("Database product exist: " + databaseProduct.toString());
    }
    if (product.getId() != null && product.getId().isEmpty()) databaseController.save(product);

    databaseController.save(databaseProduct);

    logger.log("Product updated");

    return databaseProduct;
}


public void deleteProduct(Product product) {
    Product databaseProduct = databaseController.get(Product.class, product.getId());
    databaseController.deleteProductFromDB(databaseProduct);

    logger.log("Item deleted");
}

For sure u need to see how save method in dynamoDBcontroller looks:

public <T> void save(T objectToSave) {
    logger.log("Saveing object " + objectToSave);
    getMapper().save(objectToSave);
    logger.log("Saved object " + objectToSave);
}

And get from DB method:

public <T> T get(Class<T> objectClass, String id) {
    logger.log("Getting object class " + objectClass + ", where id was " + id);
    T data = getMapper().load(objectClass, id);
    logger.log("Get data " + data);
    if(data instanceof Product){
        logger.log("Product Class found");
        logger.log(data.toString());
    }
    return data;
}

I know that i need to have converter to do it well so I've created generic converter for complex object class and other mapper for enums:

Object:

public class ObjectConverter<T extends Object> implements DynamoDBTypeConverter<String, T> {

ObjectMapper objectMapper = new ObjectMapper();


@Override
public String convert(T object) {

    try {
        return objectMapper.writeValueAsString(object);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    throw new IllegalArgumentException("Unable to parse JSON");
}

@Override
public T unconvert(String object) {
    try {
        T unconvertedObject = objectMapper.readValue(object, new TypeReference<T>() {
        });
        return unconvertedObject;
    } catch (JsonParseException e) {
        e.getMessage();
    } catch (JsonMappingException e) {
        e.getMessage();
    } catch (IOException e) {
        e.printStackTrace();
    }
    throw new IllegalArgumentException("Unable to parse to JSON");
}

Enums:

public class EnumConverter<T extends Object> implements DynamoDBTypeConverter<String, List<T>> {

@Override
public String convert(List<T> 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<T> unconvert(String objectsString) {
    ObjectMapper objectMapper = new ObjectMapper();
    try {
        List<T> objects = objectMapper.readValue(objectsString, new TypeReference<List<T>>() {
        });
        return objects;
    } catch (JsonParseException e) {
        //do something
    } catch (JsonMappingException e) {
        //do something
    } catch (IOException e) {
        //do something
    }
    return null;
}

I'm testing this using POSTMAN, this is how JSON that I'm sending looks:

{"id":"3bdc1372-4f67-493e-92d7-db3b529fe3bd","name":"bąkiKacpraPoEdycji","calories":1500,"fat":400.0,"carbo":20.0,"protein":40.0,"productKinds":["MEAT"],"author":{"name":"Plejer Annołn","id":"testID2"},"media":{"name":"heheszki","url":"http://blabla.pl","mediaType":"IMAGE"},"approved":false}

More details:

Product[Media]; could not unconvert attribute: com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: Product[Media]; could not unconvert attribute

D.Zet
  • 753
  • 3
  • 11
  • 32
  • It would help if you posted the stack trace of that error message on the top. Also the JSON you are sending does not seem to match the model class. How are you deserializing that into your model class? – jingx Jan 04 '18 at 15:13
  • I copied wrong JSON, I edited my question. I'm using ObjectConverter that implements DynamoDBTypeConverter and there is unconvert method, I have to do this with generics, I can't use differents converters to every complex classes even if it works fine :( – D.Zet Jan 04 '18 at 16:08

0 Answers0