Maintaining subtype information when serializing Java objects using Jackson without using a wrapper class

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

Json document without shell

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

I annotated the Abstract Animal class, as shown at 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 generated JSON file will contain an additional animals object.

Wrapped JSON Document

 { "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 simple Java list, I can successfully read from a Json document without a shell , but if I try to write it, the output file will look like this:

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 type information when serializing objects in JSON?

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

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

EDIT 2: As mentioned in the comments, I ended up using arrays of objects 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); 
+4
source share
1 answer

The problem is erasing the Java type, which means that the nominal type of the List object is List. And since java.lang.Object does not have @JsonTypeInfo, it will not be included.

But you can configure Jackson to use a specific type of Java to convert list items:

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

Code changed:

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 a custom TypeReference to tell Jackson which class to use to convert the List:

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

This solution will work, but I like the main solution better, because it is cleaner, you do not need to instantiate the class "TypeReference" ...

+4
source

Source: https://habr.com/ru/post/1393388/


All Articles