JACKSON JSON deserialization issue with overrloaded setter - java

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

Related

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.

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.

Is it possible to use jaxb annotations to navigate objects after they are unmarshalled?

I have a fairly complex XML schema and I use hyperjaxb3 to generate pojo's with annotations for. There are times when I have the parent object and would like to check the value of a child object that may be 8 or 9 children deep. Is there anyway to use jaxb, or another tool, to get a list of child objects of a specific class based on jaxb annotations?
I could write a recursive function to search all children for an instance of a class but that would be less than ideal. Any advise would be appreciated, thanks.
You don't have to write the code to walk the object tree yourself — there are a few out there already (jdereg's java-util for example), and the JAXBIntrospector will find objects annotated with XmlRootElement for you. There would be a little more work required if you're looking for other annotations, but not much.
For example:
public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
JAXBIntrospector is = jc.createJAXBIntrospector();
// Found objects
List<Foo.Bar> bars = new ArrayList<>();
// Object name to look for
QName barName = new QName("", "bar");
// Unmarshalled root object to introspect
Foo target = new Foo(new Foo.Bar());
// Walk the object graph looking for "bar" elements
Traverser.traverse(target, o -> {
if (barName.equals(is.getElementName(o))) {
bars.add((Foo.Bar) JAXBIntrospector.getValue(o));
}
});
System.out.println(bars);
}
//
// Some test objects
//
#XmlRootElement(name = "foo")
public class Foo {
private Bar bar;
public Foo() { }
public Foo(Bar bar) {
this.bar = bar;
}
#XmlElement(name="bar")
public Bar getBar() {
return bar;
}
public void setBar(Bar bar) {
this.bar = bar;
}
#XmlRootElement(name="bar")
public static class Bar {
String name = "kate";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Bar{name='" + name + '\'' + '}';
}
}
}

Repeat json field with different name

i have a POJO mapped which i serialize using Jackson
public class Foo{
private String bar;
// public setter and getter for bar
}
it serializes to
{bar:"value"}
is there a jackson annotation to get another field in the JSON with the same value but with a different alias name, something like
{bar:"value", another_bar:"value"}
This should work for duplicating the value, though why you'd want to waste space like that is puzzling:
public class Foo {
private String bar;
#JsonProperty
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
#JsonProperty("another_bar")
public String getAnotherBar() {
return this.bar;
}
}

Prevent writing default attribute values JAXB

I am trying to write the class in order to write an element with attributes in JAXB. In this XML there are some default values whether they be Strings, ints, or custom class types.
The following cut down example:
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
// I want to not write this to the xml when it is 0
#XmlAttribute(name = "num")
private int number;
// I want to not write this when it is "default"
#XmlAttribute(name = "str")
private String str;
}
As per JAXB Avoid saving default values
I know if I want to not write the String I can modify the getters/setters to write null and read in the default value if it reads in null.
However, with the int I am not sure what to do as it will always have the value 0 unless it is specifically changed.
Is there a nicer way to do this? I could change the internal data types to String and then cast it whenever it is needed but that's a bit messy.
You could do the following by changing the fields to be the object types by default null values do not appear in the XML representation) and putting some logic in the getters:
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
#XmlAttribute(name = "num")
private Integer number;
#XmlAttribute
private String str;
public int getNumber() {
if(null == number) {
return 0;
} else {
return number;
}
}
public void setNumber(int number) {
this.number = number;
}
public String getStr() {
if(null == str) {
return "default";
} else {
return str;
}
}
public void setStr(String str) {
this.str = str;
}
}
Allowing the Property to be Unset
If you want to allow the set operation to return a property to its default state then you need to add logic in the set method.
public void setNumber(int number) {
if(0 == number) {
this.number = null;
} else {
this.number = number;
}
}
Alternatively you could offer an unset method:
public void unsetNumber() {
this.number = null;
}
Allowing a Set to null
If you want to allow the str property to be set to null so that the get method will return null and not "default" then you can maintain a flag to track if it has been set:
private strSet = false;
public String getStr() {
if(null == str && !strSet) {
return "default";
} else {
return str;
}
}
public void setStr(String str) {
this.str = str;
this.strSet = true;
}
UPDATE
Blaise, don't you think that the solution is pretty verbose?
Yes
I mean that such use case should be probably supported by framework.
For example using annotation like #DefaultValue.
How JAXB Supports Default Values Today
If a node is absent from the XML then a set is not performed on the corresponding field/property in the Java Object. This means whatever value you have initialized the property to be is still there. On a marshal since the value is populated it will be marshalled out.
What is Really Being Asked For
What is really being asked for is to not marshal the field/property when it has the default value. In this way you want the marshal behaviour to be the same for null and default values. This introduces some problems to be solved:
How do you now marshal null to XML? By default is it still marshalled as a missing node?
Does a mechanism need to be provided to distinguish between the property being the default value (not present in the XML) and having been set to the same value as the default (present in the XML)?
What Are People Doing Today?
Generally for this use case people would just change the int property to Integer and have null be the default. I haven't encountered someone asking for this behaviour for a String before.
Use Integer instead of primitive int. Replace all primitive types with their object counterparts, then you can use NULL.
As per the string default value, use and modify the getter
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "FIELD")
public class NullAttrs {
private Integer number;
private String str;
public void setNumber(Integer number) {
this.number = number;
}
#XmlAttribute(name = "num")
public Integer getNumber() {
return number;
}
public void setStr(String str) {
this.str = str;
}
#XmlAttribute(name = "str")
public String getStr() {
if (str != null && str.equalsIgnoreCase("default"))
return null;
else if (str == null)
return "default";
else
return str;
}
public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(NullAttrs.class);
NullAttrs root = new NullAttrs();
root.setNumber(null);
root.setStr("default");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Result in this case would be, empty FIELD:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<FIELD/>
You can change to Integer
private Integer number;
Then the value of the object will be null when not instantiated.
Though it's not as terse as one would wish, one can create XmlAdapters to avoid marshalling the default values.
The use case is like this:
#XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
#XmlAttribute(name = "num")
#XmlJavaTypeAdapter(value = IntegerZero.class, type = int.class)
public int number;
#XmlAttribute(name = "str")
#XmlJavaTypeAdapter(StringDefault.class)
public String str = "default";
}
And here are adapters.
IntegerZero:
public class IntegerZero extends DefaultValue<Integer>
{
public Integer defaultValue() { return 0; }
}
StringDefault:
public class StringDefault extends DefaultValue<String>
{
public String defaultValue() { return "default"; }
}
DefaultValueAdapter:
public class DefaultValue<T> extends XmlAdapter<T, T>
{
public T defaultValue() { return null; }
public T marshal(T value) throws Exception
{
return (value == null) || value.equals(defaultValue()) ? null : value;
}
public T unmarshal(T value) throws Exception
{
return value;
}
}
With small number of different default values this approach works well.
I find the solution using custom getters/setters or adapters annoyingly verbose, so I went for a different solution: a marshaller that checks values and nulls them out if they are at default.
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Set;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.helpers.AbstractMarshallerImpl;
import javax.xml.transform.Result;
import com.google.common.collect.ImmutableSet;
class MyJaxbMarshaller extends AbstractMarshallerImpl {
/** See https://docs.oracle.com/cd/E13222_01/wls/docs103/webserv/data_types.html#wp221620 */
private static final Set<String> SUPPORTED_BASIC_TYPES = ImmutableSet.of(
"boolean", "java.lang.Boolean", "byte", "java.lang.Byte", "double", "java.lang.Double",
"float", "java.lang.Float", "long", "java.lang.Long", "int", "java.lang.Integer",
"javax.activation.DataHandler", "java.awt.Image", "java.lang.String",
"java.math.BigInteger", "java.math.BigDecimal", "java.net.URI", "java.util.Calendar",
"java.util.Date", "java.util.UUID", "javax.xml.datatype.XMLGregorianCalendar",
"javax.xml.datatype.Duration", "javax.xml.namespace.QName",
"javax.xml.transform.Source", "short", "java.lang.Short");
private final Marshaller delegate;
MyJaxbMarshaller(Marshaller delegate) {
this.delegate = delegate;
}
#Override
public void setProperty(String name, Object value) throws PropertyException {
super.setProperty(name, value);
delegate.setProperty(name, value);
}
#Override
public void marshal(Object jaxbElement, Result result) throws JAXBException {
try {
delegate.marshal(clearDefaults(jaxbElement), result);
} catch (ReflectiveOperationException ex) {
throw new JAXBException(ex);
}
}
private Object clearDefaults(Object element) throws ReflectiveOperationException {
if (element instanceof Collection) {
return clearDefaultsFromCollection((Collection<?>) element);
}
Class<?> clazz = element.getClass();
if (isSupportedBasicType(clazz)) {
return element;
}
Object adjusted = clazz.getConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
copyOrRemove(field, element, adjusted);
}
return adjusted;
}
private Object clearDefaultsFromCollection(Collection<?> collection)
throws ReflectiveOperationException {
#SuppressWarnings("unchecked")
Collection<Object> result = collection.getClass().getConstructor().newInstance();
for (Object element : collection) {
result.add(clearDefaults(element));
}
return result;
}
private static boolean isSupportedBasicType(Class<?> clazz) {
return SUPPORTED_BASIC_TYPES.contains(clazz.getName());
}
private void copyOrRemove(Field field, Object element, Object adjusted)
throws ReflectiveOperationException {
Object value = field.get(element);
if (value != null) {
if (value.equals(field.get(adjusted))) {
value = null;
} else {
value = clearDefaults(value);
}
}
field.set(adjusted, value);
}
}
This works with classes like
#XmlRootElement
public class Foo {
#XmlAttribute public Integer intAttr = 0;
#XmlAttribute public String strAttr = "default";
}
You can make this more flexible if you want, e.g. you can use an annotation to mark attributes you want to omit when they're at default, or extend the class to be aware of things like #XmlTransient or method accessors (neither of which is an issue in my project right now).
The price you pay for the simplicity of your binding classes is that the marshaller is going to create a deep copy of the object you're about to marshal, and make lots of comparisons to defaults to determine what to null out. So if runtime performance is an issue for you, this might be a no-go.

Categories