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


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

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

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, но процесс чрезвычайно сложен, и мне не удалось найти правильный способ сделать это. Я также попытался перегрузить аннотацию интроспектора безрезультатно, думая, что это может помочь мне игнорировать аннотацию в DeserializerContext. Наконец, это швы, которые я мог бы иметь некоторый успех с помощью JsonDeserializerBuilders, хотя это потребовало от меня сделать некоторые волшебные вещи, чтобы получить доступ к контексту приложения с весны. Я был бы признателен за любую вещь, которая могла бы привести меня к более чистому решению, например, как я могу построить контекст десериализации без чтения аннотации JsonDeserializer.

7 72

7 ответов:

как 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);
  }
}

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

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

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

другой способ, который может работать (но не на 100% уверен) было бы указать

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

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

}

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

}

более простым решением для меня было просто добавить еще один боб 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 в моем случае (например, для 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;
}

Я не был в порядке с помощью BeanSerializerModifier Так как это заставляет объявить некоторые поведенческие изменения в Центральном ObjectMapper а не в самом пользовательском десериализаторе, и на самом деле это параллельное решение для аннотирования класса сущностей с помощью JsonSerialize. Если вы чувствуете это подобным образом, вы можете оценить мой ответ здесь:https://stackoverflow.com/a/43213463/653539

по линии чего Томаш Залуски предложил, в случаях, когда с помощью 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();
    JsonDeserializer<Object> defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, User.class, config.introspect(User.class));

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