How to ignore Jackson annotations? - java

I've got two classes:
public class Bar {
private String identifier;
private String otherStuff;
public Bar(){}
public Bar(String identifier, String otherStuff) {
this.identifier = identifier;
this.otherStuff = otherStuff;
}
// Getters and Setters
}
and
public class Foo {
private String foo;
#JsonSerialize(using=BarsMapSerializer.class)
#JsonDeserialize(using=BarsMapDeserializer.class)
private Map<String, Bar> barsMap;
public Foo(){}
public Foo(String foo, Map<String, Bar> barsMap) {
this.foo = foo;
this.barsMap = barsMap;
}
// Getters and Setters
}
When I sserialize Foo with code:
public static void main(String[] args) throws Exception {
Map<String, Bar> barsMap = new LinkedHashMap<>();
barsMap.put("b1", new Bar("bar1", "nevermind1"));
barsMap.put("b2", new Bar("bar2", "nevermind2"));
barsMap.put("b3", new Bar("bar3", "nevermind3"));
Foo foo = new Foo("foo", barsMap);
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(foo);
System.out.println(jsonString);
}
the otput is:
{"foo":"foo","barsMap":{"b1":"bar1","b2":"bar2","b3":"bar3"}}
For most cases it's ok, but in some cases I want to have full Bar object in my json, like bellow:
{"foo":"foo","barsMap":{"b1":{"identifier":"bar1", "otherStuff":"nevermind1"},"b2":{"identifier":"bar2", "otherStuff":"nevermind2"},"b3":{"identifier":"bar3", "otherStuff":nevermind3"}}}
Is it possible to achieve this without writing custom serializer?
I know that I can add annotation using mix-in mechanism, but basically I need to ignore existing one in some cases.

I've resolved my problem using mix-in mechanism.
public interface FooMixin {
#JsonSerialize
Map<String, Bar> getBarsMap();
#JsonDeserialize
void setBarsMap(Map<String, Bar> barsMap);
}
With this interface mixed in, class Foo is serialized as with default serializer.
As You can see You need to add JsonSerialize / JsonDeserialize annotations without specify any class.
Below code shows usage of this interface:
mapper = new ObjectMapper();
mapper.addMixInAnnotations(Foo.class, FooMixin.class);
jsonString = mapper.writeValueAsString(foo);
System.out.println(jsonString);

Related

How to deserialize extended class using Jackson to include Base class fields

In given class Base which is extended by Ext class. Serialization works perfect but issue is when trying to deserialize the serialized string back to Ext class. I want to deserialize back to Ext class including the all the Base class fields.
#Data, #NonFinal, #Value are all lombok annotations.
#Data
public class Base {
private String foo;
public Base( String foo) {
this.foo = foo;
}
}
#Value
#NonFinal
public class Ext extends Base {
private String bar;
public Ext(String foo, String bar) {
super(foo);
this.bar = bar;
}
}
Method to Deserialize
#Test
void shouldDeserialize() throws IOException {
ObjectMapper mapper = new ObjectMapper();
Ext ext = new Ext("foo", "bar");
String serializedExt = mapper.writeValueAsString(ext);
System.out.println(serializedExt); // {"foo":"foo","bar":"bar"}
// Throws err
base = mapper.readValue(serializedExt, Ext.class);
}
Error: com.fasterxml.jackson.databind.exc.InvalidDefinitionException:Cannot construct instance of ..Inhertence.Ext(no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (String)"{"foo":"foo","bar":"bar"}"; line: 1, column: 2]
The error message is indicative : in your class the default constructor is not present and you haven't annotated its constructor with the JsonCreator annotation. You can deserialize your class annotating its constructor :
#Value
#NonFinal
public class Ext extends Base {
private String bar;
#JsonCreator
public Ext(#JsonProperty("foo") String foo, #JsonProperty("bar") String bar) {
super(foo);
this.bar = bar;
}
}

Toggle JSON serializers for different endpoints

In Jersey's endpoints I want to return same DTO but serialise it differently by using different serialisers: different Date formats needed.
public class Foo {
private Date foo;
public Foo() {
this.foo = new Date();
}
public Date getFoo() {
return foo;
}
public void setFoo(Date foo){
this.foo = foo;
}
}
public class MyEndpointsUnix {
#GET
#Path("/dateAsUnix")
public Foo getDateAsUnix() {
return new Foo();
}
}
public class MyEndpointsUTC {
#GET
#Path("/dateAsUTC")
public Foo getdateAsUTC() {
return new Foo();
}
}
I suppose it should be possible to change serialisers for response manually.
From OOP point of view we can create new class for every kind of view:
class UnixFoo extends Foo {
private Foo foo;
public UnixFoo(Foo foo) {
this.foo = foo;
}
#JsonFormat(pattern = "yyyy-MM-dd")
#Override
public Date getFoo() {
return foo.getFoo();
}
// other getters
}
and in our controller we can:
public class MyEndpointsUnix {
#GET
#Path("/dateAsUnix")
public Foo getDateAsUnix() {
return new UnixFoo(new Foo());
}
}
Of course this solution has a downside that we need to copy our DTO classes. To avoid that we can use Jackson MixIn Annotation. To do that we should create new interface:
interface UnixFooMixIn {
#JsonFormat(pattern = "yyyy-MM-dd")
Date getFoo();
}
and enrich ObjectMapper with it:
public class MyEndpointsUnix {
#GET
#Path("/dateAsUnix")
public String getDateAsUnix() {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.addMixIn(Foo.class, UtcFooMixIn.class);
return mapper.writeValueAsString(new Foo());
}
}
In this case we need to change our method signature and return String. Also we can create this ObjectMapper once and use it as singleton. For each kind of view we need to define new interface and new ObjectMapper instance.

Jackson - deserialize using Builder without annotation

Is it possible to use Jackson to deserialize a value class (final, no setters) that only has an all args constructor and a Builder? I can't use the JsonDeserialize and JsonPOJOBuilder since I am trying to deserialize a model defined in a client library, so I cannot add the annotations. Can I specify the builder to use another way?
You can try using MixIn.
I have created one sample for your use case:
Original class:
final class Sample {
final int id;
Sample(int id) {
this.id = id;
}
}
MixIn (provide non-args constructor with same args):
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
abstract class SampleMixin {
#JsonCreator
public SampleMixin(#JsonProperty("id") int id) {
}
}
Deserilaization:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Sample.class, SampleMixin.class);
Sample sample = mapper.readValue(json, Sample.class);
You can. Builder must meet certain requirements. For instance its methods for fields must have certain prefix, like "with" or "set".
Here is DTO class and its builder without any jackson annotations:
public class DtoBuilderWithFinalFieldsWithoutJackson {
public final String stringValue;
private DtoBuilderWithFinalFieldsWithoutJackson(final String stringValue){
this.stringValue = stringValue;
}
public static Builder builder(){
return new Builder();
}
public static class Builder {
private String stringValue;
public Builder withStringValue(String stringValue) {
this.stringValue = stringValue;
return this;
}
public DtoBuilderWithFinalFieldsWithoutJackson build() {
return new DtoBuilderWithFinalFieldsWithoutJackson(stringValue);
}
}
}
Without any additional effort with a default custom object you can serialize this dto object. You are responsible for instance creation. Jackson only needs to access fields. In our case this is a public field.
In case DTO is used for deserialization, you need to customize custom object:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
#Override
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
//set analogue of: #JsonDeserialize(builder = DtoBuilderWithFinalFieldsWithoutJackson.Builder.class)
if (DtoBuilderWithFinalFieldsWithoutJackson.class.equals(ac.getRawType())) {
return DtoBuilderWithFinalFieldsWithoutJackson.Builder.class;
} else {
return super.findPOJOBuilder(ac);
}
}
#Override
public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) {
if (DtoBuilderWithFinalFieldsWithoutJackson.class.equals(ac.getRawType())) {
//both build and with - are default in this case:
//set analogue of #JsonPOJOBuilder(buildMethodName = "build", withPrefix = "with")
return new JsonPOJOBuilder.Value("build", "with");
} else {
return super.findPOJOBuilderConfig(ac);
}
}
});
and use this customized CustomObject in your implementations. Here is a test and full example can be found here: DtoBuilderWithFinalFieldsWithoutJackson test

How to tell Jackson to ignore a field given that there is no condition on the field being null?

[Not a repeat].
I need the exact opposite of JsonIgnore. In my use case there is just one field that I need to add in serialization and all others are to be ignored. I've explored #JsonSerialize, #JsonInclude and #JsonProperty but none work as required by me.
Is there a way to achieve this or I'll need to mark the fields to be ignored with #JsonIgnore?
By default, Jackson will auto-detect all getters in your object and serialize their values. Jackson distinguished between "normal" getters (starting with "get") and "is-getters" (starting with "is", for boolean values). You can disable auto-detection for both entirely by configuring the ObjectMapper like this:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.AUTO_DETECT_GETTERS);
mapper.disable(MapperFeature.AUTO_DETECT_IS_GETTERS);
Alternatively, you can disable the auto-detection on a per class basis using #JsonAutoDetect. Annotate the fields or getters you actually do want to serialize with #JsonProperty.
#JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
public class MyObject {
private String foo;
#JsonProperty("bar")
private String bar;
private boolean fubar;
public MyObject(String foo, String bar, boolean fubar) {
this.foo = foo;
this.bar = bar;
this.fubar = fubar;
}
public String getFoo() {
return foo;
}
public String getBar() {
return bar;
}
public boolean isFubar() {
return fubar;
}
}
Serializing MyObject like this:
mapper.writeValueAsString(new MyObject("foo", "bar", true));
will result in the following JSON
{"bar":"bar"}
you can try this #JsonIgnoreProperties("propertyName") on the top of variable..

JACKSON JSON deserialization issue with overrloaded setter

From several reasons we have to make for developers convenience be able to set the one reference via overloaded setters ( this due it is modelled as oneOf attribute).
I would expect that depending on the JSON schema the polymorphic (oneOf) property would have the deserialized reference to object of FooType or BarType, ....
depending on the JSON schema.
I was hoping since FooType , BarType follow bean convention they can be easily determined like it happens for JacksonFeature in JAXRS ....
In my dummy test it seems to not work as described below :
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.READ_ENUMS_USING_TO_STRING, true);
mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
mapper.readValue(SCHEMA, SimplePojo.class);
The issue is that the mapper crashes, and JSON schema(SCH1) can not be deserialized to POJO
The JSON schema (SCH1)
{
"dummy" : {
"bar" : "bar",
"baz" : 10
},
"other" : {
"foo" : "hi there"
},
"simple" : "simple"
}
The sub element types dummy, other look like :
public class BarType {
private String bar;
private Number baz;
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
public Number getBaz() {
return baz;
}
public void setBaz(Number baz) {
this.baz = baz;
}
and
public class FooType {
private Object foo;
public Object getFoo() {
return foo;
}
public void setFoo(Object foo) {
this.foo = foo;
}
The top level POJO ( i skipped some part )
public class SimplePojo {
private String simpleField;
private Object dummyField;
private Object otherField;
public String getSimple() {
return simpleField;
}
public void setSimple(String simple) {
this.simpleField = simple;
}
...
public void setDummy(final FooType dummy) {
this.dummyField = dummy;
}
public void setDummy(final BarType dummy) {
this.dummyField = dummy;
}
public void setDummy(final String dummy) {
this.dummyField = dummy;
}
the issue is that i can not deserialize correctly the schema (SCH1), instead I receive the :
com.fasterxml.jackson.databind.JsonMappingException: Conflicting setter definitions for property "dummy": com.hybris.api.poc.SimplePojo#setDummy(1 params) vs com.hybris.api.poc.SimplePojo#setDummy(1 params)
I was trying to use the #JsonCreator, and #JsonDeserialize but no luck it seems that i can not have two (non primitive) override setters
#JsonDeserialize( builder = FooType.FooTypeBuilder.class)
#JsonCreator
public void setDummy(final FooType dummy) {
this.dummyField = dummy;
}
/**
* Type specific setter for #dummy;
*
* #param dummy a reference to be set for #dummy
*/
#JsonDeserialize( builder = BarType.BarTypeBuilder.class )
#JsonCreator
public void setDummy(final BarType dummy) {
this.dummyField = dummy;
}
Can you hint me where I should the solution or am i breaking some principal concept ?
I do think you are breaking some principal concept there. For this type of scenario, having a base abstract class with JsonTypeInfo and JsonSubTypes annotations to describe your sub-object would probably be preferred. If you absolutely need the ability to set the three types via setDummy(...), would this work for you?
Replace:
public void setDummy(final FooType dummy) {
this.dummyField = dummy;
}
public void setDummy(final BarType dummy) {
this.dummyField = dummy;
}
public void setDummy(final String dummy) {
this.dummyField = dummy;
}
With:
#JsonDeserialize( using = DummyDeserializer.class )
public void setDummy(final Object dummy) {
// If you really need to restrict to the three types, throw exception here
if (! (dummy instanceof FooType || dummy instanceof BarType || dummy instanceof String) ) {
throw new Exception("Cannot setDummy dummy!");
}
this.dummyField = dummy;
}
This would require you to do the deserialization manually for all three classes in your DummyBuilder, but should solve your multi-setters problem. I've not tried to implement this, but think it works.
No, without inheritance structure Jackson has no way of automatically determining intended type during deserialization. If they did share the same base type, you could use #JsonTypeInfo to indicate how type id is included (usually as a property); and have a single setter (or creator property).
Otherwise you can not have conflicting setters (i.e. more than one with types that are not related to each other by sub-typing).

Categories