I am having issues when trying to deserializing the following class:
public class MetricValuesDto {
private Map<MetricType, MetricValueDto<?>> metricValues;
public MetricValuesDto() {
}
public MetricValuesDto(Map<MetricType, MetricValueDto<?>> metricValues) {
this.metricValues = metricValues;
}
public Map<MetricType, MetricValueDto<?>> getMetricValues() {
return metricValues;
}
public void setMetricValues(Map<MetricType, MetricValueDto<?>> metricValues) {
this.metricValues = metricValues;
}
}
My generic abstract class:
public abstract class MetricValueDto<T> {
private T value;
private MetricTrend trend;
public MetricValueDto(T value, MetricTrend trend) {
this.value = value;
this.trend = trend;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public MetricTrend getTrend() {
return trend;
}
public void setTrend(MetricTrend trend) {
this.trend = trend;
}
}
I have two concrete classes which implement MetricValueDto:
IntMetricValueDto:
public class IntMetricValueDto extends MetricValueDto<Integer> {
public IntMetricValueDto(Integer value, MetricTrend trend) {
super(value, trend);
}
}
FloatMetricValueDto:
public class FloatMetricValueDto extends MetricValueDto<Float> {
public FloatMetricValueDto(Float value, MetricTrend trend) {
super(value, trend);
}
}
Any idea of what's the correct strategy to deserialize MetricValueDto so I can parse it through ObjectMapper or an RestTemplate? Whenever I run:
restTemplate.exchange("myEndpoint", HttpMethod.GET, entity, DataCollectionEventDto.class);
I get
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.resson.dto.MetricValueDto: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
DataCollectionEventDto:
public class DataCollectionEventDto {
private List<MapLayerDto> mapLayers;
#JsonUnwrapped
private MetricValuesDto metricValues;
public List<MapLayerDto> getMapLayers() {
return mapLayers;
}
public void setMapLayers(List<MapLayerDto> mapLayers) {
this.mapLayers = mapLayers;
}
public MetricValuesDto getMetricValues() {
return metricValues;
}
public void setMetricValues(MetricValuesDto metricValues) {
this.metricValues = metricValues;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
I have basically tried everything on web and I could not make it work; any suggestion would be helpful.
Use JsonSubTypes annotation with JsonTypeInfo to indicate subtypes. The property attribute JsonTypeInfo is used to differentiate between different subclasses.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "typ")
#JsonSubTypes({
#JsonSubTypes.Type(value = IntMetricValueDto.class, name = "INT"),
#JsonSubTypes.Type(value = FloatMetricValueDto.class, name = "FLT")})
public abstract class MetricValueDto<T> {
private T value;
private MetricTrend trend;
...
}
While JsonTypeInfo works, and adds implementation-specific detail to the response, which later might add confusion to the API client.
I ended up implementing a custom StdDeserializer:
public class MetricValueDtoDeserializer<T> extends StdDeserializer<MetricValueDto<T>> {
private static final long serialVersionUID = 1L;
public MetricValueDtoDeserializer() {
this(null);
}
public MetricValueDtoDeserializer(Class<?> vc) {
super(vc);
}
private ObjectMapper mapper;
#Override
public MetricValueDto<T> deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException, JsonProcessingException {
String metricType = jsonParser.getCurrentName();
mapper = (ObjectMapper) jsonParser.getCodec();
ObjectNode objectNode = (ObjectNode) mapper.readTree(jsonParser);
Iterator<Entry<String, JsonNode>> elementsIterator = objectNode.fields();
Number number = null;
while (elementsIterator.hasNext()) {
Entry<String, JsonNode> element = elementsIterator.next();
String key = element.getKey();
if (key.equals("value")) {
number = parseValue(element, metricType);
}
if (key.equals("trend")) {
MetricTrend metricTrend = parseTrend(element);
return (produceMetricValueDto(number, metricTrend));
}
}
throw new IOException();
}
#SuppressWarnings("unchecked")
private MetricValueDto<T> produceMetricValueDto(Number number, MetricTrend metricTrend) throws IOException {
if (number instanceof Integer) {
return (MetricValueDto<T>) new IntMetricValueDto((Integer) number, metricTrend);
} else if (number instanceof Float) {
return (MetricValueDto<T>) new FloatMetricValueDto((Float) number, metricTrend);
}
throw new IOException();
}
private MetricTrend parseTrend(Entry<String, JsonNode> element)
throws JsonProcessingException {
String trend = mapper.treeToValue(element.getValue(), String.class);
if (trend == null) {
return null;
} else {
return MetricTrend.valueOf(trend);
}
}
private Number parseValue(Entry<String, JsonNode> element, String metricType)
throws IOException {
if (metricType.equals(MetricType.CANOPY_COVERAGE.toValue())
|| metricType.equals(MetricType.PLANT_SIZE.toValue())) {
return mapper.treeToValue(element.getValue(), Float.class);
} else if (metricType.equals(MetricType.INSECT_COUNT.toValue())
|| metricType.equals(MetricType.PLANT_COUNT.toValue())) {
return mapper.treeToValue(element.getValue(), Integer.class);
}
throw new IOException();
}
}
The code ended up to being more complex than JsonTypeInfo, but the API client is unaware of implementation-specific details.
Related
I'm facing an issue while using ObjectMapper for non-static inner class. I need to create MixIn to make it work but could not reach to the solution. Below is my class(which I can't change) and the MixIn, I tried. Help needed to create such MixIn.
Base Class
public class NestedClass implements Serializable{
private static final long serialVersionUID = -4509619645418618657L;
private NestedInnerClass innerClass;
public NestedClass() {
innerClass = null;
setInnerClass(new NestedInnerClass(new NestedInnerClass2(), new NestedInnerClass3()));
}
public NestedClass(NestedClass nestedCls) {
innerClass = null;
setInnerClass(nestedCls.getInnerClass());
}
public class NestedInnerClass implements Serializable{
private static final long serialVersionUID = 9099474732768960830L;
NestedClass.NestedInnerClass2 nestedInnerClass2;
NestedClass.NestedInnerClass3 nestedInnerClass3;
public NestedInnerClass() {
super();
}
public NestedInnerClass(NestedInnerClass2 nestedInnerClass2, NestedInnerClass3 nestedInnerClass3) {
super();
this.nestedInnerClass2 = nestedInnerClass2;
this.nestedInnerClass3 = nestedInnerClass3;
}
public NestedClass.NestedInnerClass2 getNestedInnerClass2() {
return nestedInnerClass2;
}
public void setNestedInnerClass2(NestedClass.NestedInnerClass2 nestedInnerClass2) {
this.nestedInnerClass2 = nestedInnerClass2;
}
public NestedClass.NestedInnerClass3 getNestedInnerClass3() {
return nestedInnerClass3;
}
public void setNestedInnerClass3(NestedClass.NestedInnerClass3 nestedInnerClass3) {
this.nestedInnerClass3 = nestedInnerClass3;
}
}
public class NestedInnerClass2 implements Serializable{
private static final long serialVersionUID = -3451502802923307744L;
String nestedString;
HashMap<String, String> nestedHashMap = new HashMap<String, String>();
public NestedInnerClass2() {
super();
}
public NestedInnerClass2(String nestedString, HashMap<String, String> nestedHashMap) {
super();
this.nestedString = nestedString;
this.nestedHashMap = nestedHashMap;
}
public NestedInnerClass2(String nestedString) {
this.nestedString = nestedString;
}
public String getNestedString() {
return nestedString;
}
public void setNestedString(String nestedString) {
this.nestedString = nestedString;
}
public HashMap<String, String> getNestedHashMap() {
return nestedHashMap;
}
public void setNestedHashMap(HashMap<String, String> nestedHashMap) {
this.nestedHashMap = nestedHashMap;
}
}
public class NestedInnerClass3 implements Serializable{
private static final long serialVersionUID = 1799737022784300052L;
String nestedString;
public NestedInnerClass3() {
super();
}
public NestedInnerClass3(String nestedString) {
super();
this.nestedString = nestedString;
}
public String getNestedString() {
return nestedString;
}
public void setNestedString(String nestedString) {
this.nestedString = nestedString;
}
}
public NestedInnerClass getInnerClass() {
return innerClass;
}
public void setInnerClass(NestedInnerClass innerClass) {
this.innerClass = innerClass;
}
}
Child Class of Nested Class :
public class NestedClassChild extends NestedClass implements Serializable, Cloneable{
private static final long serialVersionUID = 7022339501842754692L;
public NestedClassChild() {}
}
Assist Class :
public class NestedClassAssist {
public static void setNestedValues(NestedClass nestedClass, String key, String value, String nestedString)
{
if(nestedClass != null && nestedClass.getInnerClass() != null && nestedClass.getInnerClass().getNestedInnerClass2() != null)
{
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put(key, value);
nestedClass.getInnerClass().getNestedInnerClass2().setNestedHashMap(hashMap);
nestedClass.getInnerClass().getNestedInnerClass2().setNestedString(nestedString);
}
}
public static void setValue(NestedClass nestedClass, String value){
setNestedValues(nestedClass, "keyStr", value, "ABC");
}
}
To convert to JSON payload :
public class NestedClassToJson {
public static void main(String[] args) {
NestedClassChild nestedClassChild = new NestedClassChild();
NestedClassAssist.setValue(nestedClassChild, "12345");
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.writerWithDefaultPrettyPrinter();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
mapper.writeValue(new File("json/testNested.json"),nestedClassChild);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Generated JSON payload from above class :
{
"innerClass" : {
"nestedInnerClass2" : {
"nestedString" : "ABC",
"nestedHashMap" : {
"keyStr" : "12345"
}
},
"nestedInnerClass3" : {
"nestedString" : null
}
}
}
Class to de-serialize from JSON :
public class NestedClassFromJson {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.writerWithDefaultPrettyPrinter();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
NestedClass objectNested = mapper.readValue(getPostBodyAsJSON(), NestedClassChild.class);
System.out.println(mapper.writeValueAsString(objectNested));
}
private static String getPostBodyAsJSON() {
StringBuffer postBody = new StringBuffer();
String line = null;
try {
BufferedReader reader = new BufferedReader(new FileReader(new File("json/testNested.json")));
while ((line = reader.readLine()) != null)
postBody.append(line);
} catch (IOException e) {
throw new RuntimeException("Issue Occured While Reading POST Body", e);
}
return postBody.toString();
}
}
But I'm getting below Exception (though I do have default contructor) :
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com.test.jackson.NestedClass$NestedInnerClass]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: { "innerClass" : { "nestedInnerClass2" : { "nestedString" : "ABC", "nestedHashMap" : { "keyStr" : "12345" } }, "nestedInnerClass3" : { "nestedString" : null } }}; line: 1, column: 24] (through reference chain: com.test.jackson.NestedClassChild["innerClass"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1106)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:296)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:133)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:520)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:95)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:258)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:125)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3736)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2726)
at com.test.jackson.NestedClassFromJson.main(NestedClassFromJson.java:21)
Nested MixIn which I tried but didn't work:
public abstract class NestedMixIn {
#JsonCreator
public NestedMixIn(#JsonProperty("innerClass") NestedInnerClass innerClass ) {
}
public static class SourceIdInnerMixin{
#JsonCreator
public SourceIdInnerMixin(#JsonProperty("nestedInnerClass2") NestedInnerClass2 nestedInnerClass2,
#JsonProperty("nestedInnerClass3") NestedInnerClass3 nestedInnerClass3) {
}
}
}
If I make the inner classes static , it works but since it is 3rd party class, I can't change it.
Will appreciate your help !!!
In your example, I did not notice any relation between parent class and nested classes. Also you mentioned you can change it to static and it works, so all we need to do, is provide an instance of inner class to deserialisation process. By default Jackson uses com.fasterxml.jackson.databind.deser.BeanDeserializer to map JSON Object to a given class. We can extend it and register suppliers to instantiate objects.
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Supplier;
public class JsonNestedApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule nestedModule = new SimpleModule();
nestedModule.setDeserializerModifier(new NestedBeanDeserializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(nestedModule);
// other configuration
NestedClass nestedClass = mapper.readValue(jsonFile, NestedClass.class);
System.out.println(nestedClass);
}
}
class NestedBeanDeserializerModifier extends BeanDeserializerModifier {
private final NestedClass parent = new NestedClass();
private final Map<Class, Supplier> availableSuppliers = new HashMap<>();
public NestedBeanDeserializerModifier() {
availableSuppliers.put(NestedClass.NestedInnerClass2.class, () -> parent.new NestedInnerClass2());
availableSuppliers.put(NestedClass.NestedInnerClass3.class, () -> parent.new NestedInnerClass3());
}
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
final Supplier supplier = availableSuppliers.get(beanDesc.getBeanClass());
if (supplier != null) {
return new NestedBeanDeserializer((BeanDeserializerBase) deserializer, supplier);
}
return deserializer;
}
}
class NestedBeanDeserializer extends BeanDeserializer {
private final Supplier supplier;
protected NestedBeanDeserializer(BeanDeserializerBase src, Supplier supplier) {
super(src);
this.supplier = Objects.requireNonNull(supplier);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(p, ctxt, supplier.get());
}
}
Above code should deserialise JSON payload with success.
As suggested above : Solution is to extend the BeanDeserializer class.
======================
Supplier Interface:
public interface Supplier<T> {
T get();
}
======================
BeanDeserializerModifier :
public class NestedBeanDeserializerModifier extends BeanDeserializerModifier {
private NestedClass parent = new NestedClass();
private Map<Class<?>, Supplier<?>> availableSuppliers = new HashMap<Class<?>, Supplier<?>>();
public NestedBeanDeserializerModifier() {
availableSuppliers.put(NestedClass.NestedInnerClass.class, new Supplier<NestedClass.NestedInnerClass>() {
public NestedClass.NestedInnerClass get() {
return parent.new NestedInnerClass();
}
});
availableSuppliers.put(NestedClass.NestedInnerClass2.class, new Supplier<NestedClass.NestedInnerClass2>() {
public NestedClass.NestedInnerClass2 get() {
return parent.new NestedInnerClass2();
}
});
availableSuppliers.put(NestedClass.NestedInnerClass3.class, new Supplier<NestedClass.NestedInnerClass3>() {
public NestedClass.NestedInnerClass3 get() {
return parent.new NestedInnerClass3();
}
});
}
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
final Supplier<?> supplier = availableSuppliers.get(beanDesc.getBeanClass());
if (supplier != null) {
return new NestedBeanDeserializer((BeanDeserializerBase) deserializer, supplier);
}
return deserializer;
}
}
======================
BeanDeserializer :
public class NestedBeanDeserializer extends BeanDeserializer {
private static final long serialVersionUID = 1L;
private Supplier<?> supplier;
protected NestedBeanDeserializer(BeanDeserializerBase src, Supplier<?> supplier) {
super(src);
this.supplier = requireNonNull(supplier);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(p, ctxt, supplier.get());
}
private static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
}
====================
Output class using above deserializer :
public class NestedClassFromJson {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.writerWithDefaultPrettyPrinter();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
SimpleModule nestedModule = new SimpleModule();
nestedModule.setDeserializerModifier(new NestedBeanDeserializerModifier());
mapper.registerModule(nestedModule);
NestedClass objectNested = mapper.readValue(getPostBodyAsJSON(), NestedClassChild.class);
System.out.println(mapper.writeValueAsString(objectNested));
}
private static String getPostBodyAsJSON() {
StringBuffer postBody = new StringBuffer();
String line = null;
try {
BufferedReader reader = new BufferedReader(new FileReader(new File("json/testNested.json")));
while ((line = reader.readLine()) != null)
postBody.append(line);
} catch (IOException e) {
throw new RuntimeException("Issue Occured While Reading POST Body", e);
}
return postBody.toString();
}
}
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;
}
}
For json mapping I use the following method:
public static <T> T mapJsonToObject(String json, T dtoClass) throws Exception {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, new TypeReference<RestResponse<UserDto>>() {
});
}
And UserDto looks like this:
#JsonIgnoreProperties(ignoreUnknown = true)
public class UserDto {
#JsonProperty("items")
private List<User> userList;
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User> userList) {
this.userList = userList;
}
}
I want to improve this method of mapping without being attached to a UserDto class, and replacing it with a generic.
Is it possible? And How?
Thanks.
TypeReference requires you to specify parameters statically, not dynamically, so it does not work if you need to further parameterize types.
What I think you need is JavaType: you can build instances dynamically by using TypeFactory. You get an instance of TypeFactory via ObjectMapper.getTypeFactory(). You can also construct JavaType instances from simple Class as well as TypeReference.
One approach will be to define a Jackson JavaType representing a list of items of type clazz. You still need to have access to the class of the generic parameter at runtime. The usual approach is something like
<T> class XX { XX(Class<T> clazz, ...) ... }
to pass the class of the generic parameter into the generic class at construction.
Upon access to the Class clazz variable you can construct a Jackson JavaType representing, for example, a list of items of class clazz with the following statement.
JavaType itemType = mapper.getTypeFactory().constructCollectionType(List.class, clazz);
I hope it helped. I am using this approach in my own code.
Try this:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Array;
import java.util.Arrays;
...
public static <TargetType> List<TargetType> convertToList(String jsonString, Class<TargetType> targetTypeClass) {
List<TargetType> listOfTargetObjects = null;
ObjectMapper objectMapper = new ObjectMapper();
TargetType[] arrayOfTargetType = (TargetType[]) Array.newInstance(targetTypeClass, 0);
try {
listOfTargetObjects = (List<TargetType>) Arrays.asList(objectMapper.readValue(jsonString, arrayOfTargetType.getClass()));
} catch (JsonMappingException jsonMappingException) {
listOfTargetObjects = null;
} catch (JsonProcessingException jsonProcessingException) {
listOfTargetObjects = null;
} catch (Exception exception) {
listOfTargetObjects = null;
}
return listOfTargetObjects;
}
...
This is an example of parsing simple List based generics with Jackson, with a simple Java annotation!
package innovate.tamergroup.lastmiledelivery.loader.otm.models;
import java.util.List;
import javax.annotation.Generated;
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"hasMore",
"limit",
"count",
"offset",
"items",
"links"
})
#Generated("jsonschema2pojo")
public class OTMListWrapper<T> {
#JsonProperty("hasMore")
private Boolean hasMore;
#JsonProperty("limit")
private Long limit;
#JsonProperty("count")
private Long count;
#JsonProperty("offset")
private Long offset;
#JsonTypeInfo(use=JsonTypeInfo.Id.NONE, include=JsonTypeInfo.As.PROPERTY, property="items")
private List<T> items = null;
#JsonProperty("links")
private List<OTMLink> links = null;
#JsonProperty("hasMore")
public Boolean getHasMore() {
return hasMore;
}
#JsonProperty("hasMore")
public void setHasMore(Boolean hasMore) {
this.hasMore = hasMore;
}
public OTMListWrapper<T> withHasMore(Boolean hasMore) {
this.hasMore = hasMore;
return this;
}
#JsonProperty("limit")
public Long getLimit() {
return limit;
}
#JsonProperty("limit")
public void setLimit(Long limit) {
this.limit = limit;
}
public OTMListWrapper<T> withLimit(Long limit) {
this.limit = limit;
return this;
}
#JsonProperty("count")
public Long getCount() {
return count;
}
#JsonProperty("count")
public void setCount(Long count) {
this.count = count;
}
public OTMListWrapper<T> withCount(Long count) {
this.count = count;
return this;
}
#JsonProperty("offset")
public Long getOffset() {
return offset;
}
#JsonProperty("offset")
public void setOffset(Long offset) {
this.offset = offset;
}
public OTMListWrapper<T> withOffset(Long offset) {
this.offset = offset;
return this;
}
#JsonProperty("items")
public List<T> getItems() {
return items;
}
#JsonProperty("items")
public void setItems(List<T> items) {
this.items = items;
}
public OTMListWrapper<T> withItems(List<T> items) {
this.items = items;
return this;
}
#JsonProperty("links")
public List<OTMLink> getLinks() {
return links;
}
#JsonProperty("links")
public void setLinks(List<OTMLink> links) {
this.links = links;
}
public OTMListWrapper<T> withLinks(List<OTMLink> links) {
this.links = links;
return this;
}
}
I'm trying to document my JSON API. My API returns Java POJOs which get serialized by Jackson. I'm writing a service to return example JSON for each service. What I'd like to do is something like this:
class MyPojo {
#Example("Bob")
public String name;
#Example(42)
public Integer myInt;
public String noExample;
}
I'll need some method to get Jackson to serialize this as:
{
"name": "Bob",
"myInt": 42
"noExample": "string"
}
when I need an example.
What's the easiest way to make this happen?
You can consider using the ObjectMapper.acceptJsonFormatVisitor method to access the meta information of your bean class. Similar approach is used in the Jackson JSON schema module for schema generation.
Here is an example demonstrating the idea:
public class JacksonSchema1 {
#Retention(RetentionPolicy.RUNTIME)
public static #interface Example {
String value();
}
public static class MyPojo {
#Example("Bob")
public String name;
#Example("42")
public Integer myInt;
public String noExample;
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyJsonFormatVisitorWrapper visitor = new MyJsonFormatVisitorWrapper(mapper);
mapper.acceptJsonFormatVisitor(MyPojo.class, visitor);
System.out.println(mapper.writeValueAsString(visitor.getExample()));
}
private static class MyJsonFormatVisitorWrapper implements JsonFormatVisitorWrapper {
private final ObjectMapper mapper;
private final Map<String, Object> example = new LinkedHashMap<>();
private MyJsonFormatVisitorWrapper(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public JsonObjectFormatVisitor expectObjectFormat(JavaType type) throws JsonMappingException {
return new JsonObjectFormatVisitor.Base() {
#Override
public void optionalProperty(BeanProperty prop) throws JsonMappingException {
Example a = prop.getAnnotation(Example.class);
if (a != null) {
example.put(prop.getName(), mapper.convertValue(a.value(), prop.getType()));
} else {
example.put(prop.getName(), prop.getType().toCanonical());
}
}
};
}
#Override
public JsonArrayFormatVisitor expectArrayFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public JsonStringFormatVisitor expectStringFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public JsonNumberFormatVisitor expectNumberFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public JsonIntegerFormatVisitor expectIntegerFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public JsonBooleanFormatVisitor expectBooleanFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public JsonNullFormatVisitor expectNullFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public JsonAnyFormatVisitor expectAnyFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public JsonMapFormatVisitor expectMapFormat(JavaType type) throws JsonMappingException {
return null;
}
#Override
public SerializerProvider getProvider() {
return null;
}
#Override
public void setProvider(SerializerProvider provider) {
}
public Map<String,Object> getExample() {
return example;
}
}
}
Output:
{"name":"Bob","myInt":42,"noExample":"java.lang.String"}