Джексон: десериализовать в карту <String, Object> с правильным типом для каждого значения
У меня есть класс, который выглядит следующим образом
public class MyClass {
private String val1;
private String val2;
private Map<String,Object> context;
// Appropriate accessors removed for brevity.
...
}
Я ищу, чтобы иметь возможность совершить кругосветное путешествие с Джексоном с объекта на JSON и обратно. Я могу сериализовать объект выше штрафа и получить следующий вывод:
{
"val1": "foo",
"val2": "bar",
"context": {
"key1": "enumValue1",
"key2": "stringValue1",
"key3": 3.0
}
}
Проблема, с которой я сталкиваюсь, заключается в том, что, поскольку значения в сериализованной карте не имеют никакой информации о типе, они не десериализованы правильно. Например, в примере выше enumValue1 следует десериализовать как значение перечисления, но вместо этого десериализуется как строка. Я видел примеры для того, чтобы основывать какой тип на разных вещах, но в моем сценарии я не буду знать, какие типы (они будут объектами, создаваемыми пользователем, которые я не буду знать заранее), поэтому мне нужно быть способный сериализовать информацию о типе с помощью пары значений ключа. Как я могу выполнить это с Джексоном?
Для записи я использую версию Jackson 2.4.2. Код, который я использую для проверки поездки туда и обратно, выглядит следующим образом:
@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);
}
Поскольку это проект на основе Spring, создатель создается следующим образом:
@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();
}
}
Ответы
Ответ 1
Я думаю, что самый простой способ добиться того, что вы хотите, - это:
ObjectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
Это добавит информацию о типе в сериализованном json.
Здесь вы работаете, и вам нужно будет адаптироваться к 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);
}
}
Объект будет сериализован как нечто похожее на:
[ "so_27871226.MyClass", {
"val1" : "foo",
"val2" : "var",
"context" : [ "java.util.HashMap", {
"key3" : 3.0,
"key2" : [ "so_27871226.Main$MyEnum", "enumValue1" ],
"key1" : "stringValue1"
} ]
} ]
И будет десериализован обратно правильно, и утверждение пройдет.
Кстати, есть больше способов сделать это, посмотрите на https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization для получения дополнительной информации.
Надеюсь, это поможет.