Как создать пользовательский десериализатор в Jackson для универсального типа?
представьте себе следующую ситуацию:
class <T> Foo<T> {
....
}
class Bar {
Foo<Something> foo;
}
я хочу написать пользовательский десериализатор Джексон для Foo. Для этого (например, для десериализации Bar
классе Foo<Something>
свойства), мне нужно знать конкретный тип Foo<T>
, в Bar
, во время десериализации (например, мне нужно знать, что T
is Something
в этом случае случае).
Как написать такой десериализатор? Это должно быть возможно, так как Джексон это с типизированными коллекциями и картами.
пояснения:
кажется, есть 2 части для решения проблемы:
1) Получить объявленный тип собственности foo
внутри Bar
и используйте это для десериализации Foo<Somehting>
2) узнайте во время десериализации, что мы десериализуем свойство foo
внутри класса Bar
для успешного завершения шага 1)
Как выполнить 1 и 2 ?
2 ответов
вы можете реализовать пользовательский JsonDeserializer
для вашего общего типа, который также реализует ContextualDeserializer
.
например, предположим, что у нас есть следующий простой тип оболочки, содержащий общее значение:
public static class Wrapper<T> {
public T value;
}
теперь мы хотим десериализовать JSON, который выглядит так:
{
"name": "Alice",
"age": 37
}
в экземпляр класса, который выглядит так:
public static class Person {
public Wrapper<String> name;
public Wrapper<Integer> age;
}
реализация ContextualDeserializer
позволяет создать определенный десериализатор для каждого поля в Person
класс, основанный на параметрах универсального типа поля. Это позволяет десериализовать имя как строку, а возраст как целое число.
полный десериализатор выглядит следующим образом:
public static class WrapperDeserializer extends JsonDeserializer<Wrapper<?>> implements ContextualDeserializer {
private JavaType valueType;
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
JavaType wrapperType = property.getType();
JavaType valueType = wrapperType.containedType(0);
WrapperDeserializer deserializer = new WrapperDeserializer();
deserializer.valueType = valueType;
return deserializer;
}
@Override
public Wrapper<?> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
Wrapper<?> wrapper = new Wrapper<>();
wrapper.value = ctxt.readValue(parser, valueType);
return wrapper;
}
}
лучше посмотреть createContextual
вот сначала, как это будет называться Первым Джексоном. Мы читаем тип поля из BeanProperty
(например,Wrapper<String>
), а затем извлеките первый параметр универсального типа (например,String
). Мы затем создайте новый десериализатор и сохраните внутренний тип как valueType
.
один раз deserialize
вызывается на этом недавно созданном десериализаторе, мы можем просто попросить Джексона десериализовать значение как внутренний тип, а не как весь тип оболочки, и вернуть новый Wrapper
содержит десериализованное значение.
чтобы зарегистрировать этот пользовательский десериализатор, нам нужно создать модуль, который его содержит, и зарегистрировать этот модуль:
SimpleModule module = new SimpleModule()
.addDeserializer(Wrapper.class, new WrapperDeserializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
если затем мы пытаемся десериализовать пример JSON сверху, мы видим, что он работает так, как ожидалось:
Person person = objectMapper.readValue(json, Person.class);
System.out.println(person.name.value); // prints Alice
System.out.println(person.age.value); // prints 37
есть еще некоторые подробности о том, как контекстные десериализаторы работают в документация Джексон.
Если цель сама является универсальным типом, то свойство будет null, для этого вам нужно будет получить valueTtype из DeserializationContext:
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
if (property == null) { // context is generic
JMapToListParser parser = new JMapToListParser();
parser.valueType = ctxt.getContextualType().containedType(0);
return parser;
} else { // property is generic
JavaType wrapperType = property.getType();
JavaType valueType = wrapperType.containedType(0);
JMapToListParser parser = new JMapToListParser();
parser.valueType = valueType;
return parser;
}
}