如何在Jackson的自定义解串器中调用默认解串器


105

我在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中有注释。由于明显的原因,这会导致堆栈溢出异常。

我曾尝试初始化a,BeanDeserializer但过程极其复杂,而且我还没有设法找到正确的方法。我也曾尝试重载AnnotationIntrospector,但毫无用处,以为这可能会帮助我忽略中的注释DeserializerContext。最终,它表明我可能已经取得了一些成功,JsonDeserializerBuilders尽管这需要我做一些魔术工作才能掌握Spring的应用程序上下文。我将不胜感激任何可以引导我使用更简洁解决方案的方法,例如,如何在不阅读JsonDeserializer注释的情况下构造反序列化上下文。


2
否。这些方法将无济于事:问题在于您将需要一个完全构造的默认解串器;这需要先构建一个,然后解串器才能访问它。DeserializationContext不是您应该创建或更改的内容;它将由提供ObjectMapperAnnotationIntrospector,同样,在获取访问权限方面也无济于事。
StaxMan

最终您是怎么做到的?
khituras 2014年

好问题。我不确定,但可以肯定以下答案对我有所帮助。我目前不拥有我们编写的代码,如果您确实找到解决方案,请在此处将其发布给其他人。
Pablo Jomer 2014年

Answers:


93

正如StaxMan已经建议的那样,您可以通过编写a 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);
  }
}

谢谢!我已经用另一种方法解决了这个问题,但是如果我有更多时间,我会研究您的解决方案。
Pablo Jomer

5
有没有办法做到这一点,但有一个JsonSerializer?我有几个序列化器,但是它们具有通用的代码,因此我想对其进行泛化。我尝试直接调用序列化程序,但结果未在JSON结果中解包(每次调用序列化程序都会创建一个新对象)
herau 2014年

1
@herau BeanSerializerModifierResolvableSerializer并且ContextualSerializer都使用了序列化的匹配接口。
StaxMan '16

这适用于EE版本的容器(Wildfly 10)吗?我得到JsonMappingException :(是java.lang.NullPointerException)(通过参考链:java.util.ArrayList [0])
1927033年

问题使用readTree()但答案没有。与Derek Cochran发表的方法相比,这种方法的优点是什么?有没有办法使它起作用readTree()
吉利

14

我在ans找到了一个答案,该答案比接受的答案更具可读性。

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

确实没有比这容易的事情了。


嗨,吉莉!谢谢,我希望人们找到这个答案并有时间验证它。我现在无法这样做了,因为我目前无法接受答案。如果我看到人们确实说这是一种可能的解决方案,我当然会引导他们朝着这个方向发展。也可能不是所有版本都可以。仍然感谢您的分享。
Pablo Jomer

不能与Jackson 2.9.9一起编译。JsonParser.readTree()不存在。
ccleve

@ccleve看起来像一个简单的错字。固定。
吉利

可以确认该版本适用于Jackson 2.10,谢谢!
Stuart Leyland-Cole

2
我不知道它是如何工作的,导致结果为StackOverflowError,因为杰克逊将再次使用相同的序列化程序User...
john16384

12

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

}

DeserialisationContext.readValue()不存在,这是ObjectMapper的方法
Pedro Borges

此解决方案效果很好,但是,如果反序列化值类(例如
Date.class

9

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

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

另外,如果您必须实际创建并填充对象,则可以这样做,并调用带有deserialize()第三个参数的重载版本;反序列化为对象。

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


我的答案仍然不变:您需要让Jackson构造默认的反序列化器以委派给它;并且必须找到一种“替代”它的方法。BeanDeserializerModifier是允许的回调处理程序。
StaxMan

7

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

}

1
对。唯一对我有用的方法。由于递归调用解串器,导致出现StackOverflowErrors。
ccleve

3

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

对于Jackson 2.9.9,这就像梦一样。它不会像其他示例那样遭受StackOverflowError的困扰。
meta1203

2

这是使用ObjectMapper的oneliner

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

并请:确实不需要使用任何String值或其他值。所有需要的信息都由JsonParser给出,因此请使用它。



1

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

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

0

如果您尝试从头开始创建自定义解串器,那么注定会失败。

相反,您需要通过custom拥有(完全配置的)默认解串器实例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;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.