杰克逊:如何添加自定义属性到JSON,而无需修改POJO(Jackson: How to add c

2019-07-19 07:48发布

我使用的杰克逊连载我的POJO域对象,以JSON表示开发一个REST接口为我的应用程序。 我想自定义序列化对于某些类型的附加属性添加到不存在的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();
}

或(甚至更好)以某种方式拦截前的系列化jgen.writeEndObject()调用,如:

@Override void beforeEndObject(....) {
    jgen.writeObjectField("my_extra_field", "some data");
}

我以为我可以延长BeanSerializer并覆盖其serialize(..)方法,但它的声明final ,也是我无法找到一个简单的方法来创建的新实例BeanSerializer没有向它提供所有类型的元数据信息几乎复制了良好的部分杰克逊。 所以我已经放弃了这样做。

我的问题是 -如何定制杰克逊的系列化额外的东西添加到JSON输出特定的POJO没有引入太多的样板代码的重用,并尽可能默认杰克逊的行为。

Answer 1:

因为(我认为)杰克逊1.7,你可以用做BeanSerializerModifier和扩展BeanSerializerBase 。 我曾与杰克逊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();
        }
    }
}


Answer 2:

杰克逊2.5引入的@JsonAppend注释,其可用于序列化过程中添加的“虚拟”的性质。 它可与混入的功能可以用来避免修改原始POJO。

下面的示例将一个ApprovalState序列化过程中属性:

@JsonAppend(
    attrs = {
        @JsonAppend.Attr(value = "ApprovalState")
    }
)
public static class ApprovalMixin {}

注册用的混入ObjectMapper

mapper.addMixIn(POJO.class, ApprovalMixin.class);

使用ObjectWriter序列化过程中设置的属性:

ObjectWriter writer = mapper.writerFor(POJO.class)
                          .withAttribute("ApprovalState", "Pending");

使用作家序列化将增加ApprovalState场传至输出。



Answer 3:

你可以这样做(以前版本杰克逊2.6后没有工作,但这个工程与杰克逊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();
        }
    }
}


Answer 4:

尽管这个问题已经回答了,我发现,不需要特殊的杰克逊挂钩的另一种方式。

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

输出:

{
  "baseField" : "inner",
  "extraField" : "outer"
}

对于写作的集合,你可以简单地使用一个观点:

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

输出:

[ {
  "baseField" : "1",
  "extraField" : "hello"
}, {
  "baseField" : "2",
  "extraField" : "hello"
} ]


Answer 5:

对于我的使用情况下,我可以用一个更简单的方法。 在一个基类我有我所有的“杰克逊的POJO”我补充一下:

protected Map<String,Object> dynamicProperties = new HashMap<String,Object>();

...


public Object get(String name) {
    return dynamicProperties.get(name);
}

// "any getter" needed for serialization    
@JsonAnyGetter
public Map<String,Object> any() {
    return dynamicProperties;
}

@JsonAnySetter
public void set(String name, Object value) {
    dynamicProperties.put(name, value);
}

我现在可以反序列化POJO的,有下地干活和重新序列化witjout损失任何性能。 我还可以添加/更改非POJO属性:

// Pojo fields
person.setFirstName("Annna");

// Dynamic field
person.set("ex", "test");

(来自明白了Cowtowncoder )



Answer 6:

我们可以使用反射来得到你想要解析对象的所有领域。

@JsonSerialize(using=CustomSerializer.class)
class Test{
  int id;
  String name;
  String hash;
}    

在自定义序列,我们有这样的我们的序列化方法:

        @Override
        public void serialize(Test value, JsonGenerator jgen,
                SerializerProvider provider) throws IOException,
                JsonProcessingException {

            jgen.writeStartObject();
            Field[] fields = value.getClass().getDeclaredFields();

            for (Field field : fields) {
                try {
                    jgen.writeObjectField(field.getName(), field.get(value));
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
            jgen.writeObjectField("extra_field", "whatever_value");
            jgen.writeEndObject();

        }


Answer 7:

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

让序列化2个步骤。 首先创建一个Map<String,Object>这样的:

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

然后添加你要喜欢的属性:

map.put( "custom", "value" );

然后连载这JSON:

String json = req.mapper().writeValueAsString( map );


Answer 8:

从什么瓦伊达说灵感,写在这个要点 :

下面是如何在杰克逊1.9.12添加一个监听bean序列。 在这个例子中,listerner被认为是命令一个链,其接口是:

public interface BeanSerializerListener {
    void postSerialization(Object value, JsonGenerator jgen) throws IOException;
}

MyBeanSerializer.java:

public class MyBeanSerializer extends BeanSerializerBase {
    private final BeanSerializerListener serializerListener;

    protected MyBeanSerializer(final BeanSerializerBase src, final BeanSerializerListener serializerListener) {
        super(src);
        this.serializerListener = serializerListener;
    }

    @Override
    public void serialize(final Object bean, final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonGenerationException {
        jgen.writeStartObject();
        if (_propertyFilterId != null) {
            serializeFieldsFiltered(bean, jgen, provider);
        } else {
            serializeFields(bean, jgen, provider);
        }

        serializerListener.postSerialization(bean, jgen);

        jgen.writeEndObject();
    }
}

MyBeanSerializerBuilder.java:

public class MyBeanSerializerBuilder extends BeanSerializerBuilder {
    private final BeanSerializerListener serializerListener;

    public MyBeanSerializerBuilder(final BasicBeanDescription beanDesc, final BeanSerializerListener serializerListener) {
        super(beanDesc);
        this.serializerListener = serializerListener;
    }

    @Override
    public JsonSerializer<?> build() {
        BeanSerializerBase src = (BeanSerializerBase) super.build();
        return new MyBeanSerializer(src, serializerListener);
    }
}

MyBeanSerializerFactory.java:

public class MyBeanSerializerFactory extends BeanSerializerFactory {

    private final BeanSerializerListener serializerListener;

    public MyBeanSerializerFactory(final BeanSerializerListener serializerListener) {
        super(null);
        this.serializerListener = serializerListener;
    }

    @Override
    protected BeanSerializerBuilder constructBeanSerializerBuilder(final BasicBeanDescription beanDesc) {
        return new MyBeanSerializerBuilder(beanDesc, serializerListener);
    }
}

下面的最后一堂课了如何使用RestEasy的3.0.7提供它:

@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
    private final MapperConfigurator mapperCfg;

    public ObjectMapperProvider() {
        mapperCfg = new MapperConfigurator(null, null);
        mapperCfg.setAnnotationsToUse(new Annotations[]{Annotations.JACKSON, Annotations.JAXB});
        mapperCfg.getConfiguredMapper().setSerializerFactory(serializerFactory);
    }

    @Override
    public ObjectMapper getContext(final Class<?> type) {
        return mapperCfg.getConfiguredMapper();
    }
}


Answer 9:

我们可以扩展BeanSerializer ,但小动作。

首先,定义一个Java类包装器的POJO。

@JsonSerialize(using = MixinResultSerializer.class)
public class MixinResult {

    private final Object origin;
    private final Map<String, String> mixed = Maps.newHashMap();

    @JsonCreator
    public MixinResult(@JsonProperty("origin") Object origin) {
        this.origin = origin;
    }

    public void add(String key, String value) {
        this.mixed.put(key, value);
    }

    public Map<String, String> getMixed() {
        return mixed;
    }

    public Object getOrigin() {
        return origin;
    }

}

然后,实现自定义serializer

public final class MixinResultSerializer extends BeanSerializer {

    public MixinResultSerializer() {
        super(SimpleType.construct(MixinResult.class), null, new BeanPropertyWriter[0], new BeanPropertyWriter[0]);
    }

    public MixinResultSerializer(BeanSerializerBase base) {
        super(base);
    }

    @Override
    protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (bean instanceof MixinResult) {
            MixinResult mixin  = (MixinResult) bean;
            Object      origin = mixin.getOrigin();

            BeanSerializer serializer = (BeanSerializer) provider.findValueSerializer(SimpleType.construct(origin.getClass()));

            new MixinResultSerializer(serializer).serializeFields(origin, gen, provider);

            mixin.getMixed().entrySet()
                    .stream()
                    .filter(entry -> entry.getValue() != null)
                    .forEach((entry -> {
                        try {
                            gen.writeFieldName(entry.getKey());
                            gen.writeRawValue(entry.getValue());
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }));
        } else {
            super.serializeFields(bean, gen, provider);
        }

    }

}

通过这种方式,我们可以处理,使用杰克逊标注产地对象序列化定制行为的情况。



Answer 10:

我需要这种能力为好; 在我的情况,以支持REST的服务领域扩展。 我结束了开发一个小框架来解决这个问题,它的开源,开源在github上 。 它也可以在Maven的中央存储库 。

它负责所有的工作。 简单地包裹在MorphedResult的POJO,然后随意添加或删除属性。 当系列化,MorphedResult包装消失,任何“改变”出现在序列化JSON对象。

MorphedResult<?> result = new MorphedResult<>(pojo);
result.addExpansionData("my_extra_field", "some data");

对于更多细节和例子见GitHub的页面。 一定要与杰克逊的对象映射到注册库“过滤器”,像这样:

ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(new FilteredResultProvider());


Answer 11:

寻找更多关于杰克逊的源代码后,我的结论是,这是根本不可能实现的,而不写我自己的BeanSerializerBeanSerializerBuilderBeanSerializerFactory ,并提供一些扩展点,如:

/*
/**********************************************************
/* 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
}

不幸的是,我不得不复制和粘贴的整个杰克逊 BeanSerializer源代码MyCustomBeanSerializer因为前者不进行功能扩展,宣布所有的领域和一些重要的方法开发(如serialize(...)作为final



文章来源: Jackson: How to add custom property to the JSON without modifying the POJO