I want to include only fields in my classes that have my custom annotation #MyInclude but Jackson ends up ignoring everything. What am I doing wrong?
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.setAnnotationIntrospector(new IgnoreIntrospector());
MyNestedObject nestedObject = new MyNestedObject("value1", "value2");
MyObject object = new MyObject();
object.setNestedObject(nestedObject);
String json = mapper.writeValueAsString(object); //This returns {}
}
public static class IgnoreIntrospector extends JacksonAnnotationIntrospector {
private static final long serialVersionUID = -3951086067314107368L;
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return !m.hasAnnotation(MyInclude.class) || super.hasIgnoreMarker(m);
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MyObject {
#MyInclude
private MyNestedObject nestedObject;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MyNestedObject {
#MyInclude
private String value1;
private String value2;
}
mapper.writeValueAsString(object) is returning {} but it should return NestedObject with value1 populated instead (ignoring value2).
If I update my IgnoreIntrospector.hasIgnoreMarker () to just super.hasIgnoreMarker(m) then everything would be included in the json string.
The IgnoreIntrospector alone wasn't enough. Since my custom annotations were only on fields, I needed to disable all visibility:
mapper.setAnnotationIntrospector(new IgnoreIntrospector());
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
mapper.setVisibility(mapper.getDeserializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
And updated my IgnoreIntrospector:
public static class IgnoreIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return m instanceof AnnotatedField && _findAnnotation(m, MyInclude.class) == null;
}
}
Alternatively, override both hasIgnoreMarker() and findNameForSerialization():
JsonMapper jsonMapper = JsonMapper.builder()
.annotationIntrospector(new JacksonAnnotationIntrospector()
{
#Override
public boolean hasIgnoreMarker(AnnotatedMember m)
{
return m.hasAnnotation(CustomIgnore.class) || super.hasIgnoreMarker(m);
}
#Override
public PropertyName findNameForSerialization(Annotated a)
{
if(a.hasAnnotation(CustomIgnore.class)) return null;
return super.findNameForSerialization(a);
}
})
.build();
Related
I'm creating a common class to standardize my JSON structure as written below,
public class Wrapper<T> {
private SoaHeader soaHeader;
private T payload;
}
public class PayloadFoo {
private String foo;
}
public class PayloadBar {
private String bar;
}
public class main(){
var foo = new Wrapper<PayloadFoo>();
var bar = new Wrapper<PayloadBar>();
}
Then later the expected JSON result for both foo and bar are
{
"foo": {
"soaHeader": {},
"payload": {
"foo": ""
}
}
}
and
{
"bar": {
"soaHeader": {},
"payload": {
"bar": ""
}
}
}
Can Jackson do such task by put either #JsonTypeName or #JsonRootName annotation on the PayloadFoo and PayloadBar classes? or any suggestion how can I achieve this? Thankyou
Jackson can handle this by using the #JsonTypeName annotation on the PayloadFoo and PayloadBar classes and the #JsonTypeInfo annotation on the Wrapper class.
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
public class JsonSubTypesExample {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
public static void main(String[] args) throws JsonProcessingException {
Wrapper<PayloadFoo> payloadFooWrapper = new Wrapper<>(new SoaHeader(), new PayloadFoo("foo"));
Wrapper<PayloadBar> payloadBarWrapper = new Wrapper<>(new SoaHeader(), new PayloadBar("bar"));
System.out.println(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(payloadFooWrapper));
System.out.println(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(payloadBarWrapper));
}
#JsonSubTypes({
#JsonSubTypes.Type(value = PayloadFoo.class, name = "foo"),
#JsonSubTypes.Type(value = PayloadBar.class, name = "bar"),
})
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class Wrapper<T> {
private SoaHeader soaHeader;
private T payload;
}
#JsonTypeName("foo")
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class PayloadFoo {
private String foo;
}
#JsonTypeName("bar")
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class PayloadBar {
private String bar;
}
#Data
#ToString
public static class SoaHeader {
}
}
Class A looks like this:
#EqualsAndHashCode(callSuper = true)
#Data
#AllArgsConstructor
public final class A extends TreeSet<B> {
private final a;
private b;
private c;
public A(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
}
}
Class B:
#Data
#AllArgsConstructor
#EqualsAndHashCode
#ToString
public final class B {
private final int x;
private final double y;
}
When I serialize a class A object using Jackson:
jsonString = objectMapper.writeValueAsString(class_a_object);
I get a json Array like this:
[
{
"x": 3,
"y": 3.23
},
{
"x": 4,
"y": 2.12
},...
]
but the member variables a,b,c are missing. Is there a way I can include them into the json string?
Jackson recognises class A as a collection and register CollectionSerializer to serialise A's instances. We can modify default serialiser and provide custom serialiser. We can use BeanSerializerModifier to do that and reuse collection serialiser in custom implementation. To generate valid JSON you need to provide property name for set values.
Example:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.type.CollectionType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.File;
import java.io.IOException;
import java.util.TreeSet;
public class ModifyCollectionSerializerApp {
public static void main(String[] args) throws IOException {
A a = new A(1, 2);
a.add(new B(22, 2.2));
a.add(new B(33, 3.3));
SimpleModule aModule = new SimpleModule();
aModule.setSerializerModifier(new ABeanSerializerModifier());
JsonMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.addModule(aModule)
.build();
String json = mapper.writeValueAsString(a);
System.out.println(json);
}
}
class ABeanSerializerModifier extends BeanSerializerModifier {
#Override
public JsonSerializer<?> modifyCollectionSerializer(SerializationConfig config, CollectionType valueType, BeanDescription beanDesc, JsonSerializer<?> serializer) {
return new AJsonSerializer(serializer);
}
}
class AJsonSerializer extends JsonSerializer<A> {
private final JsonSerializer valuesSerializer;
AJsonSerializer(JsonSerializer valuesSerializer) {
this.valuesSerializer = valuesSerializer;
}
#Override
public void serialize(A value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeNumberField("a", value.getA());
gen.writeNumberField("b", value.getB());
gen.writeFieldName("values");
valuesSerializer.serialize(value, gen, serializers);
gen.writeEndObject();
}
}
#EqualsAndHashCode(callSuper = true)
#Data
#AllArgsConstructor
class A extends TreeSet<B> {
private final int a;
private final int b;
}
#Data
#AllArgsConstructor
#EqualsAndHashCode
#ToString
class B implements Comparable<B> {
private final int x;
private final double y;
#Override
public int compareTo(B o) {
return this.x - o.x;
}
}
Above code prints:
{
"a" : 1,
"b" : 2,
"values" : [ {
"x" : 22,
"y" : 2.2
}, {
"x" : 33,
"y" : 3.3
} ]
}
So the problem is the next:
I have POJO like:
#Data
#Accessors(chain = true)
#JsonInclude(Include.NON_NULL)
public class TestPOJO {
private Long id;
private String name;
private JsonNode jsonNode;
Also I have json like
{
"id":1
"name": "foo"
"jsonNode":null
}
When I try deserialize the last one by the
ObjectMapper objectMapper = new ObjectMapper();
TestPOJO testPojo = objectMapper.readValue(<json String>, TestPOJO.class);
I get testPojo object where jsonNode field is NullNode, but I need in testPojo == null
How I can fix it?
Add a class that extends JsonDeserializer<JsonNode> and that returns null if parser.getText() is null:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
public class JsonNodeDeserializer extends JsonDeserializer<JsonNode> {
#Override
public JsonNode deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
String value = parser.getText();
if(value == null) {
return null;
}
return (JsonNode) context.findRootValueDeserializer(context.constructType(JsonNode.class)).deserialize(parser, context);
}
}
Then annotate the jsonNode attribute with #JsonDeserialize(using = JsonNodeDeserializer.class) to tell Jackson to use your custom deserializer:
#Data
#Accessors(chain = true)
#JsonInclude(Include.NON_NULL)
public class TestPOJO {
private Long id;
private String name;
#JsonDeserialize(using = JsonNodeDeserializer.class)
private JsonNode jsonNode;
// getters and setters
}
I am consuming a "RESTful" service (via RestTemplate) that produces JSON as follows:
{
"id": "abcd1234",
"name": "test",
"connections": {
"default": "http://foo.com/api/",
"dev": "http://dev.foo.com/api/v2"
},
"settings": {
"foo": "{\n \"fooId\": 1, \"token\": \"abc\"}",
"bar": "{\"barId\": 2, \"accountId\": \"d7cj3\"}"
}
}
Note the settings.foo and settings.bar values, which cause issues on deserialization. I would like to be able to deserialize into objects (e.g., settings.getFoo().getFooId(), settings.getFoo().getToken()).
I was able to solve this specifically for an instance of Foo with a custom deserializer:
public class FooDeserializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
String text = node.toString();
String trimmed = text.substring(1, text.length() - 1);
trimmed = trimmed.replace("\\", "");
trimmed = trimmed.replace("\n", "");
ObjectMapper mapper = new ObjectMapper();
JsonNode obj = mapper.readTree(trimmed);
Foo result = mapper.convertValue(obj, Foo.class);
return result;
}
}
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class Settings {
#JsonDeserialize(using = FooDeserializer.class)
private Foo foo;
private Bar bar;
}
However, now if I want to deserialize settings.bar, I need to implement another custom deserializer. So I implemented a generic deserializer as follows:
public class QuotedObjectDeserializer<T> extends JsonDeserializer<T> implements ContextualDeserializer {
private Class<?> targetType;
private ObjectMapper mapper;
public QuotedObjectDeserializer(Class<?> targetType, ObjectMapper mapper) {
this.targetType = targetType;
this.mapper = mapper;
}
#Override
public JsonDeserializer<T> createContextual(DeserializationContext context, BeanProperty property) {
this.targetType = property.getType().containedType(1).getRawClass();
return new QuotedObjectDeserializer<T>(this.targetType, this.mapper);
}
#Override
public T deserialize(JsonParser jp, DeserializationContext context) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
String text = node.toString();
String trimmed = text.substring(1, text.length() - 1);
trimmed = trimmed.replace("\\", "");
trimmed = trimmed.replace("\n", "");
JsonNode obj = this.mapper.readTree(trimmed);
return this.mapper.convertValue(obj, this.mapper.getTypeFactory().constructType(this.targetType));
}
}
Now I'm not sure how to actually use the deserializer, as annotating Settings.Foo with #JsonDeserialize(using = QuotedObjectDeserializer.class) obviously does not work.
Is there a way to annotate properties to use a generic custom deserializer? Or, perhaps more likely, is there a way to configure the default deserializers to handle the stringy objects returned in my example JSON?
Edit: The problem here is specifically deserializing settings.foo and settings.bar as objects. The JSON representation has these objects wrapped in quotes (and polluted with escape sequences), so they are deserialized as Strings.
Sorry about the length of the code here. There are plenty of shortcuts here (no encapsulation; added e to defaulte to avoid keyword etc.) but the intent is there
Model class:
package com.odwyer.rian.test;
import java.io.IOException;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Model {
public String id;
public String name;
public Connections connections;
public Settings settings;
public static class Connections {
public String defaulte;
public String dev;
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
public static class Foo {
public Foo () {}
#JsonCreator
public static Foo create(String str) throws JsonParseException, JsonMappingException, IOException {
return (new ObjectMapper()).readValue(str, Foo.class);
}
public Integer fooId;
public String token;
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
public static class Bar {
public Bar() {}
#JsonCreator
public static Bar create(String str) throws JsonParseException, JsonMappingException, IOException {
return (new ObjectMapper()).readValue(str, Bar.class);
}
public Integer barId;
public String accountId;
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
public static class Settings {
public Foo foo;
public Bar bar;
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
The caller:
package com.odwyer.rian.test;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestClass {
private static ObjectMapper objectMapper = new ObjectMapper();
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
Scanner file = new Scanner(new File("test.json"));
String jsonStr = file.useDelimiter("\\Z").next();
Model model = objectMapper.readValue(jsonStr, Model.class);
System.out.println(model.toString());
}
}
The result (too much hassle to format out but it is all there!):
com.odwyer.rian.test.Model#190083e[id=abcd1234,name=test,connections=com.odwyer.rian.test.Model$Connections#170d1f3f[defaulte=http://foo.com/api/,dev=http://dev.foo.com/api/v2],settings=com.odwyer.rian.test.Model$Settings#5e7e6ceb[foo=com.odwyer.rian.test.Model$Foo#3e20e8c4[fooId=1,token=abc],bar=com.odwyer.rian.test.Model$Bar#6291bbb9[barId=2,accountId=d7cj3]]]
The key, courtesy of Ted and his post (https://stackoverflow.com/a/8369322/2960707) is the #JsonCreator annotation
JsonIgnore annotation doesn't seem to work for me. Any ideas why?
public class JsonTest implements Serializable {
#JsonIgnore
private static JsonTest instance = null;
#JsonIgnore
private transient Set<String> set = new CopyOnWriteArraySet<String>();
private JsonTest() {}
#JsonIgnore
public static JsonTest getInstance() {
if (instance == null)
instance = new JsonTest();
return instance;
}
public void setSet(Set<String> set) {
this.set = set;
}
#JsonIgnore
public Set<String> getSet() {
return set;
}
public String toString() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
try {
return mapper.writeValueAsString(this);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
HashSet<String> set = new HashSet<String>();
set.add("test");
JsonTest.getInstance().setSet(set);
System.out.println(JsonTest.getInstance().toString());
}
}
Output: {"set":["test"]}
Transient means that that field will not be serialized. You do not need to add #JsonIgnore annotation there because of that field will be excluded anyway.
You can locate #JsonIgnore annotation at least in org.codehaus.jackson:jackson-mapper-asl:1.9.13 and com.fasterxml.jackson.core:jackson-annotations:2.4.3 (this is what I used). Where ObjectMapper is in jackson-mapper-asl artifact. The interesting part here is that if I use #JsonIgnore from jackson-annotations (com.fasterxml.jackson.annotation.JsonIgnore) -- it doesn't work ('set' is in response) even if I configure ObjectMapper to use only properties. Probably it is a bug in fasterxml implementation but I didn't find it.
So, it is working fine if you will use codehaus rather then fasterxml (I added configuration to use only fields):
import org.codehaus.jackson.annotate.JsonAutoDetect;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class JsonTest implements Serializable {
#JsonIgnore
private static JsonTest instance = null;
private transient Set<String> set = new CopyOnWriteArraySet<String>();
private JsonTest() {}
#JsonIgnore
public static JsonTest getInstance() {
if (instance == null)
instance = new JsonTest();
return instance;
}
public void setSet(Set<String> set) {
this.set = set;
}
#JsonIgnore
public Set<String> getSet() {
return set;
}
public String toString() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
mapper.setVisibilityChecker(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
try {
return mapper.writeValueAsString(this);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
HashSet<String> set = new HashSet<String>();
set.add("test");
JsonTest.getInstance().setSet(set);
System.out.println(JsonTest.getInstance().toString());
}
}