2013-08-19 6 views
65

Ich habe ein Problem in meinem benutzerdefinierten Deserializer in Jackson. Ich möchte auf den Standard-Serializer zugreifen, um das Objekt zu füllen, in dem ich deserialisiert habe. Nach der Population werde ich einige benutzerdefinierte Dinge tun, aber zuerst möchte ich das Objekt mit dem Standard-Jackson-Verhalten deserialisieren.Wie rufe ich den Standard-Deserializer von einem benutzerdefinierten Deserializer in Jackson

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, die Standard-Deserializer zu initialisieren, damit ich meine POJO vorab füllen kann, bevor ich meine spezielle Logik starten.

Beim Aufrufen von deserialize aus dem benutzerdefinierten Deserializer Es scheint, dass die Methode aus dem aktuellen Kontext aufgerufen wird, egal wie ich die Serializer-Klasse erstellen. Wegen der Anmerkung in meinem POJO. Dies verursacht eine Stapelüberlaufausnahme aus offensichtlichen Gründen. Ich habe versucht, einen Beansserializer zu initialisieren, aber der Prozess ist extrem komplex und ich habe es nicht geschafft, den richtigen Weg zu finden. Ich habe auch versucht, den Annotations-Introspektor ohne Erfolg zu überladen, weil ich dachte, dass er mir helfen könnte, die Annotation im DeserializerContext zu ignorieren. Endlich scheint es, dass ich mit JsonDeserializerBuilder etwas Erfolg gehabt habe, obwohl ich dazu einige magische Dinge benötige, um den Anwendungskontext aus dem Frühjahr zu erhalten. Ich würde mich über jede Sache freuen, die mich zu einer saubereren Lösung führen könnte. Wie kann ich beispielsweise einen Deserialisierungskontext erstellen, ohne die JsonDeserializer-Annotation zu lesen?

+0

Nr Diese Ansätze nicht helfen: Das Problem besteht darin, dass Sie einen vollständig erstellten Standard-Deserializer benötigen. und das erfordert, dass man gebaut wird, und dann bekommt dein Deserializer Zugriff darauf. 'DeserializationContext' sollte nicht erstellt oder geändert werden. es wird von 'ObjectMapper' bereitgestellt. 'AnnotationIntrospector' hilft ebenfalls nicht beim Zugriff. – StaxMan

+0

Wie hast du es am Ende gemacht? – khituras

+0

Gute Frage. Ich bin mir nicht sicher, aber ich bin mir sicher, dass die Antwort unten mir geholfen hat. Ich bin derzeit nicht im Besitz des Codes, den wir geschrieben haben, wenn Sie eine Lösung finden, bitte posten Sie sie hier für andere. –

Antwort

66

Da StaxMan bereits vorgeschlagen hat, können Sie dies tun, indem Sie eine BeanDeserializerModifier schreiben und ü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); 
    } 
} 
+0

Danke! Ich habe das bereits auf andere Weise gelöst, aber ich werde in deine Lösung schauen, wenn ich mehr Zeit habe. –

+3

Gibt es eine Möglichkeit, dasselbe mit einem 'JsonSerializer' zu machen? Ich habe mehrere Serialisierer, aber sie haben Code auf Common, also möchte ich es generifizieren. Ich versuche, den Serializer direkt aufzurufen, aber das Ergebnis wird im JSON-Ergebnis nicht ausgepackt (jeder Aufruf von Serializer erstellt ein neues Objekt) – herau

+1

Danke, Mann, du hast mich gerettet! –

8

Es gibt mehrere Möglichkeiten, dies zu tun, aber um es richtig zu machen erfordert etwas mehr Arbeit. Grundsätzlich können Sie Sub-Classing nicht verwenden, da Standard-Deserializer von Information aus Klassendefinitionen erstellt werden.

Also, was Sie können am ehesten Gebrauch ist ein BeanDeserializerModifier zu konstruieren, registrieren, dass über Module Schnittstelle (SimpleModule verwenden). Sie müssen modifyDeserializer definieren/überschreiben, und für den speziellen Fall, in dem Sie Ihre eigene Logik hinzufügen möchten (wenn der Typ übereinstimmt), erstellen Sie Ihren eigenen Deserializer, übergeben Sie den Standard-Deserializer, den Sie erhalten. Und dann in deserialize() Methode können Sie nur Anruf delegieren, nehmen Sie das Ergebnisobjekt.

Alternativ, wenn Sie das Objekt tatsächlich erstellen und auffüllen müssen, können Sie dies tun und die überladene Version von deserialize() aufrufen, die das dritte Argument verwendet; Objekt zum Deserialisieren in. Ein anderer Weg, der funktionieren könnte (aber nicht 100% sicher) wäre Converter object (@JsonDeserialize(converter=MyConverter.class)). Dies ist eine neue Funktion von Jackson 2.2. In Ihrem Fall, Converter würde nicht tatsächlich konvertieren Typ, sondern vereinfachen das Objekt zu ändern: aber ich weiß nicht, ob das würde genau das tun, was Sie wollen, da der Standard-Deserializer würde zuerst aufgerufen werden, und nur dann Ihre Converter.

+0

Ich habe meine Frage bearbeitet, weil ich hier keinen Kommentar einfügen konnte. –

+0

Meine Antwort steht immer noch: Sie müssen Jackson den Standard-Deserializer erstellen lassen, an den delegiert werden soll; und muss einen Weg finden, um es "außer Kraft" zu setzen. 'BeanDeserializerModifier' ist der Callback-Handler, der dies erlaubt. – StaxMan

0

ich bei der Verwendung von BeanSerializerModifier nicht in Ordnung war, da es einige Verhaltensänderungen im Zentrum von ObjectMapper anstatt in benutzerdefinierten Deserializer selbst zu erklären zwingt und in der Tat ist es parallel Lösung Entitätsklasse mit JsonSerialize zu annotieren .Wenn Sie es auf ähnliche Weise fühlen, könnten Sie meine Antwort schätzen hier: https://stackoverflow.com/a/43213463/653539

1

Eine einfachere Lösung war für mich nur eine andere Bohne von ObjectMapper hinzufügen und dass das Objekt deserialisiert verwenden (dank https://stackoverflow.com/users/1032167/varren Kommentar) - in meinem Fall ich war daran interessiert, entweder zu seiner ID deserialisieren (int) oder das ganze 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 Einheit, die gehen durch individuelle Bedürfnisse werden Deserializer wir es in der globalen ObjectMapper Bohne des Frühlings-Boot konfigurieren müssen App in meinem Fall (zB 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; 
} 
0

Nach dem Vorbild von dem, was Tomáš Záluský has suggested, in Fällen, in denen BeanDeserializerModifier Verwendung ist unerwünscht Sie eine Standard konstruieren Deserializer selbst BeanDeserializerFactory verwenden, obwohl es einige zusätzliche Einrichtung erforderlich ist. Im Zusammenhang wäre diese Lösung wie 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

Wenn es möglich ist, für Sie zusätzliche Benutzerklasse zu erklären, dann können Sie es implementieren nur Anmerkungen mit

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

} 
Verwandte Themen