如何使用 Jackson 和注释以不同的方式序列化关联的对象?



鉴于以下类层次结构,我希望Foo根据在我的类层次结构中使用的上下文进行不同的序列化。

public class Foo {
public String bar;
public String biz;
}
public class FooContainer {
public Foo fooA;
public Foo fooB;
}

我希望在序列化 FooContainer 时 biz 属性不会出现在 fooB 中。 因此,输出将如下所示。

{
"fooA": {"bar": "asdf", "biz": "fdsa"},
"fooB": {"bar": "qwer"}
}

我打算使用一些 JsonView 的东西,但这必须在映射器层应用于类的所有实例,这是依赖于上下文的。


在杰克逊用户邮件列表中,Tatu给出了最简单的解决方案(适用于2.0),我现在可能最终会使用它。 将赏金授予jlabedo,因为答案是如何使用自定义注释扩展杰克逊的一个很好的例子。

public class FooContainer {
public Foo fooA;
@JsonIgnoreProperties({ "biz" })
public Foo fooB;
}

可以使用 JsonView 将自定义序列化程序与自定义属性筛选器结合使用。以下是一些与杰克逊 2.0 一起使用的代码

定义自定义注释:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FilterUsingView {
Class<?>[] value();
}

定义一些视图:

// Define your views here
public static class Views {
public class Public {};
public class Internal extends Public{};
}

然后,您可以像这样编写实体。请注意,您可以定义自己的注释,而不是使用@JsonView

public class Foo {
@JsonView(Views.Public.class)
public String bar;
@JsonView(Views.Internal.class)
public String biz;
}
public class FooContainer {
public Foo fooA;
@FilterUsingView(Views.Public.class)
public Foo fooB;
}

然后,这里是代码开始的地方:) 首先是自定义筛选器:

public static class CustomFilter extends SimpleBeanPropertyFilter {
private Class<?>[] _nextViews;
public void setNextViews(Class<?>[] clazz){
_nextViews = clazz;
}
@Override
public void serializeAsField(Object bean, JsonGenerator jgen,
SerializerProvider prov, BeanPropertyWriter writer)
throws Exception {
Class<?>[] propViews = writer.getViews();
if(propViews != null && _nextViews != null){
for(Class<?> propView : propViews){
System.out.println(propView.getName());
for(Class<?> currentView : _nextViews){
if(!propView.isAssignableFrom(currentView)){
// Do the filtering!
return;
}
}
}
}
// The property is not filtered
writer.serializeAsField(bean, jgen, prov);
}
}

然后是一个自定义AnnotationIntrospector,它将执行两项操作:

  1. 为任何 Bean 启用自定义过滤器...除非在你的类上定义了另一个过滤器(所以你不能同时使用它们,如果你明白我的意思)
  2. 如果他找到@FilterUsingView注释,请启用自定义序列化程序。

这是代码

public class CustomAnnotationIntrospector extends AnnotationIntrospector {
@Override
public Version version() {
return DatabindVersion.instance.version();
}
@Override
public Object findFilterId(AnnotatedClass ac) {
// CustomFilter is used for EVERY Bean, unless another filter is defined
Object id = super.findFilterId(ac);
if (id == null) {
id = "CustomFilter";
}
return id;
}
@Override
public Object findSerializer(Annotated am) {
FilterUsingView annotation = am.getAnnotation(FilterUsingView.class);
if(annotation == null){
return null;
}
return new CustomSerializer(annotation.value());
}
}

下面是自定义序列化程序。它唯一要做的就是将注释的值传递给自定义筛选器,然后让默认序列化程序完成这项工作。

public class CustomSerializer extends JsonSerializer<Object> {
private Class<?>[] _activeViews;
public CustomSerializer(Class<?>[] view){
_activeViews = view;
}
@Override
public void serialize(Object value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
BeanPropertyFilter filter = provider.getConfig().getFilterProvider().findFilter("CustomFilter");
if(filter instanceof CustomFilter){
CustomFilter customFilter = (CustomFilter) filter;
// Tell the filter that we will filter our next property
customFilter.setNextViews(_activeViews);
provider.defaultSerializeValue(value, jgen);
// Property has been filtered and written, do not filter anymore
customFilter.setNextViews(null);
}else{
// You did not define a CustomFilter ? Well this serializer is useless...
provider.defaultSerializeValue(value, jgen);
}
}
}

最后!让我们把这些放在一起:

public class CustomModule extends SimpleModule {
public CustomModule() {
super("custom-module", new Version(0, 1, 0, "", "", ""));
}
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
AnnotationIntrospector ai = new CustomAnnotationIntrospector();
context.appendAnnotationIntrospector(ai);
}
}

@Test
public void customField() throws Exception {
FooContainer object = new FooContainer();
object.fooA = new Foo();
object.fooA.bar = "asdf";
object.fooA.biz = "fdsa";
object.fooB = new Foo();
object.fooB.bar = "qwer";
object.fooB.biz = "test";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new CustomModule());
FilterProvider fp = new SimpleFilterProvider().addFilter("CustomFilter", new CustomFilter());
StringWriter writer = new StringWriter();
mapper.writer(fp).writeValue(writer, object);
String expected = "{"fooA":{"bar":"asdf","biz":"fdsa"},"fooB":{"bar":"qwer"}}";
Assert.assertEquals(expected, writer.toString());
}
import static org.junit.Assert.*;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Type;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.map.ser.SerializerBase;
import org.junit.Test;
class Foo {
public String bar;
public String biz;
}
class FooContainer {
public Foo fooA;
@JsonSerialize(using = FooCustomSerializer.class)
public Foo fooB;
}
class FooCustomSerializer extends SerializerBase<Foo> {
public FooCustomSerializer() {
super(Foo.class);
}
@Override
public void serialize(Foo foo, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonGenerationException {
generator.writeStartObject();
generator.writeObjectField("bar", foo.bar);
generator.writeEndObject();
}
@Override
public JsonNode getSchema(SerializerProvider arg0, Type arg1) throws JsonMappingException {
return null;
}
}
public class JacksonTest {
@Test
public void customField() throws Exception {
FooContainer object = new FooContainer();
object.fooA = new Foo();
object.fooA.bar = "asdf";
object.fooA.biz = "fdsa";
object.fooB = new Foo();
object.fooB.bar = "qwer";
object.fooB.biz = "test";
ObjectMapper mapper = new ObjectMapper();
StringWriter writer = new StringWriter();
mapper.writeValue(writer, object);
String expected = "{"fooA":{"bar":"asdf","biz":"fdsa"},"fooB":{"bar":"qwer"}}";
assertEquals(expected, writer.toString());
}
}

使用 @JsonSerialize(using = FooCustomSerializer.class) 在公共 Foo fooB;田。

http://jackson.codehaus.org/1.9.9/javadoc/org/codehaus/jackson/map/annotate/JsonSerialize.html

我会在这里使用谷歌代码gson
文档 https://code.google.com/p/google-gson/
Maven依赖项是:

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.1</version>
</dependency>

注释如下所示:
向字段用户公开@Expose注释
为已解析的 json 用户中的字段生成特殊名称@SerializedName("fieldNameInJSON")注释
因此,您的类将如下所示:

public class Foo {
@SerializedName("bar")
@Expose
public String bar;
@SerializedName("biz")
@Expose
public String biz;
}
public class FooContainer {
@SerializedName("fooA")
@Expose
public Foo fooA;
@SerializedName("fooB")
@Expose
public Foo fooB;
}

要序列化为 JSON,您将使用如下所示的代码:

public String convertToJSON(FooContainer fc) {
if (fc != null) {
GsonBuilder gson = new GsonBuilder();
return gson.excludeFieldsWithoutExposeAnnotation().create().toJson(fc);
}
return "";
}

例如,列表看起来相同:

public String convertToJSON(List<FooContainer> fcs) {
if (fcs != null) {
GsonBuilder gson = new GsonBuilder();
return gson.excludeFieldsWithoutExposeAnnotation().create().toJson(fcs);
}
return "";
}

@JsonIgnoreProperties批注可用于fooB属性,FooContainer仅在该特定上下文中忽略biz属性。

public class FooContainer {
public Foo fooA;
@JsonIgnoreProperties({ "biz" })
public Foo fooB;
}

注意:您在 2012 年的编辑中提到了这一点,但我将其作为自己的答案写出来,因为我觉得这是解决这个特定问题的最佳解决方案。

最新更新