I need to annotate a class that has a Set<UUID> property. Marshalling a scalar UUID is fine, and I can use the @DynamoDBMarshalling annotation to specify my converter, and everything works.
When trying to use the same converter on a Set<UUID>, I get “DynamoDBMappingException: Expected SS in value”. I’ve tried creating a custom converter that expects Set<UUID>, but the same problem persists.
Is it possible to custom-marshall Sets?
@DynamoDBTable(tableName="djones-test")
public class UUIDRecommendation {
private UUID id;
private Set<UUID> recommendations;
@DynamoDBHashKey
@DynamoDBMarshalling(marshallerClass=UuidConverter.class)
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
//Neither this nor UuidConverter.class work here
@DynamoDBMarshalling(marshallerClass=SetUuidConverter.class)
public Set<UUID> getRecommendations() {
return recommendations;
}
public void setRecommendations(Set<UUID> recommendations) {
this.recommendations = recommendations;
}
}
Here’s the stack trace:
com.amazonaws.services.dynamodb.datamodeling.DynamoDBMappingException: Expected SS in value {SS: [1a841b97-ab9d-4425-a2c0-f9a81bebf0b4, 1a841b97-ab9d-4425-a2c0-f9a81bebf0b4, 1a841b97-ab9d-4425-a2c0-f9a81bebf0b4], } when invoking public void com.company.model.UUIDRecommendation.setRecommendations(java.util.Set)
at com.amazonaws.services.dynamodb.datamodeling.SUnmarshaller.typeCheck(SUnmarshaller.java:26)
at com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapper.setValue(DynamoDBMapper.java:329)
at com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapper.marshallIntoObject(DynamoDBMapper.java:302)
at com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapper.load(DynamoDBMapper.java:253)
at com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapper.load(DynamoDBMapper.java:196)
at com.mendeley.service.data.DynamoRecommendedItemsDataServiceTest.testObjectMapper(DynamoRecommendedItemsDataServiceTest.java:65)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Oh dear – looks like customer marshallers of sets is unpossible. The AWS SDK is hardcoded to only deal with single string values (SUnmarshaller()).
DynamoDBReflector in AWS SDK 1.3.13, line 185:
UPDATE
As an utterly filthy hack, I’ve cobbled something together that works by copy/pasting the entire class 🙁 This is why private static final utility services are bad, and dependency injection is good.
This will work so you can use one customer converter (
UuidConverterin my case) for getters that returnUUIDinstances, orSet<UUID>instances.I added a method called getCustomMarshalledValueSet, which iterates over the List returned by value.getSS(), calls the custom marshaller for each, and adds the result to a Set which it returns.
Additionally getArgumentUnmarshaller was changed to bring the isCollection check before the conditional to decide what type of unmarshaller to use, and changed the custom marshaller block to pick the right sort.