How can I get Jackson to deserialize into my own Array implementation - java

Given my own array implementation MyArray<T>, how can I make it known to Jackson, so that it is able to deserialize from a JSON Array into MyArray<T>? So far I am only getting this exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of MyArray out of START_ARRAY token

As Dariusz mentioned, it's good to take advantage of the fact that Array class has constructor accepting normal array.
Look, if you use default serializer - your array serialized to JSON would look like:
{"items":["item1","item2"],"size":2,"ordered":true}
it's clearly a waste of space, unless you want size and ordered fields to be preserved.
I suggest you changing the way you serialize your object so that it would look more like normal array, on the other end - deserialization can build Array object again.
If you add following pair of serializer and deserializer:
SimpleModule module = new SimpleModule();
module.addDeserializer(Array.class, new StdDelegatingDeserializer<>(
new StdConverter<Object[], Array>() {
#Override
public Array convert(Object[] value) {
return new Array(value);
}
}));
module.addSerializer(Array.class, new StdDelegatingSerializer(
new StdConverter<Array, Object>() {
#Override
public Object convert(Array value) {
return value.toArray();
}
}));
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
you will have transparent conversion between these types

The Array class from libgdx has a constructor which accepts an array: public Array (T[] array).
Instead of trying to serialize libgdx array use a simple class with an array as a base for serialization/desrialization, and then create a libgdx array based on the deserialized data.
In general it is a good rule to serialize only POJO-type objects.
In short:
{
//serialize:
com.badlogic.gdx.utils.Array<MyObj> arr = ...;
MyObj[] myArr = arr.toArray();
MyCustomContainer cont = new MyCustomContainer(myArr);
String serializedData = mapper.writeValueAsString(cont);
// do sth with the data
}
{
//deserialize
MyCusomContainer cont = mapper.readValue(..., MyCustomContainer.class);
com.badlogic.gdx.utils.Array<MyObj> arr = new com.badlogic.gdx.utils.Array<MyObj>(cont.getArray());
// done!
}

One way to do it is to write a serializer like
import java.io.IOException;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.ser.std.SerializerBase;
public class MyArraySerializer extends SerializerBase<MyArray> {
protected MyArraySerializer() {
super(MyArray.class);
}
#Override
public void serialize(MyArray myArray, JsonGenerator gen, SerializerProvider p)
throws IOException, JsonGenerationException {
gen.writeStartArray();
Iterator<MyObject> it = myArray.iterator();
while (it.hasNext()) {
MyObject ob = it.next();
gen.writeObject(p);
if (it.hasNext()) {
gen.writeRaw(',');
}
}
gen.writeEndArray();
}
}
And a deserializer like
import java.io.IOException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
public class MyArrayDeserializer extends JsonDeserializer<MyArray> {
#Override
public MyArray deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException {
MyObject[] obs = parser.readValueAs(MyObject[].class);
return new MyArray(obs); //presuming you have a copy-constructor
}
}
Then annotate the property that holds such an array with #JsonSerialize(using = MyArraySerializer.class) #JsonDeserialize(using = MyArrayDeserializer.class).
If you use your array implementation directly, instead of inside a container class, this page has an example of how to register serialization handlers at run-time http://wiki.fasterxml.com/JacksonHowToCustomSerializers
I should note that in this answer I am using the Jackson 1.9 API and the 2.x may be slightly different. According to http://wiki.fasterxml.com/JacksonUpgradeFrom19To20 the most noticeable differences are the changes in package names and where some classes are located. Otherwise this code should be unaffected.

Related

How to deserialize generic List<T> with Jackson?

I've been using Jackson to serialize/deserialize objects for years and have always found it needlessly complicated to use TypeReference<T> to deserialize List etc. I created a simple helper function:
public static <T> TypeReference<List<T>> list() {
return new TypeReference<List<T>>(){}
}
With intended use:
List<Foo> foos = objectMapper.readValue(json, list());
And it works! Kind of. When inspecting through the debugger, rather than a list of Foo, it is rather a list of LinkedHashMap. I understand that ObjectMapper deserializes into LinkedHashMap for type Object and I read the explanation for that here:
Jackson and generic type reference
However, why is it able to assign List<LinkedHasMap> to a List<Foo>? At the very least shouldn't that be some sort of ClassCastException?
Also, is there anyway to do this with Java's type system?
NOTE: the following method declaration has the same issue, which makes sense because the additional argument is not needed for T to be determined:
public static <T> TypeReference<List<T>> listOf(Class<T> ignored) {
return new TypeReference<List<T>>(){}
}
It works like this because of type erasure in Java. Please, read about it before you start reading next part of this answer:
Type Erasure
Type Erasure in Java Explained
Java generics type erasure: when and what happens?
As you probably know right now, after reading above articles, your method after compilation looks like this:
static <T> TypeReference<List> listOf(Class<T> ignored) {
return new TypeReference<List>(){};
}
Jackson will try to find out the most appropriate type for it which will be java.util.LinkedHashMap for a JSON Object. To create irrefutable type you need to use com.fasterxml.jackson.databind.type.TypeFactory class. See below example:
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.io.File;
import java.util.List;
public class JsonTypeApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println("Try with 'TypeFactory'");
List<Id> ids = mapper.readValue(jsonFile, CollectionsTypeFactory.listOf(Id.class));
System.out.println(ids);
Id id1 = ids.get(0);
System.out.println(id1);
System.out.println("Try with 'TypeReference<List<T>>'");
List<Id> maps = mapper.readValue(jsonFile, CollectionsTypeFactory.erasedListOf(Id.class));
System.out.println(maps);
Id maps1 = maps.get(0);
System.out.println(maps1);
}
}
class CollectionsTypeFactory {
static JavaType listOf(Class clazz) {
return TypeFactory.defaultInstance().constructCollectionType(List.class, clazz);
}
static <T> TypeReference<List> erasedListOf(Class<T> ignored) {
return new TypeReference<List>(){};
}
}
class Id {
private int id;
// getters, setters, toString
}
Above example, for below JSON payload:
[
{
"id": 1
},
{
"id": 22
},
{
"id": 333
}
]
prints:
Try with 'TypeFactory'
[{1}, {22}, {333}]
{1}
Try with 'TypeReference<List<T>>'
[{id=1}, {id=22}, {id=333}]
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Id
at com.example.JsonTypeApp.main(JsonTypeApp.java:27)
See also:
How to use Jackson's TypeReference with generics?
Jackson create JavaType from Class

How to tell Jackson Library to replace null value fields with an empty object? [duplicate]

Say I have classes Foo
public class Foo {
private Bar bar;
}
and Bar
public class Bar {
private String fizz;
private String bang;
}
EDIT: For clarification I do not own Foo and Bar and cannot alter these classes.
If I want to serialize an empty object of type Foo, it's member, which is of type Bar, will be returned as null.
String json = objectMapper.writeValueAsString(new Foo()); // "{"bar" : null}"
Is there any way I can get the object mapper to serialize an empty Bar object without having to instantiate a new instance of Bar and then adding it to a new instance of Foo?
String json = objectMapper.writeValueAsString(new Foo()) // "{bar": {"fizz" : null, "bang" : null } }"
I was also required to produce such a structure for legacy client compatibility, here is my solution (depends on Spring Boot since uses #JsonComponent annotation)
Create "special object" that will be treated as empty
public class EmptyObject {
}
Create property in your model
#JsonProperty("data")
private EmptyObject data = new EmptyObject();
public EmptyObject getData() {
return data;
}
Create serializer that will process empty object above
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.sevensenders.datahub.api.service.response.model.EmptyObject;
import org.springframework.boot.jackson.JsonComponent;
import java.io.IOException;
#JsonComponent
public class EmptyObjectSerializer extends StdSerializer<EmptyObject> {
public EmptyObjectSerializer() {
this(null);
}
public EmptyObjectSerializer(Class<EmptyObject> t) {
super(t);
}
#Override
public void serialize(EmptyObject value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// to maintain AF compatible format it is required to write {} instead of null
gen.writeStartObject();
gen.writeEndObject();
}
}
Output:
{
...
"data": {}
}
You could create a custom serializer for serializing Foo objects. Then in your custom FooSerializer implementation, you could check for a null bar value and serialize it as a default Bar instance. See https://spin.atomicobject.com/2016/07/01/custom-serializer-jackson/ or http://www.baeldung.com/jackson-custom-serialization for some examples of how to create custom serializers.
It's a bit unrelated to this, but if you define members as private on data class in Kotlin, then, Jackson serializer will produce empty json such as {}.
If you don't want to write your own serializer you can use this approach of declaring type of field as ObjectNode:
private ObjectNode data;
You can set/initialize it like this:
data = new ObjectNode(JsonNodeFactory.instance)
No. I don't see any way doing this. If you don't initialize your Bar, it'll be null inside the JSON.
Since you can't alter these classes, you can just check if the Bar inside the Foo is null and if it is, just initialize it and you'll get what you want.
Bar bar = foo.getBar();
if (bar == null) {
foo.setBar(new Bar());
}
String json = objectMapper.writeValueAsString(foo);
The json will be the following:
{
"bar" : {
"fizz" : null,
"bang" : null
}
}
Hope this helps.

Jackson Mapper serialize empty object instead of null

Say I have classes Foo
public class Foo {
private Bar bar;
}
and Bar
public class Bar {
private String fizz;
private String bang;
}
EDIT: For clarification I do not own Foo and Bar and cannot alter these classes.
If I want to serialize an empty object of type Foo, it's member, which is of type Bar, will be returned as null.
String json = objectMapper.writeValueAsString(new Foo()); // "{"bar" : null}"
Is there any way I can get the object mapper to serialize an empty Bar object without having to instantiate a new instance of Bar and then adding it to a new instance of Foo?
String json = objectMapper.writeValueAsString(new Foo()) // "{bar": {"fizz" : null, "bang" : null } }"
I was also required to produce such a structure for legacy client compatibility, here is my solution (depends on Spring Boot since uses #JsonComponent annotation)
Create "special object" that will be treated as empty
public class EmptyObject {
}
Create property in your model
#JsonProperty("data")
private EmptyObject data = new EmptyObject();
public EmptyObject getData() {
return data;
}
Create serializer that will process empty object above
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.sevensenders.datahub.api.service.response.model.EmptyObject;
import org.springframework.boot.jackson.JsonComponent;
import java.io.IOException;
#JsonComponent
public class EmptyObjectSerializer extends StdSerializer<EmptyObject> {
public EmptyObjectSerializer() {
this(null);
}
public EmptyObjectSerializer(Class<EmptyObject> t) {
super(t);
}
#Override
public void serialize(EmptyObject value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// to maintain AF compatible format it is required to write {} instead of null
gen.writeStartObject();
gen.writeEndObject();
}
}
Output:
{
...
"data": {}
}
You could create a custom serializer for serializing Foo objects. Then in your custom FooSerializer implementation, you could check for a null bar value and serialize it as a default Bar instance. See https://spin.atomicobject.com/2016/07/01/custom-serializer-jackson/ or http://www.baeldung.com/jackson-custom-serialization for some examples of how to create custom serializers.
It's a bit unrelated to this, but if you define members as private on data class in Kotlin, then, Jackson serializer will produce empty json such as {}.
If you don't want to write your own serializer you can use this approach of declaring type of field as ObjectNode:
private ObjectNode data;
You can set/initialize it like this:
data = new ObjectNode(JsonNodeFactory.instance)
No. I don't see any way doing this. If you don't initialize your Bar, it'll be null inside the JSON.
Since you can't alter these classes, you can just check if the Bar inside the Foo is null and if it is, just initialize it and you'll get what you want.
Bar bar = foo.getBar();
if (bar == null) {
foo.setBar(new Bar());
}
String json = objectMapper.writeValueAsString(foo);
The json will be the following:
{
"bar" : {
"fizz" : null,
"bang" : null
}
}
Hope this helps.

Jackson serializer produces invalid Json for subclass of Map

I have this piece of code, that serialises a subclass of Map using Jackson.
Without the serializer registered I get a valid json, I use the serializer to get the name to lower case.
However, the produced json looks like:
{:"two":"aaa":"one":"aaa"}
Any idea why? How to fix?
#Test
public void test_serialization() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("JSONModule", new Version(1, 0, 0, null, null, null));
module.addSerializer(Circle.class, new CircleSerializer());
module.addKeySerializer(Circle.class, new CircleSerializer());
mapper.registerModule(module);
CircleMap statistics = new CircleMap();
statistics.put(Circle.ONE, "aaa");
statistics.put(Circle.TWO, "aaa");
System.out.println(mapper.writeValueAsString(statistics));
}
enum Circle {
ONE, TWO
}
static class CircleMap extends HashMap<Circle, String> {
}
static class CircleSerializer extends JsonSerializer<Circle> {
#Override
public void serialize(Circle value, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
gen.writeString(value.name().toLowerCase());
}
}
Replace
gen.writeString(value.name().toLowerCase());
with
gen.writeFieldName(value.name().toLowerCase());
You're serializing a key. That's interpreted as a JSON field name so you have to use that method. writeString javadoc states
Method for outputting a String value. Depending on context this means
either array element, (object) field value or a stand alone String;
You're straight up writing the string value. That's not what you want.
Note that you'll need a different serializer for keys and for normal values.

Using Jackson ObjectMapper with Generics to POJO instead of LinkedHashMap

Using Jersey I'm defining a service like:
#Path("/studentIds")
public void writeList(JsonArray<Long> studentIds){
//iterate over studentIds and save them
}
Where JsonArray is:
public class JsonArray<T> extends ArrayList<T> {
public JsonArray(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<T>> typeRef = new TypeReference<ArrayList<T>>() {};
ArrayList<T> list = objectMapper.readValue(v, typeRef);
for (T x : list) {
this.add((T) x);
}
}
}
This works just fine, but when I do something more complicated:
#Path("/studentIds")
public void writeList(JsonArray<TypeIdentifier> studentIds){
//iterate over studentIds and save them by type
}
Where the Bean is a simple POJO such as
public class TypeIdentifier {
private String type;
private Long id;
//getters/setters
}
The whole thing breaks horribly. It converts everything to LinkedHashMap instead of the actual object. I can get it to work if I manually create a class like:
public class JsonArrayTypeIdentifier extends ArrayList<TypeIdentifier> {
public JsonArrayTypeIdentifier(String v) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new MappingJsonFactory());
TypeReference<ArrayList<TypeIdentifier>> typeRef = new TypeReference<ArrayList<TypeIdentifier>>(){};
ArrayList<TypeIdentifier> list = objectMapper.readValue(v, typeRef);
for(TypeIdentifier x : list){
this.add((TypeIdentifier) x);
}
}
}
But I'm trying to keep this nice and generic without adding extra classes all over. Any leads on why this is happening with the generic version only?
First of all, it works with Longs because that is sort of native type, and as such default binding for JSON integral numbers.
But as to why generic type information is not properly passed: this is most likely due to problems with the way JAX-RS API passes type to MessageBodyReaders and MessageBodyWriters -- passing java.lang.reflect.Type is not (unfortunately!) enough to pass actual generic declarations (for more info on this, read this blog entry).
One easy work-around is to create helper types like:
class MyTypeIdentifierArray extends JsonArray<TypeIdentifier> { }
and use that type -- things will "just work", since super-type generic information is always retained.

Categories

Resources