Как создать пользовательский десериализатор в 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;
    }
}