web-dev-qa-db-fra.com

Jackson: désérialiser en une carte <chaîne, objet> avec un type correct pour chaque valeur

J'ai une classe qui ressemble à ceci

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

Je cherche à pouvoir faire l'aller-retour avec Jackson d'objet en JSON et retour. Je peux sérialiser l'objet ci-dessus très bien et recevoir la sortie suivante:

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

Le problème que je rencontre est que, puisque les valeurs de la carte sérialisée n'ont pas d'informations de type, elles ne sont pas désérialisées correctement. Par exemple, dans l'exemple ci-dessus, enumValue1 doit être désérialisé en tant que valeur enum mais est plutôt désérialisé en tant que chaîne. J'ai vu des exemples pour baser ce type sur une variété de choses, mais dans mon scénario, je ne saurai pas quels sont les types (ce seront des objets générés par l'utilisateur que je ne saurai pas à l'avance), donc je dois être capable de sérialiser les informations de type avec la paire valeur/clé. Comment puis-je accomplir cela avec Jackson?

Pour mémoire, j'utilise la version 2.4.2 de Jackson. Le code que j'utilise pour tester l'aller-retour est le suivant:

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

Comme il s'agit d'un projet basé sur Spring, le mappeur est créé comme suit:

@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();
    }
}
30
Michael Minella

Je pense que la façon la plus simple de réaliser ce que vous voulez est d'utiliser:

ObjectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

Cela ajoutera des informations de type dans le json sérialisé.

Voici un exemple en cours d'exécution, que vous devrez adapter à Spring:

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

}

L'objet sera sérialisé en quelque chose de similaire à:

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

Et sera désérialisé correctement, et l'assertion passera.

Par ailleurs, il existe d'autres façons de le faire, veuillez consulter https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization pour plus d'informations.

J'espère que cela aidera.

20
fonkap