web-dev-qa-db-fra.com

Déssérialisation du modèle JSON en modèle objet polymorphe à l'aide des annotations Spring et JsonTypeInfo

J'ai le modèle d'objet suivant dans mon application Web Spring MVC (v3.2.0.RELEASE):

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 {}

Lorsque je sérialise la classe Order en JSON, j'obtiens le résultat suivant (qui correspond exactement à ce que je veux):

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

Malheureusement, si je prends le JSON ci-dessus et tente de le désérialiser dans mon modèle d'objet, j'obtiens l'exception suivante:

Impossible de lire JSON: Impossible de résoudre le type ID 'creditCardPayment' dans un sous-type de [type simple, classe Paiement] dans [Source: org.Apache.catalina.connector.CoyoteInputStream@19629355; ligne 1, colonne: 58] (via la chaîne de référence: commande ["paiement"]); imbriqué l'exception est com.fasterxml.jackson.databind.JsonMappingException: Impossible de résoudre le type ID 'creditCardPayment' en un sous-type de [type simple, class Payment] dans [Source: org.Apache.catalina.connector.CoyoteInputStream@19629355; ligne 1, colonne: 58] (via la chaîne de référence: commande ["paiement"])

Mon application est configurée via Spring JavaConf, comme suit:

@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());
    }
}

Pour les tests, j’ai un contrôleur avec 2 méthodes, l’une retourne une demande Order for HTTP GET (celle-ci fonctionne) et l’autre qui accepte une commande via HTTP POST (celle-ci échoue), par exemple.

@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) {}

}

J'ai essayé toutes les suggestions des différentes discussions sur SO mais je n'ai pas eu de chance jusqu'à présent. Quelqu'un peut-il voir ce que je fais mal?

18
grigori

Essayez d'enregistrer le sous-type en utilisant ObjectMapper.registerSubtypes au lieu d'utiliser des annotations

10
ragnor

La méthode registerSubtypes() fonctionne!

@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: Nous utilisons l'interface et les classes d'implémentation. Si vous voulez que Jackson déssérialise les classes conformément à leurs classes d'implémentation, vous devez toutes les enregistrer à l'aide de la méthode "registerSubtypes" d'ObjectMapper.

Note2: De plus, vous utilisez "@JsonTypeInfo (utilisez = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property =" type ")" comme annotation avec votre interface.

Vous pouvez également définir l'ordre des propriétés lorsque le mappeur écrit une valeur de votre POJO en tant que json.

Vous pouvez le faire en utilisant l'annotation ci-dessous.

@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();
    ........
}

J'espère que cela t'aides!

4
Rashmin H Gadhavi

J'ai eu un problème similaire alors que je travaillais sur un service basé sur dropwizard. Je ne comprends pas très bien pourquoi les choses ne fonctionnent pas pour moi de la même manière que le code de l'assistant dropwizard, mais je sais pourquoi le code de l'article original ne fonctionne pas. @JsonSubTypes veut un tableau de sous-types, pas une valeur unique. Donc, si vous remplacez la ligne ...

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

avec...

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

Je crois que votre code fonctionnera.

Pour ceux qui ont ce même message d'erreur, vous pouvez avoir un problème avec les sous-types découverts. Essayez d’ajouter une ligne comme celle ci-dessus ou de chercher un problème avec la découverte des classes contenant la balise @JsonTypeName.

3
Aaron Cooley

A rencontré la même erreur et utilisé l'équivalent du JSON ci-dessous (au lieu de CreditCardPayment utilisé mon nom de classe) comme entrée pour le désérialiseur et cela a fonctionné:

{
    "type": "CreditCardPayment", 
    ...
}
0
dibs

La réponse de Rashmin a fonctionné et j'ai trouvé un autre moyen d'éviter le problème com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id into a subtype of Blah sans avoir à utiliser registerSubtypes. Ce que vous pouvez faire, c'est ajouter l'annotation suivante à la classe parente: 

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

Notez que la différence est JsonTypeInfo.Id.CLASS au lieu de JsonTypeInfo.Id.NAME. L'inconvénient est que le JSON créé contiendra le nom complet de la classe, y compris l'espace de noms complet. L'avantage est que vous n'avez pas à vous soucier de l'enregistrement des sous-types.

0
herrtim

Dans mon cas, j'avais ajouté defaultImpl = SomeClass.class à @JsonTypeInfo et j'essayais de le convertir SomeClass2.class

0
Anand Rockzz