2015-01-09 21 views
20

Ich habe eine Klasse, die wie folgtSerialize/Deserialize Map <String, Object> mit Jackson

public class MyClass { 
    private String val1; 
    private String val2; 
    private Map<String,Object> context; 
    // Appropriate accessors removed for brevity. 
    ... 
} 

von Objekt zu JSON Ich bin auf der Suche in der Lage sein, um die Hin- und Rückfahrt mit Jackson sieht und zurück . Ich kann das Objekt über feine serialisiert und erhalten die folgende Ausgabe:

{ 
    "val1": "foo", 
    "val2": "bar", 
    "context": { 
     "key1": "enumValue1", 
     "key2": "stringValue1", 
     "key3": 3.0 
    } 
} 

Das Problem, das ich in laufende bin ist, dass, da die Werte in der serialisierten Karte keine Typinformationen haben, sind sie nicht korrekt deserialisiert. Im obigen Beispiel sollte enumValue1 beispielsweise als Enumerationswert deserialisiert werden, wird aber stattdessen als String deserialisiert. Ich habe Beispiele dafür gesehen, welchen Typ ich auf verschiedene Dinge anwende, aber in meinem Szenario werde ich nicht wissen, was die Typen sind (sie werden vom Benutzer generierte Objekte sein, die ich vorher nicht kennen werde), also muss ich es sein kann die Typinformation mit dem Schlüsselwertpaar serialisieren. Wie kann ich das mit Jackson erreichen?

Für die Aufzeichnung verwende ich Jackson Version 2.4.2. Der Code, den ich die Rundfahrt zu testen, bin mit ist wie folgt:

@Test 
@SuppressWarnings("unchecked") 
public void testJsonSerialization() throws Exception { 
    // Get test object to serialize 
    T serializationValue = getSerializationValue(); 
    // Serialize test object 
    String json = mapper.writeValueAsString(serializationValue); 
    // Test that object was serialized as expected 
    assertJson(json); 
    // Deserialize to complete round trip 
    T roundTrip = (T) mapper.readValue(json, serializationValue.getClass()); 
    // Validate that the deserialized object matches the original one 
    assertObject(roundTrip); 
} 

Da dies ein Frühling basiertes Projekt wird der Mapper wie folgt erstellt werden:

@Configuration 
public static class SerializationConfiguration { 

    @Bean 
    public ObjectMapper mapper() { 
     Map<Class<?>, Class<?>> mixins = new HashMap<Class<?>, Class<?>>(); 
     // Add unrelated MixIns 
     .. 

     return new Jackson2ObjectMapperBuilder() 
       .featuresToDisable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS) 
       .dateFormat(new ISO8601DateFormatWithMilliSeconds()) 
       .mixIns(mixins) 
       .build(); 
    } 
} 
+4

Nun, natürlich wird ein 'Objekt' keine Typinformation haben; Es gibt keinen anderen Weg als 1. Erstellen Sie ein POJO für Ihre 'context' Instanzvariable (bevorzugt); oder 2. verwende keine 'Map ', sondern einen 'ObjectNode'. In jedem Fall ist dies ein primäres Beispiel für einen Code-Geruch. – fge

+0

Die Variable 'context' wird ähnlich wie' HttpSession' verwendet (sie repräsentiert Spring Batch's 'ExecutionContext', um genau zu sein), also würde ich es nicht für einen Geruch halten ... dafür ist eine 'Map' gedacht. Was ich nicht verstehe ist, warum Jackson kein Feld hinzufügen kann, das den Typ angibt. Wenn es den Wert hat, hat es die Klasse und kann daher den Typ bestimmen. Was vermisse ich? –

+0

Und wo würde es dieses Feld hinzufügen? Sie sagen ihm nur, das zu einem 'Objekt' zu deserialisieren. Der Vorteil mit einem 'ObjectNode' ist zumindest, dass Sie JSON direkt erhalten; Sie können also den Typ des Feldes mit allem abfragen, was 'JsonNode' helfen soll. – fge

Antwort

15

Ich denke, der einfachste Weg, erreichen, was Sie wollen, ist:

ObjectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 

Dies wird Typ Informationen in der serialisierten JSON hinzufügen.

Hier sind Sie ein laufendes Beispiel, dass Sie Spring anpassen müssen:

public class Main { 

    public enum MyEnum { 
     enumValue1 
    } 

    public static void main(String[] args) throws IOException { 
     ObjectMapper mapper = new ObjectMapper(); 

     MyClass obj = new MyClass(); 
     obj.setContext(new HashMap<String, Object>()); 

     obj.setVal1("foo"); 
     obj.setVal2("var"); 
     obj.getContext().put("key1", "stringValue1"); 
     obj.getContext().put("key2", MyEnum.enumValue1); 
     obj.getContext().put("key3", 3.0); 

     mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 
     String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); 

     System.out.println(json); 

     MyClass readValue = mapper.readValue(json, MyClass.class); 
     //Check the enum value was correctly deserialized 
     Assert.assertEquals(readValue.getContext().get("key2"), MyEnum.enumValue1); 
    } 

} 

Das Objekt wird in etwas ähnliches serialisiert werden:

[ "so_27871226.MyClass", { 
    "val1" : "foo", 
    "val2" : "var", 
    "context" : [ "java.util.HashMap", { 
    "key3" : 3.0, 
    "key2" : [ "so_27871226.Main$MyEnum", "enumValue1" ], 
    "key1" : "stringValue1" 
    } ] 
} ] 

Und wird wieder korrekt deserialisiert werden und die Behauptung wird bestehen.

Bytheway gibt es mehrere Möglichkeiten, dies zu tun, sehen Sie sich bitte https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization für weitere Informationen.

Ich hoffe, es wird helfen.

+0

Das ist genau das, was ich suche. Eine kleine Frage. Dies ist auf globaler Ebene. Gibt es eine Möglichkeit, dies auf Feldebene zu spezifizieren (Es gibt nur das eine Feld, mit dem ich dieses Problem habe, also wäre es nett, JSON nicht überall mit Typen zu bestreuen). Keine große Sache, aber kann nicht weh tun zu fragen. –

+1

Vielen Dank Herr, Sie haben Stunden meines Schmerzes beendet – James

+1

Ich glaube, dass das Aktivieren dieser Option unerwarteterweise das JSON beeinflussen kann, das von Ihrer Objektserialisierung erzeugt wird. Daher würde ich die Aktivierung einer globalen Option für diesen Fall nicht unterstützen. Was ich erwarten würde ist, dass, wenn ich eine polymorphe Instanz in eine Map/Collection hinzufügen die Typen Serialisierungsoptionen nicht ignoriert werden. Wenn das polymorphe Objekt jedoch unter einem Container-/Aggregationsobjekt existiert, gelten die Serialisierungsoptionen erwartungsgemäß. –

Verwandte Themen