我在杰克逊的自定义反序列化器中有问题。我想访问默认序列化程序来填充我要反序列化的对象。在填充之后,我会做一些自定义的事情,但首先我想用默认的 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 中的注释。由于显而易见的原因,这会导致堆栈溢出异常。
我尝试过初始化 BeanDeserializer
,但过程非常复杂,而且我还没有找到正确的方法来完成它。我也尝试过重载 AnnotationIntrospector
无济于事,认为它可能会帮助我忽略 DeserializerContext
中的注释。最后,我可能使用 JsonDeserializerBuilders
取得了一些成功,尽管这需要我做一些神奇的事情来从 Spring 中获取应用程序上下文。我将不胜感激任何可以使我找到更清洁的解决方案的方法,例如如何在不阅读 JsonDeserializer
注释的情况下构造反序列化上下文。
DeserializationContext
不是您应该创建或更改的东西;它将由 ObjectMapper
提供。 AnnotationIntrospector
同样对获取访问没有帮助。
正如 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);
}
}
DeserializationContext
有一个您可以使用的 readValue()
方法。这应该适用于默认反序列化器和您拥有的任何自定义反序列化器。
只需确保在要读取的 JsonNode
级别上调用 traverse()
以检索 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;
}
}
我在 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;
}
真的没有比这更容易的了。
StackOverflowError
,因为 Jackson 将再次对 User
使用相同的序列化程序...
如果您可以声明额外的 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;
}
}
有几种方法可以做到这一点,但要做到这一点需要更多的工作。基本上你不能使用子类,因为默认反序列化器需要的信息是从类定义中构建的。
因此,您最有可能使用的是构造一个 BeanDeserializerModifier
,通过 Module
接口注册它(使用 SimpleModule
)。您需要定义/覆盖 modifyDeserializer
,对于要添加自己的逻辑(类型匹配)的特定情况,构建自己的反序列化器,传递给定的默认反序列化器。然后在 deserialize()
方法中,您可以委托调用,获取结果对象。
或者,如果您必须实际创建和填充对象,您可以这样做并调用采用第三个参数的 deserialize()
的重载版本;要反序列化的对象。
另一种可能有效(但不是 100% 肯定)的方法是指定 Converter
对象 (@JsonDeserialize(converter=MyConverter.class)
)。这是 Jackson 2.2 的新功能。在您的情况下, Converter 实际上不会转换类型,而是简化修改对象:但我不知道这是否会让您完全按照您的意愿行事,因为首先会调用默认的反序列化器,然后才会调用您的 Converter
.
BeanDeserializerModifier
是允许这样做的回调处理程序。
按照 Tomáš Záluský has suggested 的思路,如果不希望使用 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;
}
如果您尝试从头开始创建自定义反序列化器,您一定会失败。
相反,您需要通过自定义 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;
}
}
我不同意使用 BeanSerializerModifier
,因为它强制在中央 ObjectMapper
中而不是在自定义反序列化器本身中声明一些行为变化,实际上它是使用 JsonSerialize
注释实体类的并行解决方案。如果您有类似的感觉,您可能会在这里欣赏我的回答:https://stackoverflow.com/a/43213463/653539
使用 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;
}
});
现在复制的映射器现在将忽略您的自定义反序列化器(MyDeserializer.class)并使用默认实现。您可以在自定义反序列化器的 deserialize
方法中使用它,通过将复制的映射器设为静态或在使用 Spring 时连接它来避免递归。
对我来说,一个更简单的解决方案是添加另一个 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;
}
这是使用默认 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;
}
并且请:真的不需要使用任何字符串值或其他东西。所有需要的信息都由 JsonParser 提供,所以使用它。
ObjectMapper
才能恢复默认行为似乎是错误的。
ObjectMapper
总是很昂贵
不定期副业成功案例分享
JsonSerializer
?我有几个序列化程序,但它们有共同的代码,所以我想对其进行泛化。我尝试直接调用序列化程序,但结果没有在 JSON 结果中展开(每次调用序列化程序都会创建一个新对象)BeanSerializerModifier
、ResolvableSerializer
和ContextualSerializer
是用于序列化的匹配接口。readTree()
但答案没有。这种方法与posted by Derek Cochran相比有什么优势?有没有办法使用readTree()
进行这项工作?