Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 464
tyrondis Avatar asked Mar 03 '16 17:03

tyrondis


3 Answers

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

like image 128
Maciej Dobrowolski Avatar answered Oct 18 '22 20:10

Maciej Dobrowolski


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!
}
like image 35
Dariusz Avatar answered Oct 18 '22 21:10

Dariusz


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.

like image 21
coladict Avatar answered Oct 18 '22 21:10

coladict