Java/Json Polymorphic serialization & De-Serialization using Jackson.

Jackson has a number of flexible ways to map Java objects to and from JSON.Serialization or de-serializing a java object is straight forward using Jackson.But when you have a Interface or Abstract class which has multiple implementations then serializing & De-Serializing  using Jackson is little tricky and this is also called Polymorphic serialization & De-Serialization.

Here in this post, I focus on serialization and deserialization of polymorphic types.

Polymorphic deserialization using de-serializer :


If Interface or abstract class have multiple implementations and try to deserialize any implementation with Interface then you will find below error

Can not construct instance of com.MyInterface, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information

To overcome this error, You could write a custom deserializer for Interface or abstract class, which would have to programmatically parse the JSON depending on the fields and parse the body as whatever concrete class you had in mind.

Let me explain by taking simple pojos

@JsonDeserialize(using = AnimalDeSerializer.class)
public interface Animal {
}

@JsonDeserialize(as = Lion.class)
public class Lion implements Animal {

    private String name;
    private String roar;

//constructor & setters & getters
}

@JsonDeserialize(as = Tiger.class)
public class Tiger implements Animal {

    private String name;
    private String purr;
//constructor & setters & getters
}


AnimalDeSerializer :


public class AnimalDeSerializer extends JsonDeserializer<Animal> {

    public static final String NAME = "name";
    public static final String LION = "lion";
    public static final String TIGER = "tiger";

    @Override
    public Animal deserialize(JsonParser jp, DeserializationContext context) throws IOException {

        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        ObjectNode root = mapper.readTree(jp);
        if (root.has(NAME)) {
            JsonNode jsonNode = root.get(NAME);
            if(jsonNode.asText().equals(LION)) {
                return mapper.readValue(root.toString(), Lion.class);
            }else if(jsonNode.asText().equals(TIGER))
            {
                return mapper.readValue(root.toString(), Tiger.class);
            }
        }
        throw context.mappingException("Failed to de-serialize animal, as name property not exist");
    }
}


Demo :
String animalString = "{\"name\":\"tiger\",\"purr\":\"purr\"}";
ObjectMapper objectMapper = new ObjectMapper();
Animal animal = objectMapper.readValue(animalString, Animal.class);
System.out.println(animal);

You should annotate Instance with @JsonDeserialize(using = AnimalDeSerializer.class) to indicate the class to be used to deserialize the Interface or abstract class.

Note: You may get Stackoverflow error if you doesn't annotate implementation class.All implementation class should deserialize with themselves, else it will use the parent class deserializer which leads to StackOverflowError.

Polymorphic serialization & deserialization using @JsonTypeInfo and @JsonSubTypes annotations :


I focus on serialization and deserialization of polymorphic types using @JsonTypeInfo and @JsonSubTypes which maintain sub type information while serializing java object and recreate the exact sub type.

In the example animal is a Interface and it can be an tiger or a lion, and they both extend the Animal Interface . While deserializing we want to create the exact animal type and demonstrate the use of @JsonTypeInfo and @JsonSubTypes annotations.


@JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
        include=JsonTypeInfo.As.PROPERTY,
        property="name")
@JsonSubTypes({
        @JsonSubTypes.Type(value=Lion.class, name="lion"),
        @JsonSubTypes.Type(value=Tiger.class, name="tiger"),
})
public interface Animal {

}

@JsonTypeName("lion")
public class Lion implements Animal {

    private String name;
    private String roar;

//constructor & setters & getters
}

@JsonTypeName("tiger")
public class Tiger implements Animal {

    private String name;
    private String purr;

//constructor & setters & getters
}


Demo :

List<Animal> animal = new ArrayList<Animal>();
animal.add(new Lion("lion", "roar"));
animal.add(new Tiger("tiger", "purr"));
animal.add(new Tiger("tiger", "purrrrrrrrr"));

URL url = JacksonPolymorphicSerialization.class.getClassLoader().getResource("animals.json");
File file = new File(url.getPath());

// de-serailization sub types
new ObjectMapper().writeValue(file, animal);

// serailization animals and its subtype
List<Animal> animals = new ObjectMapper().readValue(file, List.class);
System.out.println(animals);


output : [{name=lion, roar=roar}, {name=tiger, purr=purr}, {name=tiger, purr=purrrrrrrr}]

Hope this helps you understanding serializing and deserializing polymorphic types using Jackson.

Spring MVC rest service with multipart request plus json

In spring we can expose rest service which accepts multipart request with json content.Below is rest service construction and java script code to hit such service.

Multipart Request with JSON Data  (also called Mixed Multipart) :
-------------------------------------------------------------------------------------

Based on RESTful service in Spring 4.0.2 Release, HTTP request with a First part with XML or JSON formatted data and a second part with a file can be achieved with @RequestPart .Below is the sample implementation.


Java Snippet:
------------------

Controller will have mixed @RequestPart and MultipartFile to serve such Multipart + JSON request.


@RequestMapping(value = "/executesampleservice", method = RequestMethod.POST, consumes = {"multipart/form-data"})
@ResponseBody
public boolean executeSampleService(@RequestPart("properties") @Valid ConnectionProperties properties, @RequestPart("file") @Valid @NotNull @NotBlank MultipartFile file) {
   return projectService.executeSampleService(project, file);
}
 

Java Script Implementation steps:
------------------------------------------------


1. Create a FormData object.

2. Append the file to the FormData object using one of the below steps.
i. If the file has been uploaded using an input element of type "file", then append it to the FormData   object.
formData.append("file", document.forms[formName].file.files[0]);
ii. Directly append the file to the FormData object.
formData.append("file", myFile, "myfile.txt"); OR  formData.append("file", myBob, "myfile.txt");

3. Create a blob with the stringified JSON data and append it to the FormData object. This causes the Content-type of the second part in the multipart request to be "application/json" instead of the file type.
4. Send the request to the server.
5. Request Details :
    i. Content-Type : undefined
This causes the browser to set the Content-Type to multipart/form-data and fill the boundary correctly. Manually setting Content-Type to multipart/form-data will fail to fill in the boundary parameter of the request.


Javascript snippet:
---------------------------



 formData = new FormData();
    formData.append("file", document.forms[formName].file.files[0]);
    formData.append('properties', new Blob([JSON.stringify({
                    "name": "root",
                    "password": "root"                
                })], {
                    type: "application/json"
                }));

  

Request Details:
----------------------



    method: "POST",
    headers: {
             "Content-Type": undefined
      },
    data: formData


Request Payload:
------------------------


  Accept:application/json, text/plain, */*
    Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryEBoJzS3HQ4PgE1QB
   
    ------WebKitFormBoundaryvijcWI2ZrZQ8xEBN
    Content-Disposition: form-data; name="file"; filename="myfile.txt"
    Content-Type: application/txt
   
   
    ------WebKitFormBoundaryvijcWI2ZrZQ8xEBN
    Content-Disposition: form-data; name="properties"; filename="blob"
    Content-Type: application/json
   
   
    ------WebKitFormBoundaryvijcWI2ZrZQ8xEBN--