如何从 Jackson 中的自定义反序列化程序调用默认的反序列化程序

我在 Jackson 的自定义反序列化程序中出现了问题。我想访问默认的序列化程序来填充要反序列化的对象。在填充之后,我将执行一些自定义操作,但首先我想用默认的 Jackson 行为反序列化对象。

这是我现在的代码。

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 中有注释。由于显而易见的原因,这会导致 Stack Overflow 异常。

我曾经尝试过初始化 BeanDeserializer,但是这个过程非常复杂,我还没有找到正确的方法。我还尝试过重载 AnnotationIntrospector,但没有用,认为它可能有助于我忽略 DeserializerContext中的注释。最后,我似乎在使用 JsonDeserializerBuilders方面取得了一些成功,尽管这需要我做一些神奇的工作来从 Spring 获得应用程序上下文。如果有任何东西能够引导我找到一个更清晰的解决方案,我将非常感激,例如,我可以在不阅读 JsonDeserializer注释的情况下构建一个反序列化上下文吗。

75850 次浏览

有几种方法可以做到这一点,但要做好需要做更多的工作。基本上不能使用子类化,因为默认反序列化器需要的信息是从类定义构建的。

因此,您最有可能使用的是构造一个 BeanDeserializerModifier,通过 Module接口注册它(使用 SimpleModule)。您需要定义/覆盖 modifyDeserializer,对于需要添加自己的逻辑(类型匹配)的特定情况,构造自己的反序列化器,传递给您的默认反序列化器。 然后在 deserialize()方法中,你可以委托调用,取得结果 Object。

或者,如果必须实际创建和填充对象,则可以这样做,并调用重载版本的 deserialize(),该版本接受第三个参数; 要反序列化的对象。

另一种可行的方法(但不是100% 确定)是指定 Converter对象(@JsonDeserialize(converter=MyConverter.class))。这是 Jackson 2.2的新功能。 在您的例子中,Converter 实际上不会转换类型,而是简化对象的修改: 但是我不知道这是否能让您完全按照您想要的那样做,因为默认的反序列化器将首先被调用,然后才是您的 Converter

正如 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);
}
}

我不喜欢使用 BeanSerializerModifier,因为它强制在中央 ObjectMapper中声明一些行为变化,而不是在自定义反序列化程序本身中,实际上它是用 JsonSerialize注释实体类的并行解决方案。如果你也有类似的感觉,你可能会喜欢我在这里的回答: https://stackoverflow.com/a/43213463/653539

对我来说,一个更简单的解决方案是添加另一个 ObjectMapper bean 并使用它来反序列化对象(感谢 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;
}
}

对于每个需要通过自定义反序列化器的实体,我们需要在 Spring Boot App 的全局 ObjectMapper bean 中配置它(例如 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;
}

沿着 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();
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;
}

如果可以声明额外的 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;
}


}

DeserializationContext有一个可以使用的 readValue()方法。这对于默认反序列化器和您拥有的任何自定义反序列化器都适用。

只需要确保在您想要读取的 JsonNode级别上调用 traverse(),以检索要传递给 readValue()JsonParser

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


}

我在 https://stackoverflow.com/a/51927577/14731上找到了一个答案,它比已接受的答案更具可读性。

public User deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
User user = jp.readValueAs(User.class);
// some code
return user;
}

没有比这更容易的了。

如果尝试从头创建自定义反序列化程序,那么一定会失败。

相反,您需要通过自定义 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;
}
}

下面是使用默认 ObjectMapper 的简短解决方案

private static final ObjectMapper MAPPER = new ObjectMapper(); // use default mapper / mapper without customization


public MyObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
MyObject object = MAPPER.readValue(p, MyObject.class);
// do whatever you want
return object;
}

请注意: 实际上没有必要使用任何 String 值或其他值。所有需要的信息都由 JsonParser 提供,因此请使用它。

使用 BeanDeserializerModifier工作得很好,但是如果您需要使用 JsonDeserialize,有一种方法可以使用 AnnotationIntrospector 像这样:

ObjectMapper originalMapper = new ObjectMapper();
ObjectMapper copy = originalMapper.copy();//to keep original configuration
copy.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {


@Override
public Object findDeserializer(Annotated a) {
Object deserializer = super.findDeserializer(a);
if (deserializer == null) {
return null;
}
if (deserializer.equals(MyDeserializer.class)) {
return null;
}
return deserializer;
}
});

现在,复制的映射器将忽略自定义反序列化器(MySerializer.class)并使用默认实现。您可以在自定义反序列化器的 deserialize方法中使用它,通过使复制的映射器成为静态的来避免递归,或者在使用 Spring 时将其连接起来。