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

时间:2012-10-01 15:25:12

标签: java json 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,但是必须在mapper层为所有类的实例应用,这与上下文有关。


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

public class FooContainer {
    public Foo fooA;

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

4 个答案:

答案 0 :(得分:10)

您可以使用JsonViews将自定义序列化程序与自定义属性过滤器结合使用。以下是使用Jackson 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注释,请启用CustomSerializer。
  3. 这是代码

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

答案 1 :(得分:0)

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

答案 2 :(得分:0)

我会使用谷歌代码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 "";
        }

答案 3 :(得分:0)

可以在 fooB 中的 FooContainer 属性上使用 @JsonIgnoreProperties 注释以仅在该特定上下文中忽略 biz 属性。

public class FooContainer {
    public Foo fooA;

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

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