Toggle JSON serializers for different endpoints - java

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.

Related

How to serialize/deserialize (json) a class that has an attribute that's an interface

I'd like to serialize/deserialize (json) a class that contains an attribute that is an interface, but the underlying class doesn't have any attributes. The below is my most simplified case and my best attempt at what to do.
This throws an error when trying to deserialize No suitable constructor found for type [simple type, class com.example.Bar]: can not instantiate from JSON object (need to add/enable type information?) at [Source: java.io.StringReader#301ec38b; line: 1, column: 2]
public interface FooInterface {
String doThing();
}
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
#EqualsAndHashCode
public class Foo implements FooInterface {
#Override
public String doThing() {
return "foo";
}
}
#Getter
#Setter
#EqualsAndHashCode
public class Bar {
FooInterface foo;
public Bar(FooInterface foo) {
this.foo = foo;
}
}
#Test
public void fooTest() throws IOException {
Foo foo = new Foo();
Bar bar = new Bar(foo);
String serialized = new ObjectMapper().writeValueAsString(bar); // = {"foo":{}}
Bar deserialized = new ObjectMapper().readValue(serialized, Bar.class);
Assert.assertEquals(bar, deserialized);
}
Please add default constructor to class Bar and I guess your issue should be resolved.
#Getter
#Setter
#EqualsAndHashCode
public class Bar {
FooInterface foo;
public Bar() {}
public Bar(FooInterface foo) {
this.foo = foo;
}
}
Do let me know if this doesn't solve your problem, I will try to dig deeper.
As #Aditya mentioned I was missing the default constructor which was causing the error I was having, but then the new error led me to finding this question which was the crux of the problem that this question was asking about.
Looks like I misunderstood what the JsonAutoDetect annotation did. Below is the code that ended up working for me.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class),
})
public interface FooInterface {
String doThing();
}
#EqualsAndHashCode
public class Foo implements FooInterface {
#Override
public String doThing() {
return "foo";
}
}
#Getter
#Setter
#EqualsAndHashCode
public class Bar {
FooInterface foo;
public Bar() {}
public Bar(FooInterface foo) {
this.foo = foo;
}
}
#Test
public void fooTest() throws IOException {
Foo foo = new Foo();
Bar bar = new Bar(foo);
String serialized = new ObjectMapper().writeValueAsString(bar); // {"foo":{"type":"Foo"}}
Bar deserialized = new ObjectMapper().readValue(serialized, Bar.class);
Assert.assertEquals(bar, deserialized);
}

Java generics override static methods workaround

I'm working on a project that requires me to serialize and deserialize generic objects. The way I'm going about this, is defining an abstract class Serializer that implements a toBytes() and a static fromBytes(). All is well with this approach, as I can pass an object instance to a generic class Foo that expects a Serializer subclass, and I can ensure the object knows how to serialize and deserialize itself.
Now my question. Java serialization kinda sucks. I have multiple implementations I'd like to try swapping in and out, and ultimately I'd like the user to be able to decide the format. How would I go about changing the implementation details of Serializer? I know I can't override static methods, so how would I do this without decoupling Foo and Serializer and not being able to ensure my generic object has the appropriate toBytes() and fromBytes() method in Foo?
Here is code if anyone is confused:
public abstract class Serializer {
public static Serializer fromBytes(byte[] bytes) {
...
}
public byte[] toBytes() {
...
}
}
public class Foo<T extends Serializer> {
private T t;
public Foo(T t) {
this.t = t;
}
public void foo() {
t.toBytes(); //this will polymorph into the correct call because it's called on the object instance and not the Serializer class
}
public void bar(byte[] bytes) {
T.fromBytes(bytes); // I'd like to be able to override this method so I can use different implementations
}
}
I'm not sure if this is a good approach, but how about using Jackson library and serialize your object as a json node? for example:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = SoundFile.class, name = "sound"),
#Type(value = VideoFile.class, name = "video")
})
abstract class File{
private String id;
private String type;
#JsonCreator
public File(#JsonProperty("id") String id)
{
this.id=id;
}
public String getId() {return this.id;}
public abstract String getType();
}
class SoundFile extends File{
#JsonCreator
public SoundFile(#JsonProperty("id") String id) {
super(id);
}
#Override
public String getType() {
return "sound";
}
}
class VideoFile extends File{
#JsonCreator
public VideoFile(#JsonProperty("id") String id) {
super(id);
}
#Override
public String getType() {
return "video";
}
}
public class GenericApp {
public static void main(String[] args) {
ObjectMapper om = new ObjectMapper();
List<File> files = Arrays.asList(new VideoFile("1"),new SoundFile("2"));
//serialize
List<byte[]> fileSerialized = files.stream().map(file->{
try {
return om.writeValueAsBytes(file);
}catch(Exception e) {return null;}
}).collect(Collectors.toList());
//de-serialize
List<File> filesDeSerialized = fileSerialized.stream().map(bytes ->{
try {
return om.readValue(bytes, File.class);
}
catch(Exception e) {return null;}
}).collect(Collectors.toList());
filesDeSerialized.stream().forEach(file->{
System.out.println("id :"+file.getId()+" - "+file.getClass());
});
}
}
this would properly deserialize these objects and print:
id :1 - class com.dsncode.stackoverflow.VideoFile
id :2 - class com.dsncode.stackoverflow.SoundFile
however, you should define a #JsonTypeInfo and a #JsonSubType for all your sub-classes of your Generic Type. Because, by indicating this field, you will indicate to Jackson deserializer, which class should create for your generic type.

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

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).

How to ignore Jackson annotations?

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

Categories