Per the comments above, there is currently no solution via the AWS SDK V2.
The following code works to convert values to usable JSON when working directly with the DynamoDB API:
private Object unwrapAttributeValue(AttributeValue av) {
if (av.isNULL() != null && av.isNULL())
return null;
if (av.getM() != null)
return unmarshall(av.getM());
if (av.getL() != null)
return av.getL().stream().map(this::unwrapAttributeValue)
.collect(Collectors.toList());
return Stream
.<Function<AttributeValue, Object>>of(AttributeValue::getS,
AttributeValue::getN, AttributeValue::getBOOL,
AttributeValue::getNS, AttributeValue::getSS,
AttributeValue::getB, AttributeValue::getBS)
.map(f -> f.apply(av)).filter(Objects::nonNull).findFirst()
.orElseThrow();
}
public Map<String, Object> unmarshall(Map<String, AttributeValue> in) {
Map<String, Object> out = new HashMap<>();
for (Entry<String, AttributeValue> e : in.entrySet()) {
Object uav = unwrapAttributeValue(e.getValue());
if (uav != null)
out.put(e.getKey(), uav);
}
return out;
}
The resulting Map<String, Object>
can then be fed to Jackson for further processing like normal json data.
Another Use-Case
The following works for converting DynamoDB JSON after it's already been parsed via Jackson without going through the DynamoDB API, for example during Unit Testing with a JSON file retrieved from logs or an S3 export:
public static JsonNode unmarshall(JsonNode node) {
String type = node.fieldNames().next();
switch(type) {
case "S":
case "N":
case "BOOL":
case "SS":
case "NS":
return node.get(type);
case "NULL":
return null;
case "L":
ArrayNode arr = JsonNodeFactory.instance.arrayNode();
node.withArray(type).forEach(item -> {
arr.add(unmarshall(item));
});
return arr;
case "M":
ObjectNode out = JsonNodeFactory.instance.objectNode();
node.get(type).fieldNames().forEachRemaining(key -> {
final JsonNode value = unmarshall(node.get(type).get(key));
out.set(key, value);
});
return out;
default:
throw new IllegalArgumentException(type);
}
}