ChatGPT解决这个技术问题 Extra ChatGPT

如何从杰克逊的自定义反序列化器中调用默认反序列化器

我在杰克逊的自定义反序列化器中有问题。我想访问默认序列化程序来填充我要反序列化的对象。在填充之后,我会做一些自定义的事情,但首先我想用默认的 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 同样对获取访问没有帮助。
你最后是怎么做到的?
好问题。我不确定,但我确信下面的答案对我有帮助。我目前不拥有我们编写的代码,如果您确实找到了解决方案,请将其发布在此处以供其他人使用。
我很惊讶 2013 年是如何提出这个问题的,而在我们的 2022 年,对于如此令人难以置信的普遍要求,仍然没有明智的解决方案。
@kaqqao 我同意。我没想到这个框架甚至会再被使用。

s
schummar

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

谢谢!我已经以另一种方式解决了这个问题,但是当我有更多时间时,我会研究你的解决方案。
有没有办法做同样的事情,但使用 JsonSerializer ?我有几个序列化程序,但它们有共同的代码,所以我想对其进行泛化。我尝试直接调用序列化程序,但结果没有在 JSON 结果中展开(每次调用序列化程序都会创建一个新对象)
@herau BeanSerializerModifierResolvableSerializerContextualSerializer 是用于序列化的匹配接口。
这适用于 EE 版容器(Wildfly 10)吗?我得到 JsonMappingException: (was java.lang.NullPointerException) (通过引用链: java.util.ArrayList[0])
问题使用 readTree() 但答案没有。这种方法与posted by Derek Cochran相比有什么优势?有没有办法使用 readTree() 进行这项工作?
D
Derek Cochran

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

}

DeserialisationContext.readValue() 不存在,即ObjectMapper的一个方法
此解决方案运行良好,但是如果您反序列化值类(例如 Date.class),则可能需要调用 nextToken()
您的解决方案是最优雅的解决方案。您将 BarBean.class 的序列化调度委托给 Jackson。这很好,你可以让你的反序列化器变小、可重用和可测试。我相信您应该调用 JsonNode.traverse(codec) 而不是 JsonNode.traverse() 来传递现有的反序列化器编解码器。
G
Gili

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

真的没有比这更容易的了。


嗨吉利!谢谢你,我希望人们找到这个答案并有时间验证它。我不能再在那里这样做了,因为我现在不能接受答案。如果我看到人们确实说这是一个可能的解决方案,我当然会引导他们实现它。也可能并非所有版本都可以这样做。还是谢谢分享。
不能与 Jackson 2.9.9 一起编译。 JsonParser.readTree() 不存在。
@ccleve 看起来像一个简单的错字。固定的。
可以确认这适用于 Jackson 2.10,谢谢!
我不明白这是如何工作的,这会导致 StackOverflowError,因为 Jackson 将再次对 User 使用相同的序列化程序...
B
Bill

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

}

是的。唯一对我有用的方法。由于对反序列化器的递归调用,我得到了 StackOverflowErrors。
尽管这是某种 hack,但它允许对已知字段使用默认序列化程序,而您仍然可以访问未知字段。因此,这可用于读取包含应反序列化为 Map(或嵌套对象)的列的 csv。例如:ObjectMapper 映射器 = (ObjectMapper) jp.getCodec(); JsonNode 节点 = oc.readTree(jp);用户反序列化用户 = mapper.treeToValue(node, UserPOJO.class); String userName = node.get("user.name").asText(); deserializedUser.setUserName(userName);返回反序列化用户;
@Bill 你不需要转换为 ObjectMapper,treeToValue 是继承的
S
StaxMan

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

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

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

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


我的回答仍然成立:您需要让 Jackson 构造默认的反序列化器以委托给;并且必须找到一种方法来“覆盖”它。 BeanDeserializerModifier 是允许这样做的回调处理程序。
m
magiccrafter

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

这就像杰克逊 2.9.9 的梦想。它不会像给出的其他示例那样遭受 StackOverflowError 的影响。
o
oberlies

如果您尝试从头开始创建自定义反序列化器,您一定会失败。

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

它因缺少非参数构造函数而失败:原因:java.lang.IllegalArgumentException:类 RecordDeserializer 没有默认(无参数)构造函数。并且超级(委托)构造函数需要非空参数。
C
Community

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


F
Filip Ljubičić

使用 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 时连接它来避免递归。


M
Michail Michailidis

对我来说,一个更简单的解决方案是添加另一个 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;
}

k
kaiser

这是使用默认 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 是“我的对象映射器没有安装这个自定义反序列化器”,所以我仍然选择其他自定义。
这绝对不是正确的答案,因为创建一个新的 ObjectMapper 总是很昂贵
@FrancescoGuardiani 检查我上面的评论,您可以使 ObjectMapper 成为最终静态...我将编辑答案。