How do I serialize an associated object differently using Jackson and annotations?

前端 未结 3 1869
无人及你
无人及你 2021-02-04 13:42

Given the following class hierarchy, I would like Foo to be serialized differently depending on the context it is used in my class hierarchy.

public class Foo {
         


        
相关标签:
3条回答
  • 2021-02-04 13:58

    You could use a combination of a custom serializer with a custom property filter using JsonViews. Here is some code working with Jackson 2.0

    Define a custom annotation:

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

    Define some Views:

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

    Then you can write your entities like this. Note that you may define your own annotation instead of using @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;
    }
    

    Then, here is where the code begins :) First your custom filter:

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

    Then a custom AnnotationIntrospector that will do two things:

    1. Enable your custom filter for any bean... Unless another filter is defined on your class (so you cannot use both of them, if you see what I mean)
    2. Enable your CustomSerializer if he found a @FilterUsingView annotation.

    Here is the code

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

    Here is your custom serializer. The only thing it does is passing your annotation's value to your custom filter, then it let the default serializer do the job.

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

    Finally ! Let's put this all together :

    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());
    }
    
    0 讨论(0)
  • 2021-02-04 14:10

    I would use the google code gson
    documentation in here https://code.google.com/p/google-gson/
    Maven dependency is:

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

    The annotations are like this:
    To expose the field user the @Expose annotation
    To generate a special name for the field in the parsed json user the @SerializedName("fieldNameInJSON") annotation
    So your classes would look like this:

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

    To serialize to JSON you will use a code that looks like this:

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

    It would look the same for Lists for example:

    public String convertToJSON(List<FooContainer> fcs) {
                if (fcs != null) {
                    GsonBuilder gson = new GsonBuilder();
                    return gson.excludeFieldsWithoutExposeAnnotation().create().toJson(fcs);
                }
                return "";
            }
    
    0 讨论(0)
  • 2021-02-04 14:12
    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());
        }
    
    }
    

    Using @JsonSerialize(using = FooCustomSerializer.class) on the public Foo fooB; field.

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

    0 讨论(0)
提交回复
热议问题