I understand there may be cleaner ways to store these data, I'm skipping that part for the sake of my sanity in dealing with legacy code.
I want to store an object that looks like this in DynamoDB:
#DynamoDBTable(tableName="TableName")
public class MyItem {
// DynamoDB Attributes
private String hashKey;
private String someAttribute;
private Map<String, Config> configs;
#DynamoDBHashKey(attributeName = "hash_key")
public String getHashKey() {
return this.hashKey;
}
public void setHashKey(String hashKey) {
this.hashKey = hashKey;
}
#DynamoDBAttribute(attributeName = "some_attribute")
public String getSomeAttribute() {
return this.someAttribute;
}
public void setSomeAttribute(String someAttribute ) {
this.someAttribute = someAttribute;
}
#DynamoDBAttribute(attributeName = "configs")
public Map<String, Config> getConfigs() {
return this.configs;
}
public void setConfigs(Map<String, Config> configs)
{
this.configs = configs;
}
}
With a supplemental class
#DynamoDBDocument
public class Config {
private String field;
#DynamoDBAttribute(attributeName="field")
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
}
Will this work as written?
What would the resulting entry look like in DynamoDB for the following JSON:
{
"hash_key":"123",
"some_attribute":"attribute_value",
"a_config_key" : {
"field":"field_value"
}
}
I would recommend you to implement your own converter using #DynamoDbConvertedBy (the official dynamodb-enhanced client).
Hopefully, this sample is helpful: https://stackoverflow.com/a/70602166/12869305
Related
I discovered the #JsonTypeInfo annotation via https://www.baeldung.com/jackson-annotations and tried to use this in order to dynamically parse a given YAML-file into different POJOs. The YAML could look like this:
AWSTemplateFormatVersion: '2010-09-09'
Resources:
AWSSNSTopic:
Properties:
Subscription:
Endpoint: someEnpointInfo
Protocol: email
ResourceName: HelloWorldTopic
Type: AWS::SNS::Topic
AWSServerlessFunction:
Properties:
Attributes: SomeString
ResourceName: HelloWorldFunction
Type: AWS::Serverless::Function
Transform: AWS::Serverless-2016-10-31
What I want to do is to parse the Resources dynamically since the properties of the objects inside Resources are dependent of the property Type but cannot be implicated by the keys (such as AWSSNSTopic or AWSServerlessFunction which only happen to be consistent with the type in this case but do not have to).
So I tried to solve this somehow via #JsonSubTypes to track down the Type property and decide what type of POJO this will be mapped on. This could look like follows:
public class AWSLambdaResource {
#JsonProperty("AWSTemplateFormatVersion")
private String AWSTemplateFormatVersion;
#JsonProperty("Transform")
private String Transform;
#JsonProperty("Resources")
private Map<String, Ressource> Resources;
public String getAWSTemplateFormatVersion() {
return AWSTemplateFormatVersion;
}
public void setAWSTemplateFormatVersion(String AWSTemplateFormatVersion) {
this.AWSTemplateFormatVersion = AWSTemplateFormatVersion;
}
public String getTransform() {
return Transform;
}
public void setTransform(String Transform) {
this.Transform = Transform;
}
public Map<String, Ressource> getResources() {
return Resources;
}
public void setResources(Map<String, Ressource> resources) {
Resources = resources;
}
}
public class Ressource {
public ResourceInstance resourceInstance;
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "Type")
#JsonSubTypes({
#JsonSubTypes.Type(value = AWSServerlessFunction.class, name = "AWS::Serverless::Function"),
#JsonSubTypes.Type(value = AWSSNSTopic.class, name = "AWS::SNS::Topic")
})
public static class ResourceInstance {
#JsonProperty("Type")
public String type;
#JsonProperty("ResourceName")
public String resourceName;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getResourceName() {
return resourceName;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
}
#JsonTypeName("AWSServerlessFunction")
public static class AWSServerlessFunction extends ResourceInstance {
#JsonProperty("ResourceName")
private String resourceName;
#JsonProperty("Properties")
private Properties properties;
public static class Properties {
#JsonProperty("Attributes")
public String attributes;
public String getAttributes() {
return attributes;
}
public void setAttributes(String attributes) {
this.attributes = attributes;
}
}
public String getResourceName() {
return resourceName;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
#JsonTypeName("AWSSNSTopic")
public static class AWSSNSTopic extends ResourceInstance {
#JsonProperty("Subscription")
public Subscription subscription;
public static class Subscription {
#JsonProperty("Endpoint")
public String endpoint;
#JsonProperty("Protocol")
public String protocol;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
}
public Subscription getSubscription() {
return subscription;
}
public void setSubscription(Subscription subscription) {
this.subscription = subscription;
}
}
public ResourceInstance getResourceInstance() {
return resourceInstance;
}
public void setResourceInstance(ResourceInstance resourceInstance) {
this.resourceInstance = resourceInstance;
}
}
However, when I look into the map there are entries with keys AWSSNSTopic and AWSServerlessFunction but the corresponding resourceInstance objects are null.
Where did I go wrong? Or is this simply not possible to solve with this type of approach?
I am trying to get java object from dynamic JSON.
One Important point these given classes are from third party API.
#JsonTypeInfo(
use = Id.NAME,
include = As.PROPERTY,
property = "nodeType"
)
#JsonSubTypes({ #Type(
name = "Filter",
value = Filter.class
), #Type(
name = "Criterion",
value = Criterion.class
)})
public abstract class Node {
public Node() {
}
#JsonIgnore
public EvaluationResult evaluate(Map<UUID, List<AnswerValue>> answers) {
Evaluator evaluator = new Evaluator();
return evaluator.evaluateAdvancedLogic(this, answers);
}
}
Filter.java
#JsonInclude(Include.NON_NULL)
#JsonPropertyOrder({"evaluationType", "filters"})
public class Filter extends Node {
#JsonProperty("evaluationType")
private EvaluationType evaluationType;
#NotNull
#JsonProperty("filters")
#Valid
private List<Node> filters = new ArrayList();
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap();
public Filter() {
}
#JsonProperty("evaluationType")
public EvaluationType getEvaluationType() {
return this.evaluationType;
}
#JsonProperty("evaluationType")
public void setEvaluationType(EvaluationType evaluationType) {
this.evaluationType = evaluationType;
}
#JsonProperty("filters")
public List<Node> getFilters() {
return this.filters;
}
#JsonProperty("filters")
public void setFilters(List<Node> filters) {
this.filters = filters;
}
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
}
Criterion.java
#JsonInclude(Include.NON_NULL)
#JsonPropertyOrder({"fieldSourceType", "fieldCategoryName", "sequenceNumber", "fieldName", "values", "operator", "fieldId"})
public class Criterion extends Node {
#JsonProperty("fieldSourceType")
private FieldSourceType fieldSourceType;
#JsonProperty("fieldCategoryName")
private String fieldCategoryName;
#NotNull
#JsonProperty("sequenceNumber")
private Long sequenceNumber;
#JsonProperty("fieldName")
private String fieldName;
#JsonProperty("values")
#Valid
private List<String> values = new ArrayList();
#JsonProperty("operator")
#Valid
private Operator operator;
#NotNull
#JsonProperty("fieldId")
private UUID fieldId;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap();
public Criterion() {
}
#JsonProperty("fieldSourceType")
public FieldSourceType getFieldSourceType() {
return this.fieldSourceType;
}
#JsonProperty("fieldSourceType")
public void setFieldSourceType(FieldSourceType fieldSourceType) {
this.fieldSourceType = fieldSourceType;
}
#JsonProperty("fieldCategoryName")
public String getFieldCategoryName() {
return this.fieldCategoryName;
}
#JsonProperty("fieldCategoryName")
public void setFieldCategoryName(String fieldCategoryName) {
this.fieldCategoryName = fieldCategoryName;
}
#JsonProperty("sequenceNumber")
public Long getSequenceNumber() {
return this.sequenceNumber;
}
#JsonProperty("sequenceNumber")
public void setSequenceNumber(Long sequenceNumber) {
this.sequenceNumber = sequenceNumber;
}
#JsonProperty("fieldName")
public String getFieldName() {
return this.fieldName;
}
#JsonProperty("fieldName")
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
#JsonProperty("values")
public List<String> getValues() {
return this.values;
}
#JsonProperty("values")
public void setValues(List<String> values) {
this.values = values;
}
#JsonProperty("operator")
public Operator getOperator() {
return this.operator;
}
#JsonProperty("operator")
public void setOperator(Operator operator) {
this.operator = operator;
}
#JsonProperty("fieldId")
public UUID getFieldId() {
return this.fieldId;
}
#JsonProperty("fieldId")
public void setFieldId(UUID fieldId) {
this.fieldId = fieldId;
}
}
The json used to conversion is this.
{
"evaluationType":"AND",
"nodeType":"Criterion",
"Criterion":[
{
"fieldName":"sdada",
"values":"sdad",
"operator":{
"operatorType":"Equals"
}
},
{
"nodeType":"Criterion",
"fieldName":"dasa",
"values":"das",
"operator":{
"operatorType":"Equals"
}
},
{
"nodeType":"Criterion",
"fieldName":"dada",
"values":"dads",
"operator":{
"operatorType":"Equals"
}
}
]
}
The problem is that deserialization of this JSON fails with following error:
{
"message": "Class com.cvent.logic.model.Criterion is not assignable to com.cvent.logic.model.Filter"
}
The first part of the JSON is wrong
{
"evaluationType":"AND",
"nodeType":"Criterion",
"Criterion":[
It says that the type is Criterion but it has evaluationType from Filter.
Also, probably "Criterion" : [ should be "filters" : [
Mapping an enum class in to DynamoDB object is really simple by using Custom Marshall. But how to map a List of Enum?
Enum class
public enum Transport {
SMS,EMAIL,ALL;
}
DynamoDB mapper
public class Campaign{
private List<Transport> transport;
#DynamoDBAttribute(attributeName = "transport")
public List<Transport> getTransport() {
return transport;
}
public void setTransport(List<Transport> transport) {
this.transport = transport;
}
}
DynamoDBMarshaller is deprecated.
Use DynamoDBTypeConverter instead.
Example:
Enum class
public static enum ValidationFailure {
FRAUD, GENERAL_ERROR
}
DynamoDBTable class
#DynamoDBTable(tableName = "receipt")
public class Receipt {
private Long id;
private List<ValidationFailure> validation;
#DynamoDBHashKey(attributeName = "id")
public Long getId() {
return id;
}
#DynamoDBTypeConverted(converter = ValidationConverter.class)
public List<ValidationFailure> getValidation() {
return validation;
}
public void setId(Long id) {
this.id = id;
}
public void setValidation(List<ValidationFailure> validation) {
this.validation = validation;
}
}
Convertor:
public class ValidationConverter implements DynamoDBTypeConverter<List<String>, List<ValidationFailure>> {
#Override
public List<String> convert(List<ValidationFailure> object) {
List<String> result = new ArrayList<String>();
if (object != null) {
object.stream().forEach(e -> result.add(e.name()));
}
return result;
}
#Override
public List<ValidationFailure> unconvert(List<String> object) {
List<ValidationFailure> result = new ArrayList<ValidationFailure>();
if (object != null) {
object.stream().forEach(e -> result.add(ValidationFailure.valueOf(e)));
}
return result;
}
}
It's working for me, I have used the Set
#DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.SS)
var roles: MutableSet<Employee.Role>? = null
I think the same approach would work for List with DynamoDBAttributeType.L
I found the answer myself. I create a custom marshall like below.
public class TransportMarshaller implements DynamoDBMarshaller<List<Transport>> {
#Override
public String marshall(List<Transport> transports) {
List<String>transportMap=new ArrayList<>();
for(Transport transport:transports){
transportMap.add(transport.name());
}
return transportMap.toString().replaceAll("\\[|\\]", "");//Save as comma separate value for the purpose of easiness to unmarshall
}
#Override
public List<Transport> unmarshall(Class<List<Transport>> aClass, String s) {
List<String>map= Arrays.asList(s.split("\\s*,\\s*")); //split from comma and parse to List
List<Transport>transports=new ArrayList<>();
for (String st:map){
transports.add(Transport.valueOf(st));
}
return transports;
}
}
I am trying to assign the value returned by some function to a field in the deserialized class of json.
FileInfo.java
public class FileInfo {
#SerializedName("Name")
private String mName;
#SerializedName("Url")
private String mUri;
#SerializedName("Size")
private Integer mSize;
#SerializedName("ModTime")
private Long mModifiedTime;
private FileType mType;
#SerializedName("Children")
private ArrayList<FileInfo> mChildren = new ArrayList<>();
public ArrayList<FileInfo> getChildren() {
return mChildren;
}
public long getModifiedTime() {
return mModifiedTime;
}
public String getName() {
return mName;
}
public Integer getSize() {
return mSize;
}
public String getUrl() {
return mUri;
}
public FileType getType() {
return mType;
}
public void setChildren(ArrayList<FileInfo> mChildren) {
this.mChildren = mChildren;
}
public void setModifiedTime(long mModifiedTime) {
this.mModifiedTime = mModifiedTime;
}
public void setName(String mName) {
this.mName = mName;
}
public void setSize(Integer mSize) {
this.mSize = mSize;
}
public void setType(FileType mType) {
this.mType = mType;
}
public void setUri(String mUri) {
this.mUri = mUri;
}
#Override
public String toString() {
return FileInfo.class.toString();
}
public FileInfo() {
}
}
The mType needs to be assigned to foo(mName). I looked up custom deserializers and instance creators but none of those helped. I also thought of TypeAdapters which i feel defeats the purpose of keeping deserialization(using GSON) simple.
This is a sample JSON string that will be deserialized.
[
{
"Name":"Airport",
"Url":"http://192.168.2.2/api/sites/Baltimore%20Airport/Airport",
"Size":0,
"ModTime":"2015-12-02T14:19:17.29824-05:00",
"Children":null
}
]
P.S. I'm not sure if this should be done during deserialization but trying anyways. Also please let me know of alternative ways to achieve this.
I'm being given a Json file with the form:
{
"descriptions": {
"desc1": "someString",
"desc2": {"name":"someName", "val": 7.0}
}
}
I have the POJO:
public class CustomClass {
Map<String, Object> descriptions;
public static class NameVal{
String name;
double val;
public NameVal(String name, double val){...}
}
}
I can recreate the json file with the code:
CustomClass a = new CustomClass();
a.descriptions = new HashMap<String, Object>();
a.descriptions.put("desc1", "someString");
a.descriptions.put("desc2", new CustomClass.NameVal("someName", 7.0));
new ObjectMapper().writeValue(new File("testfile"), a);
But, when I read the object back in using:
CustomClass fromFile = new ObjectMapper().readValue(new File("testfile"), CustomClass.class);
then fromFile.descriptions.get("desc2") is of type LinkedHashMap instead of type CustomClass.NameVal.
How can I get Jackson to properly parse the type of the CustomClass.NameVal descriptors (other than making some class that wraps the parsing and explicitly converts the LinkedHashMap after Jackson reads the file)?
Try this. Create a class Description with name and value attributes:
public class Description {
private String name;
private double val;
}
Now in your CustomClass do this:
public class CustomClass {
List<Description> descriptions;
}
And that's it. Remember to create getters and setters because Jackson needs it.
You could try something like this:
public class DescriptionWrapper {
private Description descriptions;
public Description getDescriptions() {
return descriptions;
}
public void setDescriptions(Description descriptions) {
this.descriptions = descriptions;
}
}
public class Description {
private String desc1;
private NameValue desc2;
public String getDesc1() {
return desc1;
}
public void setDesc1(String desc1) {
this.desc1 = desc1;
}
public NameValue getDesc2() {
return desc2;
}
public void setDesc2(NameValue desc2) {
this.desc2 = desc2;
}
}
public class NameValue {
private String name;
private double val;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getVal() {
return val;
}
public void setVal(double val) {
this.val = val;
}
}