web-dev-qa-db-fra.com

CustomDeserializer n'a pas de constructeur par défaut (pas d'argument)

Je consomme un REST Api avec RestTemplate. La réponse que je reçois de l'API a beaucoup d'objets imbriqués. Voici un petit extrait à titre d'exemple:

"formularios": [
  {
    "form_data_id": "123006",
    "form_data": {
      "form_data_id": "123006",
      "form_id": "111",
      "efs": {
        "1": {},
        "2": "{\"t\":\"c\",\"st\":\"m\",\"v\":[{\"id\":\"3675\",\"l\":\"a) Just an example\",\"v\":\"1\"},{\"id\":\"3676\",\"l\":\"b) Another example.\",\"v\":\"2\"}]}"
      }
    }

Le problème que je rencontre est que la plupart du temps, le "1" a un contenu, tout comme "2", et le jackson l’analyse comme une chaîne sur l’objet "efs". Mais parfois, comme dans l'extrait de code, l'API l'envoie vide et Jackson le prend comme un objet, ce qui me donne une erreur qui dit quelque chose à propos de START_OBJECT (je ne me souviens pas de l'erreur exacte, mais ce n'est pas important pour cette question. ).

J'ai donc décidé de créer un désérialiseur personnalisé afin que, lorsque Jackson lit "1", il ignore l'objet vide et le traite comme une chaîne vide.

Voici mon désérialiseur personnalisé:

public class CustomDeserializer extends StdDeserializer<Efs> {

 public CustomDeserializer(Class<Efs> t) {
     super(t);
 }

 @Override
 public Efs deserialize(JsonParser jp, DeserializationContext dc)
         throws IOException, JsonProcessingException {

     String string1 = null;
     String string2 = null;
     JsonToken currentToken = null;

     while ((currentToken = jp.nextValue()) != null) {
         if (currentToken.equals(JsonToken.VALUE_STRING)) {
             if (jp.getCurrentName().equals("1")) {
                 string1 = jp.getValueAsString();
             } else {
                 string2 = jp.getValueAsString();
             }

         } else {
             if (jp.getCurrentName().equals("2")) {
                 string2 = jp.getValueAsString();
             }

         }
     }
     return new Efs(string1, string2);

  }
 }

Et voici comment je l'utilise lorsque je reçois la réponse de l'API:

    ObjectMapper mapper = new ObjectMapper();  
    SimpleModule mod = new SimpleModule("EfsModule");
    mod.addDeserializer(Efs.class, new CustomDeserializer(Efs.class));
    mapper.registerModule(mod);


    List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
    MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
    jsonMessageConverter.setObjectMapper(mapper);
    messageConverters.add(jsonMessageConverter);


    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setMessageConverters(messageConverters);

Je reçois l'erreur:

 CustomDeserializer has no default (no arg) constructor

Mais je ne sais pas exactement ce que je fais mal ni comment le résoudre. Merci pour l'aide et les excuses pour la longue question, je voulais donner le plus de contexte possible.

7
abril

Vous devez disposer d’un constructeur par défaut sans arguments. Vous pouvez en créer un (ou remplacer l’autre si vous n’en avez pas vraiment besoin):

public class CustomDeserializer extends StdDeserializer<Efs> {

   public CustomDeserializer() {
       super(Efs.class);
   }
   ...
}
8

Il existe également un piège dans lequel les utilisateurs peuvent tomber (comme moi-même). Si vous déclarez le désérialiseur en tant que classe interne (pas une classe imbriquée statique), procédez comme suit:

@JsonDeserialize(using = DomainObjectDeserializer.class)
public class DomainObject {
    private String key;

    public class DomainObjectDeserializer extends StdDeserializer<DomainObject> {
        public DomainObjectDeserializer() {
            super(DomainObject.class);
        }

        @Override
        public DomainObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            // code
        }
    }
}

Jackson utilise le Class # getDeclaredConstructor () sans argument (la méthode accepte vararg) qui signifie: donne-moi un constructeur par défaut (sans argument). Le code ci-dessus lève une exception lorsque Jackson essaie de créer DomainObjectDeserializer car javac génère le constructeur qui accepte la référence de classe englobante. Techniquement parlant, DomainObjectDeserializer n'a pas de constructeur par défaut.

Par souci de curiosité, vous pouvez exécuter DomainObjectDeserializer.class.getDeclaredConstructors() et vous assurer que cette méthode renvoie un tableau à élément unique contenant une définition de constructeur avec une référence de classe englobante.

La DomainObjectDeserializer doit être déclarée comme une classe static.

Voici un bon réponse à lire plus en détail.

1
Edgar Asatryan