Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maintain sub type information while serializing java objects using Jackson, without using wrapper class

I am trying to convert between a JSON file and an abstract class with two subclasses in Java using Jackson. Ideally, I would like to use a JSON as the following:

Json document without wrapper

[ {
  "type" : "lion",
  "name" : "Simba",
  "endangered" : true,
  "action" : "running"
}, {
  "type" : "elephant",
  "name" : "Dumbo",
  "endangered" : false,
  "table" : [ 1.0, 2.0, 3.0 ]
} ]

I have annotated the Animal abstract class as shown on http://www.studytrails.com/java/json/java-jackson-Serialization-polymorphism.jsp

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes({ @Type(value = Lion.class, name = "lion"),
    @Type(value = Elephant.class, name = "elephant") })
public abstract class Animal {
    String name;
    boolean endangered;

//Skipped constructor/getters/setters

}

I can successfully read/write it using a wrapper object, but the produced JSON file will include an additional animals object.

JSON document with wrapper

{
  "animals" : [ {
    "type" : "lion",
    "name" : "Simba",
    "endangered" : true,
    "action" : "running"
  }, {
    "type" : "elephant",
    "name" : "Dumbo",
    "endangered" : false,
    "table" : [ 1.0, 2.0, 3.0 ]
  } ]
}

When I use a plain Java list, I can successfully read from the Json document without wrapper but if I try to write it, the output file will be as follows:

output.json

[ {
  "name" : "Simba",
  "endangered" : true,
  "action" : "running"
}, {
  "name" : "Dumbo",
  "endangered" : false,
  "table" : [ 1.0, 2.0, 3.0 ]
} ]

Java code

// Test reading using raw list
JavaType listType = mapper.getTypeFactory()
        .constructCollectionType(List.class, Animal.class);
List<Animal> jsonList = mapper.readValue(new FileInputStream(
        "demo.json"), listType);
jsonDocument = new File(outputFile);
// Test writing using raw list
mapper.writerWithDefaultPrettyPrinter().writeValue(jsonDocument,
        jsonList);

Any ideas how I can include the type information when serializing the objects into JSON?

The complete Eclipse project can be found on: https://github.com/nkatsar/json-subclass

EDIT : Simplified the Java code to use JavaType instead of TypeReference. The code on GitHub has been also updated with the correct solutions

EDIT 2 : As mentioned in the comments, I ended up using Object arrays for serialization/deserialization as follows:

// Test reading using array
Animal[] jsonArray = mapper.readValue(
        new FileInputStream(demoFile), Animal[].class);
System.out.println("Reading using array:\nObject: "
        + Arrays.toString(jsonArray));

// Test writing using array
outputJson = mapper.writeValueAsString(jsonArray);
System.out.println("Writing using array:\nJSON: " + outputJson);
like image 736
nkatsar Avatar asked Dec 11 '14 15:12

nkatsar


People also ask

Does Jackson use Java serialization?

Note that Jackson does not use java. io. Serializable for anything: there is no real value for adding that. It gets ignored.

How do you serialize an object to JSON Jackson in Java?

Converting Java object to JSON In it, create an object of the POJO class, set required values to it using the setter methods. Instantiate the ObjectMapper class. Invoke the writeValueAsString() method by passing the above created POJO object. Retrieve and print the obtained JSON.


1 Answers

The problem here is Java type erasure, which means that nominal type of List object is List. And since java.lang.Object does not have @JsonTypeInfo, it won't be enabled.

But you can configure Jackson to use specific Java type to convert List elements:

JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, Animal.class);
String outputJson = mapper.writerWithType(listJavaType).writeValueAsString(myList);

Modified your code:

public static void main(String[] args) {

List<Animal> myList = new ArrayList<Animal>();
myList.add(new Lion("Simba"));
// myList.add(new Lion("Nala"));
myList.add(new Elephant("Dumbo"));
// myList.add(new Elephant("Lucy"));
AnimalList wrapperWrite = new AnimalList(myList);

try {
    ObjectMapper mapper = new ObjectMapper();
    JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, Animal.class);

    String outputJson = mapper.writerWithType(listJavaType).writeValueAsString(myList);
    System.out.println(outputJson);

    List wrapperRead = mapper.readValue(outputJson, List.class);
    System.out.println("Read recently generated data: " + wrapperRead);

    // Test reading using raw list
    List<Animal> jsonList = mapper.readValue(new FileInputStream( "demo.json"), listJavaType);
    System.out.println("Read from demo.json \ndata: " + jsonList);

    outputJson = mapper.writerWithType(listJavaType).writeValueAsString(jsonList);
    System.out.println(outputJson);

} catch (JsonGenerationException e) {
    System.out.println("Could not generate JSON: " + e.getMessage());
} catch (JsonMappingException e) {
    System.out.println("Invalid JSON Mapping: " + e.getMessage());
} catch (IOException e) {
    System.out.println("File I/O error: ");
}

}

Result:

[{"type":"lion","name":"Simba","endangered":false,"action":null},{"type":"elephant","name":"Dumbo","endangered":false,"table":null}]
Read recently generated data: [{type=lion, name=Simba, endangered=false, action=null}, {type=elephant, name=Dumbo, endangered=false, table=null}]
Read from demo.json 
data: [Animal(name=Nala, endangered=true, action=running}, Animal(name=Lucy, endangered=false, table=[1.0, 2.0, 3.0]}]
[{"type":"lion","name":"Nala","endangered":true,"action":"running"},{"type":"elephant","name":"Lucy","endangered":false,"table":[1.0,2.0,3.0]}]

Another solution: You can use custom TypeReference to inform Jackson what class needs to be used to convert List:

mapper.writerWithType(new TypeReference<List<Animal>>() {}).writeValueAsString(myList)

That solution will work as well but I like main solution better because it is more clean, you don't need to instantiate abstract 'TypeReference' class...

like image 136
Ilya Ovesnov Avatar answered Sep 20 '22 13:09

Ilya Ovesnov