3
votes

I have some entries in DynamoDB, which have an field as follows, in DynamoDB document format:

{ "foo" : { "N" : "45" }, "bar" : { "N" : "12" }}

I've got a Java class representing the document type:

public class FooBar {
  private final int foo;
  private final int bar;

  public FooBar(
      int foo,
      int bar
  ) {
    this.foo = foo;
    this.bar = bar;
  }

  public int getFoo() {
    return foo;
  }

  public int getBar() {
    return bar;
  }
}

I'd like to use a DynamoDB mapper to get and put rows to the table (the intermediate class representing the entire row is omitted for brevity). I don't want to add the DynamoDB annotations (@DynamoDBDocument, @DynamoDBAttribute etc) to the FooBar class as they require a zero-argument constructor and setter methods but this should be an immutable data object.

Therefore, I'd like to use a DynamoDBTypeConverter (as described here) to write my own conversion, but I cannot find a suitable 'source' type (to replace [?]):

class FooBarConverter implements DynamoDBTypeConverter<[?], FooBar> {
  @Override
  public [?] convert(FooBar object) {
    ...
  }

  @Override
  public FooBar unconvert([?] object) {
    ...
  }
}
  • Map, which would seem the most natural, fails with DynamoDBMappingException: not supported; requires @DynamoDBTyped or @DynamoDBTypeConverted
  • String, which would seem to be a reasonable default, doesn't error but the string supplied to the convert() method is always empty
  • Object, which would seem to be a catch-all, isn't supported by Dynamo and fails with DynamoDBMappingException: could not resolve type of class FooBarConverter

If we regard both the Java class and the storage format as being unmodifiable, what solution is there?

1
Use @DynamoDBTypeConverted(converter = FooBarConverter.class) to the getter of the column you want to give this converter.prem30488
Also use import statement to import FooBarConverter in POJO.prem30488
In DynamoDBTypeConverter<[?], FooBar>, ? should be something, for example String. Code your logic in convert and unconvert to get desired json to object and object to json.prem30488
Use N.N is DynamoDB type -> N (number type)prem30488

1 Answers

2
votes

I've found an appropriate type to use for the source - AttributeValue. Here's the converter:

public class FooBarConverter implements DynamoDBTypeConverter<AttributeValue, FooBar> {
  @Override
  public AttributeValue convert(FooBar object) {
    return new AttributeValue()
        .addMEntry("foo", new AttributeValue().withN(Integer.toString(object.getFoo())))
        .addMEntry("bar", new AttributeValue().withN(Integer.toString(object.getBar())));
  }

  @Override
  public FooBar unconvert(AttributeValue object) {
    Map<String, AttributeValue> objectM = object.getM();
    int foo = Integer.parseInt(objectM.get("foo").getN());
    int bar = Integer.parseInt(objectM.get("bar").getN());
    return new FooBar(foo, bar);
  }
}

Also, a class to represent the whole row in Dynamo:

public class FooBarRow {
  private String key;
  private FooBar fooBar;

  public FooBarRow(
      String key,
      FooBar fooBar
  ) {
    this.key = key;
    this.fooBar = fooBar;
  }

  public FooBarRow() {
  }

  @DynamoDBHashKey
  public String getKey() {
    return key;
  }

  public void setKey(String key) {
    this.key = key;
  }

  @DynamoDBTypeConverted(converter = FooBarConverter.class)
  public FooBar getFooBar() {
    return fooBar;
  }

  public void setFooBar(FooBar fooBar) {
    this.fooBar = fooBar;
  }
}

Now, saving and loading FooBarRow instances using a DynamoDBMapper works perfectly.

public class FooBarMain {
  public static void main(String[] args) {
    DynamoDBMapper mapper = new DynamoDBMapper(
        AmazonDynamoDBClientBuilder.standard().build(),
        DynamoDBMapperConfig
            .builder()
            .withTableNameOverride(new DynamoDBMapperConfig.TableNameOverride("foobar"))
            .build()
    );

    mapper.save(new FooBarRow("foobar1", new FooBar(123, 4)));

    mapper.load(FooBarRow.class, "foobar1").getFooBar();
  }
}