it-swarm.com.de

Wie rufe ich den Standard-Deserializer von einem benutzerdefinierten Deserializer in Jackson auf?

Ich habe ein Problem mit meinem benutzerdefinierten Deserializer in Jackson. Ich möchte auf den Standard-Serialisierer zugreifen, um das Objekt zu befüllen, in das ich deserialisiert werde. Nach der Bevölkerung werde ich einige benutzerdefinierte Dinge tun, aber zuerst möchte ich das Objekt mit dem Standardverhalten von Jackson deserialisieren.

Dies ist der Code, den ich im Moment habe.

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

}

Was ich brauche, ist eine Möglichkeit, den Standard-Deserializer zu initialisieren, damit ich meinen POJO vorbelegen kann, bevor ich mit meiner speziellen Logik beginne.

Beim Aufruf von deserialize innerhalb des benutzerdefinierten Deserialisierers wird die Methode aus dem aktuellen Kontext aufgerufen, unabhängig davon, wie die Serialisiererklasse erstellt wird. Wegen der Anmerkung in meinem POJO. Dies führt aus naheliegenden Gründen zu einer Ausnahme für den Stapelüberlauf. Ich habe versucht, einen Beandeserializer zu initialisieren, aber der Prozess ist äußerst komplex und ich habe es nicht geschafft, den richtigen Weg zu finden. Ich habe auch versucht, den Annotation-Introspector ohne Erfolg zu überladen, weil ich dachte, er könnte mir dabei helfen, die Annotation im DeserializerContext zu ignorieren. Schließlich scheint es, als hätte ich mit JsonDeserializerBuilders einige Erfolge erzielt, obwohl ich dazu einige magische Sachen machen musste, um den Anwendungskontext vom Frühjahr an zu verstehen. Ich würde mich über alles freuen, was zu einer saubereren Lösung führen könnte, zum Beispiel, wie ich einen Deserialisierungskontext erstellen kann, ohne die JsonDeserializer-Annotation zu lesen.

79
Pablo Jomer

Wie StaxMan bereits vorgeschlagen hat, können Sie dies tun, indem Sie eine BeanDeserializerModifier schreiben und diese über SimpleModule registrieren. Das folgende Beispiel sollte funktionieren:

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);
  }
}
75
schummar

Es gibt verschiedene Möglichkeiten, dies zu tun, aber um es richtig zu machen, ist etwas mehr Arbeit erforderlich. Grundsätzlich können Sie keine Unterklassen verwenden, da die Informationen, die Deserialisierer standardmäßig benötigen, aus Klassendefinitionen erstellt werden.

Sie können also höchstwahrscheinlich ein BeanDeserializerModifier erstellen und dieses über die Module-Schnittstelle registrieren (verwenden Sie SimpleModule). Sie müssen modifyDeserializer definieren/überschreiben und für den speziellen Fall, in dem Sie Ihre eigene Logik hinzufügen möchten (wobei der Typ übereinstimmt), einen eigenen Deserialisierer erstellen und den vorgegebenen Deserialisierer übergeben. Und dann können Sie in der deserialize() -Methode einfach den Aufruf delegieren und das Ergebnisobjekt nehmen.

Wenn Sie das Objekt tatsächlich erstellen und füllen müssen, können Sie dies alternativ tun und die überladene Version von deserialize() aufrufen, die das dritte Argument übernimmt. Objekt zu deserialisieren.

Eine andere Möglichkeit, die funktionieren könnte (aber nicht 100% sicher ist), wäre die Angabe des Objekts Converter (@JsonDeserialize(converter=MyConverter.class)). Dies ist eine neue Funktion von Jackson 2.2. In Ihrem Fall würde Converter den Typ nicht konvertieren, sondern das Objekt vereinfachen. Ich weiß jedoch nicht, ob Sie damit genau das tun können, was Sie möchten, da der Standard-Deserializer zuerst aufgerufen wird und erst dann Ihr Converter.

8
StaxMan

Die DeserializationContext hat eine readValue()-Methode, die Sie verwenden können. Dies sollte sowohl für den Standard-Deserializer als auch für Ihre benutzerdefinierten Deserializer funktionieren.

Rufen Sie einfach traverse() auf der JsonNode-Ebene auf, die Sie lesen möchten, um die JsonParser abzurufen und an readValue() zu übergeben.

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

}
6
Derek Cochran

Wenn Sie eine zusätzliche Benutzerklasse deklarieren können, können Sie sie nur mithilfe von Anmerkungen implementieren

// 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;
  }

}
4
Bill

Ich habe unter https://stackoverflow.com/a/51927577/14731 eine Antwort gefunden, die viel lesbarer ist als die akzeptierte Antwort.

public User deserialize(JsonParser jp, DeserializationContext ctxt)
    throws IOException, JsonProcessingException {
        JsonNode tree = jp.readTree(jp);

        // To call the default deserializer, simply invoke:
        User user = tree.get("user").traverse(jp.getCodec()).readValueAs(User.class);
        return user;
      }

Einfacher geht es wirklich nicht.

1
Gili

Eine einfachere Lösung für mich war, einfach eine weitere Bean von ObjectMapper hinzuzufügen und das Objekt zu deserialisieren (dank https://stackoverflow.com/users/1032167/varren comment) - in meinem Fall war ich interessiert entweder deserialisieren zu seiner id (einem int) oder dem gesamten Objekt https://stackoverflow.com/a/46618193/986160

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.springframework.context.annotation.Bean;

import Java.io.IOException;

public class IdWrapperDeserializer<T> extends StdDeserializer<T> {

    private Class<T> clazz;

    public IdWrapperDeserializer(Class<T> clazz) {
        super(clazz);
        this.clazz = clazz;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        return mapper;
    }

    @Override
    public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        String json = jp.readValueAsTree().toString();
          // do your custom deserialization here using json
          // and decide when to use default deserialization using local objectMapper:
          T obj = objectMapper().readValue(json, clazz);

          return obj;
     }
}

für jede Entität, die einen benutzerdefinierten Deserializer durchlaufen muss, müssen wir sie in meinem Fall in der globalen ObjectMapper-Bean der Spring Boot App konfigurieren (z. B. für Category):

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
            mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
            mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
    SimpleModule testModule = new SimpleModule("MyModule")
            .addDeserializer(Category.class, new IdWrapperDeserializer(Category.class))

    mapper.registerModule(testModule);

    return mapper;
}
1

Sie sind zum Scheitern verurteilt, wenn Sie versuchen, Ihren benutzerdefinierten Deserializer von Grund auf neu zu erstellen. Stattdessen müssen Sie die (vollständig konfigurierte) Standard-Deserializer-Instanz über eine benutzerdefinierte BeanDeserializerModifier abrufen und diese Instanz an Ihre benutzerdefinierte Deserializer-Klasse übergeben:

public ObjectMapper getMapperWithCustomDeserializer() {
    ObjectMapper objectMapper = new ObjectMapper();

    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier() {
        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
                    BeanDescription beanDesc, JsonDeserializer<?> defaultDeserializer) 
            if (beanDesc.getBeanClass() == User.class) {
                return new UserEventDeserializer(defaultDeserializer);
            } else {
                return defaultDeserializer;
            }
        }
    });
    objectMapper.registerModule(module);

    return objectMapper;
}

Hinweis: Diese Modulregistrierung ersetzt die Anmerkung @JsonDeserialize, d. H. Die Felder User class oder User sollten mit dieser Anmerkung nicht mehr kommentiert werden.

Der benutzerdefinierte Deserializer sollte dann auf einer DelegatingDeserializer basieren, damit alle Methoden delegieren können, sofern Sie keine explizite Implementierung angeben:

public class UserEventDeserializer extends DelegatingDeserializer {

    public UserEventDeserializer(JsonDeserializer<?> delegate) {
        super(delegate);
    }

    @Override
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegate) {
        return new UserEventDeserializer(newDelegate);
    }

    @Override
    public User deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException {
        User result = (User) super.deserialize(p, ctxt);

        // add special logic here

        return result;
    }
}
0
oberlies

Ich war mit BeanSerializerModifier nicht einverstanden, da es zwingt, Verhaltensänderungen in zentraler ObjectMapper und nicht in benutzerdefiniertem Deserializer selbst zu deklarieren. In der Tat ist es eine parallele Lösung, Entitätsklasse mit JsonSerialize zu kommentieren. Wenn Sie es ähnlich empfinden, können Sie meine Antwort hier schätzen: https://stackoverflow.com/a/43213463/653539

0

Hier ist ein Oneliner mit ObjectMapper

public MyObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    OMyObject object = new ObjectMapper().readValue(p, MyObject.class);
    // do whatever you want 
    return object;
}

Und bitte: Es ist wirklich nicht erforderlich, einen String-Wert oder etwas anderes zu verwenden. Alle benötigten Informationen werden von JsonParser bereitgestellt. Verwenden Sie sie daher.

0
kaiser

Nach dem, was Tomáš Záluský vorgeschlagen hat , können Sie in Fällen, in denen BeanDeserializerModifier unerwünscht ist, einen Standard-Deserializer mit BeanDeserializerFactory selbst erstellen, obwohl einige zusätzliche Einstellungen erforderlich sind. Im Kontext würde diese Lösung so aussehen:

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

    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    User deserializedUser = null;

    DeserializationConfig config = ctxt.getConfig();
    JsonDeserializer<Object> defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, User.class, config.introspect(User.class));

    if (defaultDeserializer instanceof ResolvableDeserializer) {
        ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
    }

    JsonParser treeParser = oc.treeAsTokens(node);
    config.initialize(treeParser);

    if (treeParser.getCurrentToken() == null) {
        treeParser.nextToken();
    }

    deserializedUser = (User) defaultDeserializer.deserialize(treeParser, context);

    return deserializedUser;
}
0
hopper