2016-11-16 3 views
1

Wir haben einen Dienst, der derzeit JSON verbraucht. Wir wollen diesen JSON leicht umstrukturieren (eine Eigenschaft um eine Ebene nach oben verschieben), aber auch eine elegante Migration implementieren, so dass unser Dienst sowohl die alte Struktur als auch die neue Struktur verarbeiten kann. Wir verwenden Jackson für die JSON-Deserialisierung.Strukturieren Sie JSON vor dem Deserialisieren mit Jackson

Wie restrukturieren wir JSON vor der Deserialisierung mit Jackson?

Hier ist ein MCVE.

Nehmen wir unsere alten JSON sieht wie folgt aus:

{"reference": {"number" : "one", "startDate" : [2016, 11, 16], "serviceId" : "0815"}} 

Wir wollen serviceId eine Ebene nach oben:

{"reference": {"number" : "one", "startDate" : [2016, 11, 16]}, "serviceId" : "0815"} 

Dies sind die Klassen, die wir von beide alt einen neuen deserialisieren möchten JSONs:

public final static class Container { 

     public final Reference reference; 

     public final String serviceId; 

     @JsonCreator 
     public Container(@JsonProperty("reference") Reference reference, @JsonProperty("serviceId") String serviceId) { 
      this.reference = reference; 
      this.serviceId = serviceId; 
     } 

    } 

    public final static class Reference { 

     public final String number; 

     public final LocalDate startDate; 

     @JsonCreator 
     public Reference(@JsonProperty("number") String number, @JsonProperty("startDate") LocalDate startDate) { 
      this.number = number; 
      this.startDate = startDate; 
     } 
    } 

Wir auf ly wollen serviceId in Container, nicht in beiden Klassen.

Was ich Arbeits haben, ist die folgende Deserializer:

public static class ServiceIdMigratingContainerDeserializer extends JsonDeserializer<Container> { 

    private final ObjectMapper objectMapper; 

    { 
     objectMapper = new ObjectMapper(); 
     objectMapper.registerModule(new JavaTimeModule()); 
     objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true); 
     objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 
    } 

    @Override 
    public Container deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { 
     ObjectNode node = p.readValueAsTree(); 
     migrate(node); 
     return objectMapper.treeToValue(node, Container.class); 
    } 

    private void migrate(ObjectNode containerNode) { 
     TreeNode referenceNode = containerNode.get("reference"); 
     if (referenceNode != null && referenceNode.isObject()) { 
      TreeNode serviceIdNode = containerNode.get("serviceId"); 
      if (serviceIdNode == null) { 
       TreeNode referenceServiceIdNode = referenceNode.get("serviceId"); 
       if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) { 
        containerNode.set("serviceId", (ValueNode) referenceServiceIdNode); 
       } 
      } 
     } 
    } 
} 

Dieser Deserializer ruft zuerst den Baum, manipuliert sie und Deserializer es dann eine eigene Instanz von ObjectMapper verwenden. Es funktioniert, aber wir mögen es wirklich nicht, dass wir hier eine andere Instanz von ObjectMapper haben. Wenn wir es nicht erstellen und irgendwie die systemweite Instanz von ObjectMapper verwenden, erhalten wir einen unendlichen Zyklus, denn wenn wir versuchen, objectMapper.treeToValue aufzurufen, wird unser Deserializer rekursiv aufgerufen. Das funktioniert (mit einer eigenen Instanz von ObjectMapper), aber es ist keine optimale Lösung.

Eine andere Methode, die ich versucht habe, wurde eine BeanDeserializerModifier und eine eigene JsonDeserializer welche „Wraps“ die Standard-Serializer mit:

public static class ServiceIdMigrationBeanDeserializerModifier extends BeanDeserializerModifier { 

    @Override 
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, 
      JsonDeserializer<?> defaultDeserializer) { 
     if (beanDesc.getBeanClass() == Container.class) { 
      return new ModifiedServiceIdMigratingContainerDeserializer((JsonDeserializer<Container>) defaultDeserializer); 
     } else { 
      return defaultDeserializer; 
     } 
    } 
} 

public static class ModifiedServiceIdMigratingContainerDeserializer extends JsonDeserializer<Container> { 

    private final JsonDeserializer<Container> defaultDeserializer; 

    public ModifiedServiceIdMigratingContainerDeserializer(JsonDeserializer<Container> defaultDeserializer) { 
     this.defaultDeserializer = defaultDeserializer; 
    } 

    @Override 
    public Container deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { 
     ObjectNode node = p.readValueAsTree(); 
     migrate(node); 
     return defaultDeserializer.deserialize(new TreeTraversingParser(node, p.getCodec()), ctxt); 
    } 

    private void migrate(ObjectNode containerNode) { 
     TreeNode referenceNode = containerNode.get("reference"); 
     if (referenceNode != null && referenceNode.isObject()) { 
      TreeNode serviceIdNode = containerNode.get("serviceId"); 
      if (serviceIdNode == null) { 
       TreeNode referenceServiceIdNode = referenceNode.get("serviceId"); 
       if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) { 
        containerNode.set("serviceId", (ValueNode) referenceServiceIdNode); 
       } 
      } 
     } 
    } 
} 

„Wrapping“ eine Standard-Deserializer scheint ein besserer Ansatz zu sein, aber dies nicht gelingt mit ein NPE:

java.lang.NullPointerException 
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:157) 
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:150) 
    at de.db.vz.rikernpushadapter.migration.ServiceIdMigrationTest$ModifiedServiceIdMigratingContainerDeserializer.deserialize(ServiceIdMigrationTest.java:235) 
    at de.db.vz.rikernpushadapter.migration.ServiceIdMigrationTest$ModifiedServiceIdMigratingContainerDeserializer.deserialize(ServiceIdMigrationTest.java:1) 
    at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1623) 
    at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1217) 
    at ... 

der ganze MCVE-Code ist in dem folgenden PasteBin. Es ist ein einklassiger Testfall, der beide Ansätze demonstriert. Die migratesViaDeserializerModifierAndUnmarshalsServiceId schlägt fehl.

So lässt mich das mit einer Frage:

Wie strukturieren wir JSON, bevor sie mit Jackson Deserialisierung?

Antwort

0

In den besten Traditionen, direkt nach dem Posten der Frage, habe ich es geschafft, dies zu lösen.

Zwei Dinge:

  • hatte ich newJsonParser.nextToken(); zu tun NPE zu vermeiden.
  • Extend DelegatingDeserializer

Hier ist ein Arbeits DelegatingDeserializer:

public static Klasse ModifiedServiceIdMigratingContainerDeserializer erweitert DelegatingDeserializer {

public ModifiedServiceIdMigratingContainerDeserializer(JsonDeserializer<?> defaultDeserializer) { 
     super(defaultDeserializer); 
    } 

    @Override 
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) { 
     return new ModifiedServiceIdMigratingContainerDeserializer(newDelegatee); 
    } 

    @Override 
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { 
     return super.deserialize(restructure(p), ctxt); 
    } 

    @Override 
    public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException, 
      JsonProcessingException { 
     return super.deserialize(restructure(p), ctxt, intoValue); 
    } 

    public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) 
      throws IOException, JsonProcessingException { 
     return super.deserializeWithType(restructure(jp), ctxt, typeDeserializer); 
    } 

    public JsonParser restructure(JsonParser p) throws IOException, JsonParseException { 
     final ObjectNode node = p.readValueAsTree(); 
     migrate(node); 
     final TreeTraversingParser newJsonParser = new TreeTraversingParser(node, p.getCodec()); 
     newJsonParser.nextToken(); 
     return newJsonParser; 
    } 

    private void migrate(ObjectNode containerNode) { 
     TreeNode referenceNode = containerNode.get("reference"); 
     if (referenceNode != null && referenceNode.isObject()) { 
      TreeNode serviceIdNode = containerNode.get("serviceId"); 
      if (serviceIdNode == null) { 
       TreeNode referenceServiceIdNode = referenceNode.get("serviceId"); 
       if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) { 
        containerNode.set("serviceId", (ValueNode) referenceServiceIdNode); 
       } 
      } 
     } 
    } 
} 
Verwandte Themen