Как вызвать десериализатор по умолчанию из пользовательского десериализатора в Джексоне
У меня проблема с пользовательским десериализатором в Джексоне. Я хочу получить доступ к сериализатору по умолчанию, чтобы заполнить объект, в который я десериализуюсь. После заполнения я сделаю несколько пользовательских вещей, но сначала я хочу десериализовать объект с поведением Джексона по умолчанию.
Это код, который у меня есть на данный момент.
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, так что используйте ее.