32

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?

4

6 回答 6

19

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

于 2013-10-08T05:44:49.307 回答
9

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!

于 2014-12-08T22:09:17.323 回答
4

I had a similar issue while working on a dropwizard based service. I don't fully understand why things didn't work for me in the same way that the dropwizard code works, but I know why the code in the original post doesn't work. @JsonSubTypes wants an array of sub types, not a single value. So if you replace the line...

@JsonSubTypes.Type(name = "creditCardPayment", value = CreditCardPayment.class)

with...

@JsonSubTypes({ @JsonSubTypes.Type(name = "creditCardPayment", value = CreditCardPayment.class) })

I believe your code will work.

For those that are having this same error message pop up, you may be having an issue with the subtypes being discovered. Try adding a line like the one above or looking for issue with the discovery of the classes that have the @JsonTypeName tag in them.

于 2016-09-13T22:22:15.523 回答
0

Rashmin's answer worked, and I found an alternative way to avoid the com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id into a subtype of Blah issue without needing to use registerSubtypes. What you can do is add the following annotation to the parent class:

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")

Note that the difference is JsonTypeInfo.Id.CLASS instead of JsonTypeInfo.Id.NAME. The downside is that the created JSON will contain the entire class name including the full namespace. The upside is that you don't have to worry about registering subtypes.

于 2016-04-26T16:39:42.477 回答
0

Encountered the same error and used the equivalent of the below JSON (instead of CreditCardPayment used my class name) as the input for deserializer and it worked:

{
    "type": "CreditCardPayment", 
    ...
}
于 2017-07-13T22:06:28.507 回答
0

In my case I had added defaultImpl = SomeClass.class to @JsonTypeInfo and was trying to convert it SomeClass2.class

于 2017-09-01T14:13:33.513 回答