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