Java Jackson如何在自定义序列化程序中为对象使用默认序列化程序

Posted

技术标签:

【中文标题】Java Jackson如何在自定义序列化程序中为对象使用默认序列化程序【英文标题】:Java Jackson how to use the default serializer for an object in a custom serializer 【发布时间】:2018-12-19 18:07:01 【问题描述】:

我有一个名为 Resource 的自定义超类,我希望每次序列化 Resource 的实例(或子类的实例)时都会向 JSON 添加两个额外的字段。我正在使用 Spring Boot(如果这很重要)和 Jackson。我的想法是定义一个自定义序列化器供 Jackson 使用,但是我当前的实现存在一些问题。

@JsonComponent
public class CustomJsonSerializer extends JsonSerializer<Resource> 
    @Override
    public void serialize(Resource resource, JsonGenerator jsonGenerator,
                      SerializerProvider serializerProvider) throws IOException 

        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("type", "special");

        jsonGenerator.writeFieldName("data");
        jsonGenerator.writeObject(resource);

        jsonGenerator.writeFieldName("links");
        jsonGenerator.writeObject(resource.getId());

        jsonGenerator.writeEndObject();
    

不幸的是,调用 writeObject(resource) 只是重新进入我的 CustomJsonSerializer 进入递归循环。

重要的一点,资源可以嵌套,因此使用默认序列化器简单地序列化资源是不够的。

我正在寻找类似于writeAllFields(resource) 方法的东西。当然可以使用反射自己实现一个,但是我忽略了所有的 Jackson 注释,这也是我想避免的。

编辑:

Resource 类是一个超类型,并且预期的行为是 Resource 的所有子类都通过这个序列化程序(这已经是这种情况了)。

public class Resource 
    String id;

    protected Resource() 

    public String getId() 
        return id;
    


public class Item extends Resource 
    // Fields and methods, nothing special

编辑 2: 万一这很重要,这就是我向 Spring Boot 注册我的序列化程序的方式:

    @Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) 
    SimpleModule m = new SimpleModule();
    m.addSerializer(Resource.class, new CustomJsonSerializer());
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().modules(m);
    converters.add(new MappingJackson2HttpMessageConverter(builder.build()));

【问题讨论】:

能贴出Resource类的源码定义吗? @EricGreen 我用您要求的信息更新了问题。 在您的序列化程序中,您调用了#links 方法,但我在您的 Resource 类的定义中没有看到它。 我的错,我稍微简化了代码以使其更易于理解。 那么,您是否要序列化 ​​Resource 的具体子类,例如 "links": , "data": "field1": "value1", "field2": "value2" , ... 其中“数据”由具体子类的所有附加字段组成? 【参考方案1】:

您帖子的标题声明您希望从自定义序列化程序中访问默认序列化程序,因为以下帖子中的人正在尝试这样做:

https://groups.google.com/forum/#!topic/jackson-user/cFQhuyw8vpw

如果你滚动到底部,那家伙会说:

访问原本会被创建/用作 标准序列化程序是注册BeanSerializerModifier,并且 覆盖modifySerializer 的处理。

您还可以查看有关 Jackson 中高级序列化功能的本教程,其中该人正在做与您正在尝试做的事情类似的事情:

http://www.baeldung.com/jackson-serialize-field-custom-criteria

但是,通过查看您的代码,我推断您正在尝试实现类 Resource 子类型的多态序列化。

如果是这样,以下是一个简单的解决方案,希望正是您正在寻找的:


资源.java

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        property = "type")
@JsonSubTypes(
        @JsonSubTypes.Type(value = ResourceFoo.class, name = "foo"),
        @JsonSubTypes.Type(value = ResourceBar.class, name = "bar")
)
public abstract class Resource 

    private String id;

    @JsonProperty("links")
    public String getId() 
        return id;
    

    public void setId(String id) 
        this.id = id;
    


ResourceFoo.java

public class ResourceFoo extends Resource 

    private String fieldA;

    private String fieldB;

    public String getFieldA() 
        return fieldA;
    

    public void setFieldA(String fieldA) 
        this.fieldA = fieldA;
    

    public String getFieldB() 
        return fieldB;
    

    public void setFieldB(String fieldB) 
        this.fieldB = fieldB;
    


ResourceBar.java

public class ResourceBar extends Resource 

    private int fieldX;

    private int fieldY;

    public int getFieldX() 
        return fieldX;
    

    public void setFieldX(int fieldX) 
        this.fieldX = fieldX;
    

    public int getFieldY() 
        return fieldY;
    

    public void setFieldY(int fieldY) 
        this.fieldY = fieldY;
    


SerializerDemo.java

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class SerializerDemo 

    public static void main(String[] args) throws JsonProcessingException 

        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();

        ResourceFoo foo = new ResourceFoo();
        foo.setId("abc");
        foo.setFieldA("Apples");
        foo.setFieldB("Oranges");

        String fooJson = mapper
                .writerWithDefaultPrettyPrinter()
                .writeValueAsString(foo);

        System.out.println(fooJson);

        ResourceBar bar = new ResourceBar();
        bar.setId("xyz");
        bar.setFieldX(12);
        bar.setFieldY(238);

        String barJson = mapper
                .writerWithDefaultPrettyPrinter()
                .writeValueAsString(bar);

        System.out.println(barJson);
    


SerializerDemo 输出:


  "type" : "foo",
  "fieldA" : "Apples",
  "fieldB" : "Oranges",
  "links" : "abc"


  "type" : "bar",
  "fieldX" : 12,
  "fieldY" : 238,
  "links" : "xyz"

如需了解更多信息,Eugen Paraschiv 也就此发表了一篇不错的小文章:

http://www.baeldung.com/jackson-inheritance

【讨论】:

【参考方案2】:

这应该可行:

    @JsonComponent
    public static class CustomJsonSerializer extends JsonSerializer<Resource> 
        private void serializeFields(Resource bean, JsonGenerator gen, SerializerProvider provider)
                throws IOException 
            JavaType javaType = provider.constructType(Resource.class);
            BeanDescription beanDesc = provider.getConfig().introspect(javaType);
            JsonSerializer<Object> serializer = BeanSerializerFactory.instance.findBeanSerializer(provider,
                    javaType,
                    beanDesc);
            serializer.unwrappingSerializer(null).serialize(bean, gen, provider);
        

        @Override
        public void serialize(Resource resource, JsonGenerator jsonGenerator,
                              SerializerProvider serializerProvider) throws IOException 
            jsonGenerator.writeStartObject();
            jsonGenerator.writeStringField("type", "special");

            jsonGenerator.writeFieldName("data");
            jsonGenerator.writeStartObject();
            serializeFields(resource, jsonGenerator, serializerProvider);
            jsonGenerator.writeEndObject();

            jsonGenerator.writeFieldName("links");
            jsonGenerator.writeObject(resource.links());

            jsonGenerator.writeEndObject();
        
    

【讨论】:

这不起作用,在另一个问题上找到了类似的答案,但也不起作用,所以它可能与我如何将我的 objectmapper 传递给 spring 有关。所以我现在的问题是,为 Spring boot 提供我的自定义序列化程序的最佳方式是什么? 这是我尝试此操作时 spring 给我的错误:无法编写 JSON:[java.lang.NullPointerException 没有消息];嵌套异常是 com.fasterxml.jackson.databind.JsonMappingException: [no message for java.lang.NullPointerException] @LasseJacobs 如果您提供完整的异常堆栈跟踪,我可能会为您提供帮助(将其放入例如 pastebin.com)。更一般的评论:在提出问题之前尝试简化代码是可以的,但请确保在您的项目中实际执行它,而不仅仅是在您提出问题时。如果您直接使用 Jackson 来序列化您的资源 (jdoodle.com/a/zcC),请尝试查看我的建议是否有效。如果可行,请尝试查看 Spring 和序列化程序是否确实存在问题:向序列化方法添加断点或日志行,并确定它是否被调用。 .unwrappingSerializer(null) 在哪里?对我来说,如果我省略它就可以正常工作,但如果我添加它就会出错。

以上是关于Java Jackson如何在自定义序列化程序中为对象使用默认序列化程序的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Jackson 中使用自定义序列化程序?

在自定义 Java AWS 应用程序中找不到类的序列化程序

如何在自定义用户控件中为 ListBox ItemTemplate 属性设置适当的上下文

Django admin - 如何在自定义管理表单中为多对多字段添加绿色加号

jackson中自定义处理序列化和反序列化

Java 到 Jackson JSON 序列化:Money 字段