it-swarm.com.ru

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

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

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

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

79
Pablo Jomer

Как уже предложил 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);
  }
}
75
schummar

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

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

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

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

8
StaxMan

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

}
6
Derek Cochran

Если вы можете объявить дополнительный класс 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;
  }

}
4
Bill

Я нашел ответ по адресу https://stackoverflow.com/a/51927577/14731 , который гораздо более читабелен, чем принятый ответ.

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

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

1
Gili

Для меня более простым решением было просто добавить еще один компонент ObjectMapper и использовать его для десериализации объекта (благодаря https://stackoverflow.com/users/1032167/varren comment) - в моем случае мне было интересно либо десериализовать его идентификатор (целое число) или весь объект 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;
}
1
Michail Michailidis

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

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

0
Tomáš Záluský

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