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.
Related
so this is a very weird situation that I came across and have no idea how to handle.
I have a service that contains logic to deserialize an object from a request sent to it. Now the object in question is being changed and a new deserialization method is implemented to deal with that (don't ask why, all I know is we just need to change the method of deserialization).
The problem is that this change needs to be backwards compatible so we should be able to deal with both types of objects. In order to do this, we need to be able to determine the correct deserializer to use depending on the type of object but how do we do that if the object is serialized into a byte buffer? Im out of ideas... Is there a different/better way of going about this change?
The service is in Java.
Edit 1: Clearing up my intentions .
The old object used a custom serializer and the new one uses an ObjectMapper JSON serializer. So my goal is to be able to detect if I am dealing with the old or new object so I can deserialize accordingly.
I can try to use the new deserializer and catch the JsonParseException it throws and in the catch block, use the old serializer but this is not the way I want to handle a JsonParseException.
Serializable classes should have a serialVersionUID that is static, final, and of type long.
This is to ensure that the class of the object that was serialized is same as the class of the object being deserialized.
In order to achieve backward compatibility follow these steps:
Ensure that whenever class structure is changed, you change the value of this field.
Use your new custom serializer to deserialize the object.
If the object is of the previous class you will get an InvalidClassException. Catch this exception and try to deserialize that object with the legacy deserializer inside catch block.
This ensures that your custom deserializer has backward compatability.
First of all, you need to identify a difference between the new and the old object. You'll use that to switch to the old de-serializer.
You'll also need a specific ObjectMapper for these two classes, old and new.
Create a Module, and register it
final SimpleModule module = new SimpleModule();
module.addDeserializer(Object.class, new NewDeserializer(new OldDeserializer()));
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
Prepare the new StdDeserializer, which will accept as constructor argument the old one.
public class NewDeserializer extends StdDeserializer<Object> {
private final StdDeserializer<Object> oldDeserializer;
NewDeserializer(final StdDeserializer<Object> oldDeserializer) {
super(NewObject.class);
this.oldDeserializer = oldDeserializer;
}
#Override
public Object deserialize(
final JsonParser parser,
final DeserializationContext context) throws IOException {
final ObjectCodec codec = parser.getCodec();
// Read the JSON document to a tree
final TreeNode treeNode = codec.readTree(parser);
// Identify if it is the new format, or the old one
final TreeNode newField = treeNode.get("newField");
if (newField == null) {
// Delegate to the old de-serializer
final JsonFactory factory = new JsonFactory(parser.getCodec());
final JsonParser oldParser = factory.createParser(treeNode.toString());
return oldDeserializer.deserialize(oldParser, context);
}
return codec.readValue(treeNode.traverse(), NewObject.class);
}
}
The old StdDeserializer
public class OldDeserializer extends StdDeserializer<Object> {
OldDeserializer() {
super(OldObject.class);
}
#Override
public Object deserialize(
final JsonParser parser,
final DeserializationContext context) throws IOException {
return parser.getCodec().readValue(parser, OldObject.class);
}
}
Now, simply call
objectMapper.readValue(v, Object.class);
My json Object is dynamic, there may be arounf 10 -15 types of dynamic json response i will get,
EX: {"a": "B"}, {"a": [a, c, d]}, {a:b, d: []}, {a: []}, {a: [], b:[]}
these are possible types i have define.
//Before writing the below line, I have to identify the response belongs
to the correct Class Type and Convert the response into the corosponding Java Class.
A aResponse = mapper.convertValue(jsonResponse(), A.class );
Based on my above code the response always consider to take A.class and will throw an exception.
How can i identify the response belongs to specifc class, and convert it?
You can use a custom deserializer for this:
public class Test {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule("configModule", Version.unknownVersion());
module.addDeserializer(Root.class, new DeSerializer());
mapper.registerModule(module);
Root readValue = mapper.readValue(<json source>);
}
}
class DeSerializer extends StdDeserializer<Root> {
protected DeSerializer() {
super(Root.class);
}
#Override
public Root deserialize(JsonParser p, DeserializationContext ctxt) throws Exception {
// use p.getText() and p.nextToken to navigate through the json, conditionally check the tags and parse them to different objects and then construct Root object
return new Root();
}
}
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.
Is it possible to deserialize JSON using Jackson into one of two types based on the content of the JSON?
For example, I have the following Java (technically Groovy, but that's not important) interfaces and classes:
interface Id {
Thing toThing()
}
class NaturalId implements Id {
final String packageId
final String thingId
Thing toThing() {
new PackageIdentifiedThing(packageId, thingId)
}
}
class AlternateId implements Id {
final String key
Thing toThing() {
new AlternatelyIdentifiedThing(key)
}
}
The JSON I will receive will look like either of the following:
This JSON should map to NaturalId {"packageId": "SomePackage", "thingId": "SomeEntity"}
This JSON should map to AlternateId {"key": "SomeUniqueKey"}
Does anyone know how I can accomplish this with Jackson 2.x WITHOUT including type id's?
Are these the only two classes that implement Id? If so, you could write an IdDeserializer class and put #JsonDeserialize(using = IdDeserializer.class) on the Id interface, and the deserializer would look at the JSON and determine which object to deserialize into.
EDIT: The JsonParser is streaming so it should look something like this:
public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectNode node = jp.readValueAsTree();
Class<? extends Id> concreteType = determineConcreteType(node); //Implement
return jp.getCodec().treeToValue(node, concreteType);
}
Annotate your methods with #JsonIgnore
#JsonIgnore
Thing toThing() {
new PackageIdentifiedThing(packageId, thingId)
}
With Jackson2, you can easily marshall to different classes using generics:
private <T> T json2Object(String jsonString, String type, Class<T> clazz) {
JsonNode jsonObjectNode = getChildNode(jsonString, type);
T typeObject = null;
try {
typeObject = jacksonMapper.treeToValue(jsonObjectNode, clazz);
} catch (JsonProcessingException jsonProcessingException) {
LOGGER.severe(jsonProcessingException);
}
return typeObject;
}
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.