2015-07-08 2 views
6

Ich versuche, einen generischen Deserializer für alle Klassen zu implementieren, die sich von einer bestimmten abstrakten Klasse erstrecken oder eine bestimmte Schnittstelle implementieren. In diesem Beispiel verwende ich die Schnittstelle StringConvertible. Ich muss den konkreten Typ bestimmen und damit kann ich eine Instanz erstellen.Wie verwende ich Jacksons ContextualDeserializer für Root-Werte?

Eine old forum post by Programmer Bruce führte mich zu ContextDeserializer und es funktioniert, wenn die StringConvertible eine Eigenschaft in einer anderen Klasse ist.

Aber wenn ich die StringConvertible direkt deserialisieren möchte, kann ich keinen Weg finden, den konkreten Typ zu erhalten, weil der beanProperty Parameter null ist. Offenbar wird das erwartet, nach this question/answer im Jackson JSON Benutzergruppe: The only case where property should be null is when serializing a "root value", meaning the object instance passed directly to ObjectMapper's (or ObjectWriter's) writeValue() method -- in this case there simply isn't a referring property. But otherwise it should always be passed.

unter Hauptmethode Siehe als Beispiel für beide Fälle:

@JsonDeserialize(using = StringConvertibleDeserializer.class) 
public final class SomeStringConvertible implements StringConvertible { 

    private final String value; 

    public SomeStringConvertible(final String value) { 

     this.value = value; 
    } 

    @Override 
    @JsonValue 
    public String stringValue() { 

     return value; 
    } 
} 

public final class SomeWrapper { 

    public SomeStringConvertible stringConvertible; 

    public SomeWrapper() { 

    } 
} 


public class StringConvertibleDeserializer extends StdDeserializer<StringConvertible> implements ContextualDeserializer { 

    private final Class<? extends StringConvertible> targetClass; 

    StringConvertibleDeserializer() { 

     super(StringConvertible.class); 

     this.targetClass = null; 
    } 

    StringConvertibleDeserializer(final Class<? extends StringConvertible> targetClass) { 

     super(StringConvertible.class); 

     this.targetClass = targetClass; 
    } 

    @Override 
    public JsonDeserializer<?> createContextual(final DeserializationContext deserializationContext, @Nullable final BeanProperty beanProperty) 
                                       throws JsonMappingException { 

     final StringConvertibleDeserializer contextualDeserializer; 

     // ==== Determine target type ===== 
     final Class<? extends StringConvertible> targetClass; 
     JavaType type = beanProperty.getType(); // -> beanProperty is null when the StringConvertible type is a root value 
     targetClass = (Class<? extends StringConvertible>) type.getRawClass(); 

     // ==== Create contextual deserializer ===== 
     contextualDeserializer = new StringConvertibleDeserializer(targetClass); 

     // ==== Return ===== 
     return contextualDeserializer; 
    } 

    @Override 
    public StringConvertible deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException { 

     final StringConvertible value; 

     // ==== Create instance using the target type ===== 
     if (targetClass.equals(SomeStringConvertible.class)) 
      value = new SomeStringConvertible(jsonParser.getText()); 
     else { 
      throw new RuntimeException(); 
     } 

     // ==== Return ===== 
     return value; 
    } 

} 

public final class JacksonModule extends SimpleModule { 

    public JacksonModule() { 

     super(); 

     addDeserializer(StringConvertible.class, new StringConvertibleDeserializer()); 
    } 
} 

public final class Main { 

    public static void main(String[] args) { 

     final ObjectMapper mapper = new ObjectMapper(); 
     mapper.registerModule(new JacksonModule()); 

     final String wrappedValueJSON = "{\"stringConvertible\":\"hello world\"}"; 
     final String rootValueJSON = "\"hello world\""; 

     try { 
      mapper.readValue(wrappedValueJSON, SomeWrapper.class); // This works fine 
      mapper.readValue(rootValueJSON, SomeStringConvertible.class); // This causes a NPE in createContextual(...) because beanProperty is null 

     } catch (JsonProcessingException e) { 
      throw new RuntimeException(e); 
     } catch (IOException e) { 
      throw new RuntimeException(e); 
     } 
    } 
} 

Frage: Wie kann ich Typ konkreter bekommen im Falle eines Root-Wertes? Oder, wenn es eine bessere Lösung als diese gibt, was würden Sie stattdessen vorschlagen?

Antwort

9

Bei der Suche nach der Lösung StaxMan schlug ich this Github issue für jackson-databind, die genau das gleiche Problem anspricht. Als Reaktion fügte der Betreuer, ein Verfahren zum DeserializationContext in Version 2.5.0:

This turned out relatively easy to implement, so now there is: 

class DeserializationContext { 
    public JavaType getContextualType() { ... } 
} 
which will give expected type during call to createContextual(), including case of deserializers that are directly added via annotation. 

So, diese Arbeit in meinem Fall zu machen, musste ich einfach etwas Code in der Methode createContextual(...) ändern. Ich änderte sich dies:

// ==== Determine target type ===== 
    final Class<? extends StringConvertible> targetClass; 
    JavaType type = beanProperty.getType(); // -> beanProperty is null when the StringConvertible type is a root value 
    targetClass = (Class<? extends StringConvertible>) type.getRawClass(); 

Um dies:

// ==== Determine target type ===== 
final Class<? extends StringConvertible> targetClass; 
{ 
    // ==== Get the contextual type info ===== 
    final JavaType type; 
    if (beanProperty != null) 
     type = beanProperty.getType(); // -> beanProperty is null when the StringConvertible type is a root value 

    else { 
     type = deserializationContext.getContextualType(); 
    } 

    // ==== Get raw Class from type info ===== 
    targetClass = (Class<? extends StringConvertible>) type.getRawClass(); 
} 
+0

Eindrucksvolle Detektivarbeit hier - Danke für das Teilen! – StaxMan

+0

Vielen Dank für das Teilen!Dies war das letzte Puzzleteil für mich, als ich versuchte, dieses spezielle Problem zu lösen: https://gist.github.com/darylteo/a7be65b539c0d8d3ca0de94d96763f33 –

1

Einfache Antwort: Sie nicht.

Längere Antwort würde erklären, warum dies der Fall ist; aber das Wesentliche ist, dass (de) Serialisierer auf eine Weise registriert werden müssen, dass sie wissen, wofür sie registriert sind; Sie sind nicht dazu bestimmt, allgemeine Zwecke zu erfüllen, und sie müssen wissen, mit welchem ​​Typ sie umgehen. Sie müssen es bereits bei der Registrierung wissen.

Dies ist möglicherweise möglich, indem Sie die Registrierung von Deserializern ändern. Anstatt SimpleModule zu verwenden, können Sie Deserializers implementieren und Aufbau von Deserializer mit angegebenen Typinformation verarbeiten. Auf diese Weise wird es mehrere Deserializer-Instanzen geben, die mit den für die Registrierung verwendeten Typinformationen konfiguriert sind. Dies ist der Typ, der ebenfalls erwartet wird.

+1

ich eine einfache Lösung für meine Frage gefunden. Vielen Dank: Ihre Lösung wird sich für anspruchsvollere Fälle als nützlich erweisen. Für alle, die an einem Beispiel interessiert sind, ist es in jackson-datatype-guava gemacht: [GuavaDeserializers] (https://github.com/FasterXML/jackson-datatype-guava/blob/master/src/main/java/com/fasterxml/ jackson/datatype/guava/GuavaDeserializers.java), die innerhalb von [GuavaModule] registriert ist (https://github.com/FasterXML/jackson-datatype-guava/blob/master/src/main/java/com/fasterxml/jackson /datatype/guava/GuavaModule.java): 'context.addDeserializers (new GuavaDeserializers());' –

+0

Kein Problem, froh, dass Sie es herausgefunden haben. Beeindruckende Detektivarbeit - diese zusätzliche Methode hatte ich selbst vergessen. Es ist praktisch, müssen Sie daran denken. – StaxMan