Як мені зателефонувати деріаріалізатор за замовчуванням від користувальницького десеріалізатора в Джексоні


105

У мене проблема в моєму користувальницькому десеріалізаторі в Джексоні. Я хочу отримати доступ до серіалізатора за замовчуванням, щоб заповнити об'єкт, в який я десеріалізуюсь. Після населення я зроблю деякі спеціальні речі, але спершу я хочу знецінити об'єкт за поведінкою Джексона за замовчуванням.

Це код, який у мене є на даний момент.

public class UserEventDeserializer extends StdDeserializer<User> {

  private static final long serialVersionUID = 7923585097068641765L;

  public UserEventDeserializer() {
    super(User.class);
  }

  @Override
  @Transactional
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {

    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    User deserializedUser = null;
    deserializedUser = super.deserialize(jp, ctxt, new User()); 
    // The previous line generates an exception java.lang.UnsupportedOperationException
    // Because there is no implementation of the deserializer.
    // I want a way to access the default spring deserializer for my User class.
    // How can I do that?

    //Special logic

    return deserializedUser;
  }

}

Що мені потрібно - це спосіб ініціалізувати деріаріалізатор за замовчуванням, щоб я міг попередньо заповнити POJO, перш ніж запустити свою спеціальну логіку.

Під час виклику deserialize зсередини користувальницького десеріалізатора. Здається, метод викликається з поточного контексту незалежно від того, як я будую клас серіалізатора. Через анотацію в моєму POJO. Це викликає виняток переповнення стека з очевидних причин.

Я спробував ініціалізувати, BeanDeserializerале процес надзвичайно складний, і мені не вдалося знайти правильний спосіб зробити це. Я також спробував перевантажити AnnotationIntrospectorбезрезультатно, думаючи, що це може допомогти мені ігнорувати анотацію в DeserializerContext. Нарешті, це, здається, я мав би певний успіх у використанні, JsonDeserializerBuildersхоча це вимагало від мене зробити якісь магічні речі, щоб отримати сприятливий контекст програми від Spring. Я вдячний за будь-яку річ, яка могла б привести мене до більш чіткого рішення, наприклад, як я можу побудувати контекст десеріалізації, не читаючи JsonDeserializerанотацію.


2
Ні. Ці підходи не допоможуть: проблема полягає в тому, що вам знадобиться повністю сконструйований деріаріалізатор за замовчуванням; і це вимагає, щоб хтось будувався, і тоді ваш десеріалізатор отримує доступ до нього. DeserializationContextце не те, що ви повинні або створити, або змінити; це буде надано ObjectMapper. AnnotationIntrospectorтакож не допоможе отримати доступ.
StaxMan

Як ти зрештою це зробив?
khituras

Гарне питання. Я не впевнений, але впевнений, що відповідь нижче мені допомогла. На даний момент у мене немає коду, який ми написали, якщо ви знайдете рішення, будь ласка, опублікуйте його тут для інших.
Пабло Джомер

Відповіді:


93

Як вже запропонував StaxMan, ви можете зробити це, написавши BeanDeserializerModifierта зареєструвавши це через SimpleModule. Наступний приклад повинен працювати:

public class UserEventDeserializer extends StdDeserializer<User> implements ResolvableDeserializer
{
  private static final long serialVersionUID = 7923585097068641765L;

  private final JsonDeserializer<?> defaultDeserializer;

  public UserEventDeserializer(JsonDeserializer<?> defaultDeserializer)
  {
    super(User.class);
    this.defaultDeserializer = defaultDeserializer;
  }

  @Override public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException
  {
    User deserializedUser = (User) defaultDeserializer.deserialize(jp, ctxt);

    // Special logic

    return deserializedUser;
  }

  // for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer
  // otherwise deserializing throws JsonMappingException??
  @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
  {
    ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
  }


  public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException
  {
    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier()
    {
      @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer)
      {
        if (beanDesc.getBeanClass() == User.class)
          return new UserEventDeserializer(deserializer);
        return deserializer;
      }
    });


    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(module);
    User user = mapper.readValue(new File("test.json"), User.class);
  }
}

Дякую! Я вже вирішив це по-іншому, але я буду розглядати ваше рішення, коли у мене буде більше часу.
Пабло Джомер

5
Чи є спосіб зробити те ж саме, але з a JsonSerializer? У мене є кілька серіалізаторів, але вони мають загальний код, тому я хочу його узагальнити. Я намагаюся безпосередньо зателефонувати в серіалізатор, але результат не розгортається в результаті JSON (кожен виклик серіалізатора створює новий об’єкт)
herau

1
@herau BeanSerializerModifier, ResolvableSerializerі ContextualSerializerце відповідні інтерфейси, які використовуються для серіалізації.
StaxMan

Чи застосовується це для контейнерів видання EE (Wildfly 10)? Я отримую JsonMappingException: (був java.lang.NullPointerException) (через посилання: java.util.ArrayList [0])
користувач1927033

Питання використовується, readTree()але відповідь - ні. У чому перевага такого підходу порівняно з тим, який опублікував Дерек Кохран ? Чи є спосіб зробити цю роботу readTree()?
Гілі

14

Я знайшов відповідь у ans, яка набагато читає, ніж прийнята відповідь.

    public User deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException {
            User user = jp.readValueAs(User.class);
             // some code
             return user;
          }

Це дійсно не стає простішим за це.


Привіт Гілі! Дякую за це, я сподіваюся, що люди знайдуть цю відповідь і встигнуть її підтвердити. Я більше не в змозі це зробити, бо наразі не можу прийняти відповідь. Якщо я побачу, що люди кажуть, що це можливе рішення, я, звичайно, спрямовую їх на це. Можливо також, що це неможливо для всіх версій. Все ж дякую за обмін.
Пабло Джомер

Не компілюється з Джексоном 2.9.9. JsonParser.readTree () не існує.
ccleve

@ccleve Виглядає як проста друкарська помилка. Виправлено.
Гілі

Можу підтвердити, що це працює з Jackson 2.10, дякую!
Стюарт Лейланд-Коул

2
Я не розумію, як це працює, це призводить до того StackOverflowError, що Джексон знову використовувати той же серіалізатор для User...
john16384

12

DeserializationContextМає readValue()метод , який ви можете використовувати. Це має працювати як для деріаріалізатора за замовчуванням, так і для будь-яких спеціальних десяріалізаторів.

Просто не забудьте зателефонувати traverse()на JsonNodeрівні, на який ви хочете прочитати, щоб отримати JsonParserперехід до нього readValue().

public class FooDeserializer extends StdDeserializer<FooBean> {

    private static final long serialVersionUID = 1L;

    public FooDeserializer() {
        this(null);
    }

    public FooDeserializer(Class<FooBean> t) {
        super(t);
    }

    @Override
    public FooBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        FooBean foo = new FooBean();
        foo.setBar(ctxt.readValue(node.get("bar").traverse(), BarBean.class));
        return foo;
    }

}

DeserialisationContext.readValue () не існує, тобто метод ObjectMapper
Педро Борхес

це рішення працює добре, проте вам може знадобитися зателефонувати nextToken (), якщо ви дезаріалізуєте клас значень, наприклад Date.class
revau.lt

9

Існує кілька способів зробити це, але зробити це правильно - це трохи більше роботи. В основному не можна використовувати підкласифікацію, оскільки необхідність десеріалізаторів інформації за замовчуванням будується з визначень класів.

Отже, найімовірніше, ви можете побудувати BeanDeserializerModifier, зареєструвати це через Moduleінтерфейс (використання SimpleModule). Вам потрібно визначити / змінити modifyDeserializer, а для конкретного випадку, коли ви хочете додати свою власну логіку (де відповідність типу), сконструюйте власний десеріалізатор, передайте даний деріаріалізатор за замовчуванням. І тоді в deserialize()методі ви можете просто делегувати виклик, взяти результат Object.

Крім того, якщо ви дійсно повинні створити і заповнити об'єкт, ви можете це зробити і викликати перевантажену версію, deserialize()яка займає третій аргумент; об'єкт, десеріалізувати в.

Інший спосіб, який може працювати (але не на 100% впевнений), - це вказати Converterobject ( @JsonDeserialize(converter=MyConverter.class)). Це нова функція Jackson 2.2. У вашому випадку Converter фактично не перетворить тип, а спростить модифікацію об'єкта: але я не знаю, чи це дозволить вам робити саме те, що ви хочете, оскільки деріаріалізатор за замовчуванням буде називатися спочатку, а вже потім вашим Converter.


Моя відповідь все ще стоїть: вам потрібно дозволити Джексону побудувати деріалізалізатор за замовчуванням для делегування; і повинні знайти спосіб «перекрити» його. BeanDeserializerModifierце обробник зворотного дзвінка, який дозволяє це.
StaxMan

7

Якщо ви можете оголосити додатковий клас користувача, ви можете реалізувати його, просто використовуючи примітки

// your class
@JsonDeserialize(using = UserEventDeserializer.class)
public class User {
...
}

// extra user class
// reset deserializer attribute to default
@JsonDeserialize
public class UserPOJO extends User {
}

public class UserEventDeserializer extends StdDeserializer<User> {

  ...
  @Override
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = jp.ReadValueAs(UserPOJO.class);
    return deserializedUser;

    // or if you need to walk the JSON tree

    ObjectMapper mapper = (ObjectMapper) jp.getCodec();
    JsonNode node = oc.readTree(jp);
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = mapper.treeToValue(node, UserPOJO.class);

    return deserializedUser;
  }

}

1
Так. Єдиний підхід, який працював на мене. Я отримував StackOverflowErrors через рекурсивний виклик десеріалізатора.
ccleve

3

Відповідно до того, що запропонував Томаш Залускі , у випадках, коли використання BeanDeserializerModifierнебажане, ви можете самостійно створити десяріалізатор за замовчуванням, використовуючи BeanDeserializerFactory, хоча необхідні додаткові налаштування. У контексті це рішення виглядатиме так:

public User deserialize(JsonParser jp, DeserializationContext ctxt)
  throws IOException, JsonProcessingException {

    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    User deserializedUser = null;

    DeserializationConfig config = ctxt.getConfig();
    JavaType type = TypeFactory.defaultInstance().constructType(User.class);
    JsonDeserializer<Object> defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, type, config.introspect(type));

    if (defaultDeserializer instanceof ResolvableDeserializer) {
        ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
    }

    JsonParser treeParser = oc.treeAsTokens(node);
    config.initialize(treeParser);

    if (treeParser.getCurrentToken() == null) {
        treeParser.nextToken();
    }

    deserializedUser = (User) defaultDeserializer.deserialize(treeParser, context);

    return deserializedUser;
}

Це працює як мрія з Джексоном 2.9.9. Він не страждає від StackOverflowError, як інший приклад.
meta1203

2

Ось онлінер за допомогою ObjectMapper

public MyObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    OMyObject object = new ObjectMapper().readValue(p, MyObject.class);
    // do whatever you want 
    return object;
}

І будь ласка: насправді не потрібно використовувати будь-яке значення String або щось інше. Всю необхідну інформацію надає JsonParser, тому використовуйте її.


1

Мені було не в порядку з використанням, BeanSerializerModifierоскільки це змушує оголошувати деякі зміни поведінки в центральному, ObjectMapperа не в самому користувальницькому десеріалізаторі, і насправді це паралельне рішення для анотування класу особи з JsonSerialize. Якщо ви відчуваєте це схожим чином, ви можете оцінити мою відповідь тут: https://stackoverflow.com/a/43213463/653539


1

Більш простим рішенням для мене було просто додати ще один боб ObjectMapperта використовувати його для десериалізації об’єкта (завдяки коментарю https://stackoverflow.com/users/1032167/varren ) - у моєму випадку я був зацікавлений або деріаріалізувати його id (int) або весь об’єкт https://stackoverflow.com/a/46618193/986160

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import org.springframework.context.annotation.Bean;

import java.io.IOException;

public class IdWrapperDeserializer<T> extends StdDeserializer<T> {

    private Class<T> clazz;

    public IdWrapperDeserializer(Class<T> clazz) {
        super(clazz);
        this.clazz = clazz;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        return mapper;
    }

    @Override
    public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        String json = jp.readValueAsTree().toString();
          // do your custom deserialization here using json
          // and decide when to use default deserialization using local objectMapper:
          T obj = objectMapper().readValue(json, clazz);

          return obj;
     }
}

для кожної сутності, якій потрібно пройти користувальницький десеріалізатор, нам потрібно налаштувати її в глобальному ObjectMapperпоєднанні програми Spring Boot App в моєму випадку (наприклад, для Category):

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
            mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
            mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
    SimpleModule testModule = new SimpleModule("MyModule")
            .addDeserializer(Category.class, new IdWrapperDeserializer(Category.class))

    mapper.registerModule(testModule);

    return mapper;
}

0

Якщо ви спробуєте створити свій власний десеріалізатор з нуля, вам не вдасться.

Натомість вам потрібно влаштувати (повністю налаштований) екземпляр десеріалізатора за замовчуванням через спеціальний BeanDeserializerModifier, а потім передати цей екземпляр у свій власний клас десяріалізатора:

public ObjectMapper getMapperWithCustomDeserializer() {
    ObjectMapper objectMapper = new ObjectMapper();

    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier() {
        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
                    BeanDescription beanDesc, JsonDeserializer<?> defaultDeserializer) 
            if (beanDesc.getBeanClass() == User.class) {
                return new UserEventDeserializer(defaultDeserializer);
            } else {
                return defaultDeserializer;
            }
        }
    });
    objectMapper.registerModule(module);

    return objectMapper;
}

Примітка: Ця реєстрація модуля замінює @JsonDeserializeпримітку, тобто Userклас або Userполя більше не повинні коментуватися цією приміткою.

Потім користувацький десеріалізатор повинен базуватися на DelegatingDeserializerделегуванні всіх методів, якщо ви не надаєте явну реалізацію:

public class UserEventDeserializer extends DelegatingDeserializer {

    public UserEventDeserializer(JsonDeserializer<?> delegate) {
        super(delegate);
    }

    @Override
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegate) {
        return new UserEventDeserializer(newDelegate);
    }

    @Override
    public User deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException {
        User result = (User) super.deserialize(p, ctxt);

        // add special logic here

        return result;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.