jackson:如何添加自定义属性到JSON而不修改POJO
我正在为我的应用程序开发一个REST接口,使用Jackson将我的POJO域对象序列化为JSON表示。 我想为某些types定制序列化,以便为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
,我也找不到一个简单的方法来创build一个BeanSerializer
的新实例,而不提供所有的types元数据细节实际上重复一个很好的部分jackson。 所以我放弃了这样做。
我的问题是 – 如何自定义Jackson的序列化,为特定的POJO添加额外的东西到JSON输出中,而不会引入太多的样板代码,并尽可能重复使用默认的Jackson行为。
既然(我认为)jackson1.7你可以用BeanSerializerModifier
和扩展BeanSerializerBase
做到这一点。 我已经用Jackson 2.0.4testing了下面的例子。
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(); } } }
你可以这样做(以前的版本在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(); } } }
Jackson 2.5引入了@JsonAppend
注解,可以在序列化过程中添加“虚拟”属性。 它可以与mixinfunction一起使用,以避免修改原始的POJO。
以下示例在序列化过程中添加了ApprovalState
属性:
@JsonAppend( attrs = { @JsonAppend.Attr(value = "ApprovalState") } ) public static class ApprovalMixin {}
用ObjectMapper
注册mixin:
mapper.addMixIn(POJO.class, ApprovalMixin.class);
在序列化过程中使用ObjectWriter
来设置属性:
ObjectWriter writer = mapper.writerFor(POJO.class) .withAttribute("ApprovalState", "Pending");
使用编写器进行序列化将把ApprovalState
字段添加到输出中。
虽然这个问题已经得到解答,但我find了另外一个不需要特殊jackson钩子的方法。
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" } ]
我们可以使用reflection来获取要分析的对象的所有字段。
@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(); }
启发从wajda说和写在这个要点 :
以下是如何在jackson 1.9.12中添加bean序列化的监听器。 在这个例子中,李斯特纳被视为一个命令链,其界面是:
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(); } }
我们可以扩展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); } } }
这样,我们就可以处理这个情况,即使用jackson annotations来自定义序列化行为的原始对象。
在对Jackson源代码进行了更多的研究后,我得出结论:如果不编写我自己的BeanSerializer
, BeanSerializerBuilder
和BeanSerializerFactory
并且提供一些扩展点,
/* /********************************************************** /* 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 }
不幸的是,我不得不复制和粘贴整个jackson的BeanSerializer
源代码到MyCustomBeanSerializer
因为前者不是为扩展声明所有的字段和一些重要的方法(如serialize(...)
)作为final
我也需要这个能力。 在我的情况下,支持REST服务的扩展。 我最终开发了一个小框架来解决这个问题,它在github上开源。 它也可以在maven中央仓库中使用 。
它负责所有的工作。 只需将POJO包装在MorphedResult中,然后随意添加或删除属性。 当序列化时,MorphedResult包装器消失,任何“更改”出现在序列化的JSON对象中。
MorphedResult<?> result = new MorphedResult<>(pojo); result.addExpansionData("my_extra_field", "some data");
有关更多详细信息和示例,请参阅github页面。 一定要用Jackson的对象映射器注册库的“filter”,像这样:
ObjectMapper mapper = new ObjectMapper(); mapper.setFilters(new FilteredResultProvider());