杰克逊:如何在不修改POJO的情况下向JSON添加自定义属性

Ale*_*yda 55 java customization serialization json jackson

我正在为我的应用程序开发一个REST接口,使用Jackson将我的POJO域对象序列化为JSON表示.我想为某些类型自定义序列化,以便为POJO中不存在的JSON表示添加其他属性(例如,添加一些元数据,参考数据等).我知道如何编写自己的JsonSerializer,但在这种情况下,我需要为我的对象的每个属性显式调用JsonGenerator.writeXXX(..)方法,而我只需添加一个额外的属性.换句话说,我希望能够写出如下内容:

@Override
public void serialize(TaxonomyNode value, JsonGenerator jgen, SerializerProvider provider) {
    jgen.writeStartObject();
    jgen.writeAllFields(value); // <-- The method I'd like to have
    jgen.writeObjectField("my_extra_field", "some data");
    jgen.writeEndObject();
}
Run Code Online (Sandbox Code Playgroud)

或者(甚至更好)在jgen.writeEndObject()调用之前以某种方式拦截序列化,例如:

@Override void beforeEndObject(....) {
    jgen.writeObjectField("my_extra_field", "some data");
}
Run Code Online (Sandbox Code Playgroud)

我以为我可以扩展BeanSerializer和覆盖它的serialize(..)方法,但是它被声明了final,而且我找不到一种简单的方法来创建一个新的实例BeanSerializer而不提供所有类型的元数据细节实际上复制了杰克逊的一部分.所以我放弃了这样做.

我的问题是 - 如何定制Jackson的序列化,为特定POJO的JSON输出添加额外的东西,而不会引入太多的样板代码并尽可能多地重用默认的Jackson行为.

rya*_*anp 37

自从(我认为)杰克逊1.7以来,你可以通过BeanSerializerModifier扩展来做到这一点BeanSerializerBase.我用Jackson 2.0.4测试了下面的例子.

import java.io.IOException;

import org.junit.Test;

import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;


public class JacksonSerializeWithExtraField {

    @Test
    public void testAddExtraField() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();

        mapper.registerModule(new SimpleModule() {

            public void setupModule(SetupContext context) {
                super.setupModule(context);

                context.addBeanSerializerModifier(new BeanSerializerModifier() {

                    public JsonSerializer<?> modifySerializer(
                            SerializationConfig config,
                            BeanDescription beanDesc,
                            JsonSerializer<?> serializer) {
                        if (serializer instanceof BeanSerializerBase) { 
                              return new ExtraFieldSerializer(
                                   (BeanSerializerBase) serializer);
                        } 
                        return serializer; 

                    }                   
                });
            }           
        });

        mapper.writeValue(System.out, new MyClass());       
        //prints {"classField":"classFieldValue","extraField":"extraFieldValue"}
    }


    class MyClass {

        private String classField = "classFieldValue";

        public String getClassField() { 
            return classField; 
        }
        public void setClassField(String classField) { 
            this.classField = classField; 
        }
    }


    class ExtraFieldSerializer extends BeanSerializerBase {

        ExtraFieldSerializer(BeanSerializerBase source) {
            super(source);
        }

        ExtraFieldSerializer(ExtraFieldSerializer source, 
                ObjectIdWriter objectIdWriter) {
            super(source, objectIdWriter);
        }

        ExtraFieldSerializer(ExtraFieldSerializer source, 
                String[] toIgnore) {
            super(source, toIgnore);
        }

        protected BeanSerializerBase withObjectIdWriter(
                ObjectIdWriter objectIdWriter) {
            return new ExtraFieldSerializer(this, objectIdWriter);
        }

        protected BeanSerializerBase withIgnorals(String[] toIgnore) {
            return new ExtraFieldSerializer(this, toIgnore);
        }

        public void serialize(Object bean, JsonGenerator jgen,
                SerializerProvider provider) throws IOException,
                JsonGenerationException {           
            jgen.writeStartObject();                        
            serializeFields(bean, jgen, provider);
            jgen.writeStringField("extraField", "extraFieldValue"); 
            jgen.writeEndObject();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Hen*_*sen 26

Jackson 2.5引入了@JsonAppend注释,可用于在序列化期间添加"虚拟"属性.它可以与mixin功能一起使用,以避免修改原始POJO.

以下示例ApprovalState在序列化期间添加属性:

@JsonAppend(
    attrs = {
        @JsonAppend.Attr(value = "ApprovalState")
    }
)
public static class ApprovalMixin {}
Run Code Online (Sandbox Code Playgroud)

注册mixin ObjectMapper:

mapper.addMixIn(POJO.class, ApprovalMixin.class);
Run Code Online (Sandbox Code Playgroud)

使用an ObjectWriter在序列化期间设置属性:

ObjectWriter writer = mapper.writerFor(POJO.class)
                          .withAttribute("ApprovalState", "Pending");
Run Code Online (Sandbox Code Playgroud)

使用编写器进行序列化将ApprovalState字段添加到输出.


Die*_*rDP 17

虽然这个问题已经得到解答,但我找到了另一种不需要特殊杰克逊钩子的方式.

static class JsonWrapper<T> {
    @JsonUnwrapped
    private T inner;
    private String extraField;

    public JsonWrapper(T inner, String field) {
        this.inner = inner;
        this.extraField = field;
    }

    public T getInner() {
        return inner;
    }

    public String getExtraField() {
        return extraField;
    }
}

static class BaseClass {
    private String baseField;

    public BaseClass(String baseField) {
        this.baseField = baseField;
    }

    public String getBaseField() {
        return baseField;
    }
}

public static void main(String[] args) throws JsonProcessingException {
    Object input = new JsonWrapper<>(new BaseClass("inner"), "outer");
    System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(input));
}
Run Code Online (Sandbox Code Playgroud)

输出:

{
  "baseField" : "inner",
  "extraField" : "outer"
}
Run Code Online (Sandbox Code Playgroud)

要编写集合,您只需使用视图:

public static void main(String[] args) throws JsonProcessingException {
    List<BaseClass> inputs = Arrays.asList(new BaseClass("1"), new BaseClass("2"));
    //Google Guava Library <3
    List<JsonWrapper<BaseClass>> modInputs = Lists.transform(inputs, base -> new JsonWrapper<>(base, "hello"));
    System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(modInputs));
}
Run Code Online (Sandbox Code Playgroud)

输出:

[ {
  "baseField" : "1",
  "extraField" : "hello"
}, {
  "baseField" : "2",
  "extraField" : "hello"
} ]
Run Code Online (Sandbox Code Playgroud)


Ras*_*ber 16

你可以这样做(以前的版本在2.6之后不适用于Jackson,但这适用于Jackson 2.7.3):

public static class CustomModule extends SimpleModule {
    public CustomModule() {
        addSerializer(CustomClass.class, new CustomClassSerializer());
    }

    private static class CustomClassSerializer extends JsonSerializer {
        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            //Validate.isInstanceOf(CustomClass.class, value);
            jgen.writeStartObject();
            JavaType javaType = provider.constructType(CustomClass.class);
            BeanDescription beanDesc = provider.getConfig().introspect(javaType);
            JsonSerializer<Object> serializer = BeanSerializerFactory.instance.findBeanSerializer(provider,
                    javaType,
                    beanDesc);
            // this is basically your 'writeAllFields()'-method:
            serializer.unwrappingSerializer(null).serialize(value, jgen, provider);
            jgen.writeObjectField("my_extra_field", "some data");
            jgen.writeEndObject();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Sch*_*tod 6

另一个也许是最简单的解决方案:

使序列化过程分为两步。首先创建一个Map<String,Object>类似:

Map<String,Object> map = req.mapper().convertValue( result, new TypeReference<Map<String,Object>>() {} );
Run Code Online (Sandbox Code Playgroud)

然后添加您想要的属性,例如:

map.put( "custom", "value" );
Run Code Online (Sandbox Code Playgroud)

然后将其序列化为 json:

String json = req.mapper().writeValueAsString( map );
Run Code Online (Sandbox Code Playgroud)


Ale*_*yda -2

在查看了更多JacksonBeanSerializer源代码之后,我得出的结论是,如果不编写自己的代码,就根本不可能实现,BeanSerializerBuilderBeanSerializerFactory提供一些扩展点,例如:

/*
/**********************************************************
/* Extension points
/**********************************************************
 */

protected void beforeEndObject(T bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JSONException {
    // May be overridden
}

protected void afterStartObject(T bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JSONException {
    // May be overridden
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,我不得不复制并粘贴整个JacksonBeanSerializer源代码,MyCustomBeanSerializer因为前者不是为将所有字段和一些重要方法(例如serialize(...))声明为扩展而开发的final

  • 不当使用 Final 是为了纳粹代码。我自己经常面临无法扩展现有代码的问题,仅仅因为最终方法或类。并且不要争论性能:http://stackoverflow.com/questions/4279420/does-use-of-final-keyword-in-java-improve-the-performance (6认同)
  • @Dag 我不会在那里妄下结论。如果开发人员选择将课程设为决赛,那么这很可能是一个经过深思熟虑的决定。开设扩展课程是一个不应该轻易做出的决定。 (3认同)

归档时间:

查看次数:

48163 次

最近记录:

6 年,1 月 前