Как вызвать десериализатор по умолчанию из пользовательского десериализатора в Джексоне

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

Это код, который у меня есть на данный момент.

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, прежде чем запустить свою специальную логику.

При вызове десериализации из пользовательского десериализатора. Кажется, что метод вызывается из текущего контекста, независимо от того, как я создаю класс сериализатора. Из-за аннотации в моем POJO. Это вызывает исключение по очевидным причинам.

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

Ответ 1

Как уже сообщал 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);
  }
}

Ответ 2

Есть несколько способов сделать это, но для этого нужно задействовать немного больше работы. В принципе, вы не можете использовать подкласс, так как требуемые десериализаторы, используемые по умолчанию, построены из определений классов.

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

В качестве альтернативы, если вы действительно должны создавать и заполнять объект, вы можете сделать это и вызвать перегруженную версию deserialize(), которая принимает третий аргумент; объект для десериализации в.

Другим способом, который может работать (но не на 100%), является указание объекта Converter (@JsonDeserialize(converter=MyConverter.class)). Это новая функция Jackson 2.2. В вашем случае Конвертер фактически не конвертирует тип, но упрощает модификацию объекта: но я не знаю, позволит ли это сделать именно то, что вы хотите, поскольку сначала будет вызываться десериализатор по умолчанию, а только ваш Converter.

Ответ 3

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

}

Ответ 4

Я нашел ответ по адресу fooobar.com/questions/16265871/... который гораздо более читабелен, чем принятый ответ.

public User deserialize(JsonParser jp, DeserializationContext ctxt)
    throws IOException, JsonProcessingException {
        JsonNode tree = jp.readTree(jp);

        // To call the default deserializer, simply invoke:
        User user = tree.get("user").traverse(jp.getCodec()).readValueAs(User.class);
        return user;
      }

Это действительно не становится легче, чем это.

Ответ 5

Если вы можете объявить дополнительный класс User, вы можете реализовать его, используя аннотации.

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

}

Ответ 6

Более простым решением для меня было просто добавить еще один bean из ObjectMapper и использовать его для десериализации объекта (спасибо https://stackoverflow.com/users/1032167/varren комментарий) - в моем случае мне было интересно либо десериализовать его id (int), либо весь объект fooobar.com/questions/87737/...

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 bean загрузочного приложения Spring в моем случае (например, для 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;
}

Ответ 7

Я не был в порядке с использованием BeanSerializerModifier, поскольку он заставлял объявлять некоторые поведенческие изменения в центральном ObjectMapper, а не сам пользовательский десериализатор, и на самом деле это параллельное решение для аннотации класса сущности с помощью JsonSerialize. Если вы чувствуете это аналогичным образом, вы можете оценить мой ответ здесь: fooobar.com/questions/87736/...

Ответ 8

В соответствии с тем, что Томаш Залуски предложил, в тех случаях, когда использование 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;
}

Ответ 9

Вы обязательно потерпите неудачу, если попытаетесь создать свой собственный десериализатор с нуля.

Вместо этого вам нужно получить (полностью настроенный) экземпляр десериализатора по умолчанию через пользовательский 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 чтобы 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;
    }
}

Ответ 10

Вот oneliner, использующий 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;
}

И, пожалуйста: на самом деле нет необходимости использовать какие-либо строковые значения или что-то еще. Вся необходимая информация предоставляется JsonParser, так что используйте ее.