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