Example data in POJO for JSON documentation - java

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"}

Related

Need MixIn Resolution for non-static Inner Class using ObjectMapper - Java 6

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();
}
}

Custom Jackson Deserialization of a Generic Abstract class

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.

How can I configure Jackson to get a serializer via a static getInstance() method?

Let's pretend I have a custom serializer defined like this;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
#SingletonSerializer // my custom annotation
public final class ThumbnailUrlSerializer extends JsonSerializer {
private static final ThumbnailUrlSerializer INSTANCE = new ThumbnailUrlSerializer();
public static ThumbnailUrlSerializer getInstance() {
return INSTANCE;
}
private ThumbnailUrlSerializer() {
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
final JsonStreamContext context = gen.getOutputContext();
if ("thumbnailUrl".equals(context.getCurrentName()) && value == null) {
gen.writeStringField(context.getCurrentName(), "FOO");
} else {
gen.writeStringField(context.getCurrentName(), (String) value);
}
}
}
A field is annotated to use this serializer:
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class Product {
private String primaryKey;
#JsonSerialize(using = ThumbnailUrlSerializer.class)
private String thumbnailUrl;
public String getPrimaryKey() {
return primaryKey;
}
public void setPrimaryKey(String primaryKey) {
this.primaryKey = primaryKey;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public void setThumbnailUrl(String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}
}
I think using a custom com.fasterxml.jackson.databind.ObjectMapper#setSerializerProvider or com.fasterxml.jackson.databind.ObjectMapper#setSerializerFactory it is possible to do this but I am not sure how.
Any ideas how can I do this?
Custom Serializer :
public class ThumbnailUrlSerializer extends StdSerializer<Product> {
private ThumbnailUrlSerializer(Class<Product> t) {
super(t);
}
private static final ThumbnailUrlSerializer INSTANCE = new ThumbnailUrlSerializer(Product.class);
public static ThumbnailUrlSerializer getInstance() {
return INSTANCE;
}
#Override
public void serialize(Product product, JsonGenerator gen, SerializerProvider sp) throws IOException {
final JsonStreamContext context = gen.getOutputContext();
if ("thumbnailUrl".equals(context.getCurrentName()) && product == null) {
gen.writeStringField(context.getCurrentName(), "FOO");
} else {
gen.writeStringField(context.getCurrentName(), (String) product.getThumbnailUrl());
}
}
}
Usage :
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Product.class, ThumbnailUrlSerializer.getInstance()); // singleton usage
mapper.registerModule(module);
String json = mapper.writeValueAsString(product);
System.out.println("output "+json);
I hope this helps !

How to apply a transformation on all values when deserializing Json with Jackson

I want to parse a JSON document with Jackson and apply some transformation on all nodes. For example, let's say that I want all values to be in uppercase after deserialization.
The actual use case is a bit more complex:
transformation is more complex, the transformer class need to be injected with some configuration, I'd like it to be a configureable instance
transformation has to happen on all properties, I'd like to be able to not add an annotation on each property of each class deserialized.
There are enough configuration options / hooks in Jackson, so I'm fairly sure that this is possible, I just can't find my way around.
The test below shows what I'm trying to achieve:
public class JsonValueFilterTest {
private ObjectMapper mapper;
#Before
public void setupObjectMapper() {
mapper = new ObjectMapper();
// TODO: configure mapper to upper case all values
}
#Test
public void printJson() throws IOException {
Entity myEntity = new Entity("myName");
mapper.writeValue(System.out, myEntity); // prints: {"name":"myName"}
}
#Test
public void valuesAreUpperCasedWhenLoaded() throws IOException {
Entity myEntity = mapper.readValue("{\"name\":\"myName\"}", Entity.class);
assertThat(myEntity.getName()).isEqualTo("MYNAME"); // fails
}
public static class Entity {
private final String name;
#JsonCreator
public Entity(#JsonProperty("name") String name) { this.name = name; }
public String getName() { return name; }
#Override
public String toString() { return "name='" + name + "'"; }
}
}
You can use converter for that simple case to not implement custom deserializer. I don't know why, but It's not working on the creator constructors, though. So you will have to use non-final fields.
public class JsonValueFilterTest {
private ObjectMapper mapper;
#BeforeTest
public void setupObjectMapper() {
mapper = new ObjectMapper();
}
#Test
public void printJson() throws IOException {
Entity myEntity = new Entity("myName");
mapper.writeValue(System.out, myEntity); // prints: {"name":"myName"}
}
#Test
public void valuesAreUpperCasedWhenLoaded() throws IOException {
Entity myEntity = mapper.readValue("{\"name\":\"myName\"}", Entity.class);
Assert.assertEquals(myEntity.getName(), "MYNAME"); // fails
}
public static class UpCaseConverter extends StdConverter<String, String> {
public String convert(String value) {
return value==null ? null : value.toUpperCase();
}
}
public static class Entity {
private String name;
public Entity() {}
public Entity(String name) {
this.name = name;
}
#JsonDeserialize(converter = UpCaseConverter.class)
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public String toString() {
return "name='" + name + "'";
}
}
}
My final solution (thanks to Alban):
configure the ObjectMapper with a custom JsonNodeFactory which transforms all text nodes
deserialize json to JsonNode (this will apply transformation)
convert the JsonNode to my custom class
public class JsonValueFilterTest {
private ObjectMapper mapper;
#Before
public void setupObjectMapper() {
mapper = new ObjectMapper();
mapper.setNodeFactory(new JsonNodeFactory() {
#Override
public TextNode textNode(String text) {
return super.textNode(text.toUpperCase());
}
});
}
#Test
public void printJson() throws IOException {
Entity myEntity = new Entity("myName");
mapper.writeValue(System.out, myEntity); // prints: {"name":"myName"}
}
#Test
public void valuesAreUpperCasedWhenLoaded() throws IOException {
JsonNode jsonNode = mapper.readTree("{\"name\":\"myName\"}");
Entity myEntity = mapper.treeToValue(jsonNode, Entity.class);
assertThat(myEntity.getName()).isEqualTo("MYNAME");
}
public static class Entity {
private final String name;
#JsonCreator
public Entity(#JsonProperty("name") String name) { this.name = name; }
public String getName() { return name; }
#Override
public String toString() { return "name='" + name + "'"; }
}
}

Excluding null fields in pojo response

I want to exclude null fields from a pojo
****TransactionHistoryBO Pojo**
package main.java.com.as.model;
import com.fasterxml.jackson.annotation.JsonInclude;
#JsonInclude(JsonInclude.Include.NON_NULL)
public class TransactionHistoryBO
{
private String processId;
private String dateTime;
private Integer status;
private Double pointsEarned;
private String productName;
private String receiptNumber;
public String getProcessId() {
return processId;
}
public void setProcessId(String processId) {
this.processId = processId;
}
public String getDateTime() {
return dateTime;
}
public void setDateTime(String dateTime) {
this.dateTime = dateTime;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Double getPointsEarned() {
return pointsEarned;
}
public void setPointsEarned(Double pointsEarned) {
this.pointsEarned = pointsEarned;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getReceiptNumber() {
return receiptNumber;
}
public void setReceiptNumber(String receiptNumber) {
this.receiptNumber = receiptNumber;
}
}
**
Transaction History Response pojo
public class TransactionHistoryResponse
{
private ArrayList<TransactionHistoryBO> transactions;
#JsonInclude(JsonInclude.Include.NON_NULL)
public ArrayList<TransactionHistoryBO> getTransactions() {
return transactions;
}
#JsonInclude(Include.NON_NULL)
public void setTransactions(ArrayList<TransactionHistoryBO> transactions) {
this.transactions = transactions;
}
}
Array list of type Transaction History BO is used in Transaction History Response pojo.This is the exact pojo that i am showing in response.I would like to exclude the fields with null values in Transaction History BO.
I tried with #JsonInclude(JsonInclude.Include.NON_NULL).It is not working..
Also tried with JsonSerialize,but it is deprecated.Jackson version used is 2.2.2.
Any help would be appreciated..please help..
#JsonInclude(JsonInclude.Include.NON_NULL)
public class TransactionHistoryBO { ... }
#JsonInclude(JsonInclude.Include.NON_NULL)
public class TransactionHistoryResponse { ... }
public class App {
public static void main(String... args) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
TransactionHistoryResponse thr = new TransactionHistoryResponse();
TransactionHistoryBO thbo = new TransactionHistoryBO();
thbo.setProductName("TEST");
thr.setTransactions(new ArrayList<TransactionHistoryBO>());
thr.getTransactions().add(thbo);
System.out.print(om.writerWithDefaultPrettyPrinter().writeValueAsString(thr));
}
}
Produces output :
{
"transactions" : [ {
"productName" : "TEST"
} ]
}
No other annotation is used. Just add #JsonInclude annotation to classes not properties.
UPDATE:
Add a custom JacksonJsonProvider to your application
#Provider
public class CustomJsonProvider extends ResteasyJackson2Provider {
#Override
public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
ObjectMapper mapper = locateMapper(type, mediaType);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
super.writeTo(value, type, genericType, annotations, mediaType, httpHeaders, entityStream);
}
}
Register this provider in your web.xml
<context-param>
<param-name>resteasy.providers</param-name>
<param-value>com.package.CustomJsonProvider</param-value>
</context-param>
Tested with and without this and it works.

Categories