Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

De-serializing JSON to polymorphic object model using Spring and JsonTypeInfo annotation

I have the following object model in my Spring MVC (v3.2.0.RELEASE) web application:

public class Order {   private Payment payment; }  @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT) @JsonSubTypes.Type(name = "creditCardPayment", value = CreditCardPayment.class) public interface Payment {}   @JsonTypeName("creditCardPayment") public class CreditCardPayment implements Payment {} 

When I serialise the Order class to JSON, I get the following result (which is exactly what I want):

{   "payment" : {     "creditCardPayment": {       ...     }  } 

Unfortunately, if I take the above JSON and try to de-serialise it back into my object model, I get the following exception:

Could not read JSON: Could not resolve type id 'creditCardPayment' into a subtype of [simple type, class Payment] at [Source: org.apache.catalina.connector.CoyoteInputStream@19629355; line: 1, column: 58] (through reference chain: Order["payment"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id 'creditCardPayment' into a subtype of [simple type, class Payment] at [Source: org.apache.catalina.connector.CoyoteInputStream@19629355; line: 1, column: 58] (through reference chain: Order["payment"])

My application is configured via Spring JavaConf, as follows:

@Configuration @EnableWebMvc public class AppWebConf extends WebMvcConfigurerAdapter {      @Bean     public ObjectMapper objectMapper() {         ObjectMapper objectMapper = new ObjectMapper();         objectMapper.setSerializationInclusion(Include.NON_NULL);         objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);         return objectMapper;     }      @Bean     public MappingJackson2HttpMessageConverter mappingJacksonMessageConverter() {         MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();         converter.setObjectMapper(objectMapper());         return converter;     }      @Bean     public Jaxb2RootElementHttpMessageConverter jaxbMessageConverter() {         return new Jaxb2RootElementHttpMessageConverter();     }      @Override     public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {         converters.add(jaxbMessageConverter());         converters.add(mappingJacksonMessageConverter());     } } 

For testing, I have a controller with 2 methods, one returns an Order for HTTP GET request (this one works) and one that accepts an Order via a HTTP POST (this one fails), e.g.

@Controller public class TestController {      @ResponseBody     @RequestMapping(value = "/test", method = RequestMethod.GET)     public Order getTest() {}      @RequestMapping(value = "/test", method = RequestMethod.POST)     public void postTest(@RequestBody order) {}  } 

I have tried all suggestions from the various discussions on SO but so far had no luck. Can anyone spot what I'm doing wrong?

like image 881
grigori Avatar asked Oct 08 '13 04:10

grigori


People also ask

How can I add polymorphic deserialization to a JSON object?

When using Newtonsoft’s libraries, one would add support for polymorphic deserialization by storing type information for a JSON object in a property named $type. Technically, the data could have us instantiating whatever type from whatever assembly it wanted!

Can Jackson identify the right type during deserialization of JSON objects?

In cases where polymorphic types are persisted to JSON, there's no way for Jackson to figure out the right type during deserialization. Let's understand that with an example. public class Rectangle extends Shape { private int w; private int h; .............

What is jsonpolymorphicconverter<ttypedescriptor<Tbase>?

This new class is going to itself be a base class that will require a deriving class to get any use out of it with specific datasets. This new mystery class shall heretofore be known as the JsonPolymorphicConverter<TTypeDescriptor,TBase> class.

How to use @jsontypeinfo annotation?

@JsonTypeInfo annotation can be used both on classes (above example) and properties. In our example using the annotation on 'shapes' property: If this annotation exists on both class and property, then the one on property has precedence, as it is considered more specific.


2 Answers

Try to register subtype using ObjectMapper.registerSubtypes instead of using annotations

like image 121
ragnor Avatar answered Oct 06 '22 09:10

ragnor


The method registerSubtypes() works!

@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type") public interface Geometry {   //... }  public class Point implements Geometry{   //... } public class Polygon implements Geometry{   //... } public class LineString implements Geometry{   //... }  GeoJson geojson= null; ObjectMapper mapper = new ObjectMapper(); mapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.registerSubtypes(Polygon.class,LineString.class,Point.class);  try {     geojson=mapper.readValue(source, GeoJson.class);             } catch (IOException e) {     e.printStackTrace(); } 

Note1: We use the Interface and the implementing classes. I fyou want jackson to de-serialize the classes as per their implementing classes, you have to register all of them using ObjectMapper's "registerSubtypes" method.

Note2: In addition you use, " @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")" as annotation with your Interface.

You can also define the order of properties when mapper writes a value of your POJO as a json.

This you can do using below annotation.

@JsonPropertyOrder({"type","crs","version","features"}) public class GeoJson {      private String type="FeatureCollection";     private List<Feature> features;     private String version="1.0.0";     private CRS crs = new CRS();     ........ } 

Hope this helps!

like image 44
Rashmin H Gadhavi Avatar answered Oct 06 '22 09:10

Rashmin H Gadhavi