136 votes

Comment appeler le désérialiseur par défaut à partir d'un désérialiseur personnalisé dans Jackson ?

J'ai un problème avec mon deserializer personnalisé dans Jackson. Je veux accéder au sérialiseur par défaut pour peupler l'objet que je désérialise. Après le peuplement, je ferai des choses personnalisées, mais je veux d'abord désérialiser l'objet avec le comportement par défaut de Jackson.

Voici le code que j'ai pour l'instant.

public class UserEventDeserializer extends StdDeserializer<User> {

  private static final long serialVersionUID = 7923585097068641765L;

  public UserEventDeserializer() {
    super(User.class);
  }

  @Override
  @Transactional
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {

    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    User deserializedUser = null;
    deserializedUser = super.deserialize(jp, ctxt, new User()); 
    // The previous line generates an exception java.lang.UnsupportedOperationException
    // Because there is no implementation of the deserializer.
    // I want a way to access the default spring deserializer for my User class.
    // How can I do that?

    //Special logic

    return deserializedUser;
  }

}

Ce dont j'ai besoin, c'est d'un moyen d'initialiser le désérialiseur par défaut afin de pouvoir pré-remplir mon POJO avant de lancer ma logique spéciale.

Lorsque l'on appelle deserialize à partir du deserializer personnalisé, il semble que la méthode soit appelée à partir du contexte actuel, quelle que soit la façon dont je construis la classe du serializer. À cause de l'annotation dans mon POJO. Cela provoque une exception Stack Overflow pour des raisons évidentes.

J'ai essayé d'initialiser un BeanDeserializer mais le processus est extrêmement complexe et je n'ai pas réussi à trouver la bonne façon de le faire. J'ai également essayé de surcharger la fonction AnnotationIntrospector en vain, en pensant que cela pourrait m'aider à ignorer l'annotation dans le fichier DeserializerContext . Finalement, il semble que j'ai pu avoir un peu de succès en utilisant JsonDeserializerBuilders bien que cela m'ait obligé à faire un peu de magie pour obtenir le contexte d'application de Spring. J'apprécierais toute chose qui pourrait me mener à une solution plus propre ; par exemple, comment puis-je construire un contexte de désérialisation sans lire le fichier JsonDeserializer annotation.

107voto

schummar Points 1491

Comme StaxMan l'a déjà suggéré, vous pouvez faire cela en écrivant un fichier BeanDeserializerModifier et l'enregistrer via SimpleModule . L'exemple suivant devrait fonctionner :

public class UserEventDeserializer extends StdDeserializer<User> implements ResolvableDeserializer
{
  private static final long serialVersionUID = 7923585097068641765L;

  private final JsonDeserializer<?> defaultDeserializer;

  public UserEventDeserializer(JsonDeserializer<?> defaultDeserializer)
  {
    super(User.class);
    this.defaultDeserializer = defaultDeserializer;
  }

  @Override public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException
  {
    User deserializedUser = (User) defaultDeserializer.deserialize(jp, ctxt);

    // Special logic

    return deserializedUser;
  }

  // for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer
  // otherwise deserializing throws JsonMappingException??
  @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
  {
    ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
  }

  public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException
  {
    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier()
    {
      @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer)
      {
        if (beanDesc.getBeanClass() == User.class)
          return new UserEventDeserializer(deserializer);
        return deserializer;
      }
    });

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(module);
    User user = mapper.readValue(new File("test.json"), User.class);
  }
}

24voto

Derek Cochran Points 160

Le site DeserializationContext a un readValue() que vous pouvez utiliser. Cela devrait fonctionner à la fois pour le désérialiseur par défaut et pour tout désérialiseur personnalisé que vous avez.

Assurez-vous d'appeler traverse() sur le JsonNode que vous voulez lire pour récupérer le JsonParser à passer à readValue() .

public class FooDeserializer extends StdDeserializer<FooBean> {

    private static final long serialVersionUID = 1L;

    public FooDeserializer() {
        this(null);
    }

    public FooDeserializer(Class<FooBean> t) {
        super(t);
    }

    @Override
    public FooBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        FooBean foo = new FooBean();
        foo.setBar(ctxt.readValue(node.get("bar").traverse(), BarBean.class));
        return foo;
    }

}

20voto

Gili Points 14674

J'ai trouvé une réponse à https://stackoverflow.com/a/51927577/14731 qui est beaucoup plus lisible que la réponse acceptée.

public User deserialize(JsonParser jp, DeserializationContext ctxt)
    throws IOException, JsonProcessingException {
        User user = jp.readValueAs(User.class);
         // some code
         return user;
      }

Il n'y a pas plus facile que ça.

10voto

Bill Points 71

S'il vous est possible de déclarer une classe d'utilisateur supplémentaire, vous pouvez l'implémenter en utilisant les annotations.

// your class
@JsonDeserialize(using = UserEventDeserializer.class)
public class User {
...
}

// extra user class
// reset deserializer attribute to default
@JsonDeserialize
public class UserPOJO extends User {
}

public class UserEventDeserializer extends StdDeserializer<User> {

  ...
  @Override
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = jp.ReadValueAs(UserPOJO.class);
    return deserializedUser;

    // or if you need to walk the JSON tree

    ObjectMapper mapper = (ObjectMapper) jp.getCodec();
    JsonNode node = oc.readTree(jp);
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = mapper.treeToValue(node, UserPOJO.class);

    return deserializedUser;
  }

}

9voto

StaxMan Points 34626

Il y a plusieurs façons de le faire, mais pour le faire correctement, il faut un peu plus de travail. Fondamentalement, vous ne pouvez pas utiliser les sous-classes, puisque les informations dont les désérialiseurs par défaut ont besoin sont construites à partir des définitions des classes.

Donc, ce que vous pouvez utiliser le plus probablement est de construire une BeanDeserializerModifier enregistrez cela via Module (utiliser SimpleModule ). Vous devez définir/supprimer modifyDeserializer et pour le cas spécifique où vous voulez ajouter votre propre logique (lorsque le type correspond), construire votre propre désérialiseur, passez le désérialiseur par défaut qui vous est donné. Et ensuite, dans deserialize() vous pouvez juste déléguer l'appel, prendre le résultat Object.

Alternativement, si vous devez créer et remplir l'objet, vous pouvez le faire en appelant la version surchargée de la fonction deserialize() qui prend le troisième argument ; l'objet à désérialiser.

Un autre moyen qui pourrait fonctionner (mais je n'en suis pas sûr à 100%) serait de spécifier Converter objet ( @JsonDeserialize(converter=MyConverter.class) ). Il s'agit d'une nouvelle fonctionnalité de Jackson 2.2. Dans votre cas, Converter ne convertirait pas réellement le type, mais simplifierait la modification de l'objet : mais je ne sais pas si cela vous permettrait de faire exactement ce que vous voulez, puisque le désérialiseur par défaut serait appelé d'abord, et seulement ensuite votre Converter .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X