Jackson - custom serializer that overrides only specific fields

后端 未结 5 1571
灰色年华
灰色年华 2020-12-09 10:01

I know how to use a custom serializer in Jackson (by extending JsonSerializer), but I want the default serializer to work for all fields, except for just 1 fiel

相关标签:
5条回答
  • 2020-12-09 10:42

    with the help of @JsonView we can decide fields of model classes to serialize which satisfy the minimal criteria ( we have to define the criteria) like we can have one core class with 10 properties but only 5 properties can be serialize which are needful for client only

    Define our Views by simply creating following class:

    public class Views
    {
        static class Android{};
        static class IOS{};
        static class Web{};
    }
    

    Annotated model class with views:

    public class Demo 
    {
        public Demo() 
        {
        }
    
    @JsonView(Views.IOS.class)
    private String iosField;
    
    @JsonView(Views.Android.class)
    private String androidField;
    
    @JsonView(Views.Web.class)
    private String webField;
    
     // getters/setters
    ...
    ..
    }
    

    Now we have to write custom json converter by simply extending HttpMessageConverter class from spring as:

        public class CustomJacksonConverter implements HttpMessageConverter<Object> 
        {
        public CustomJacksonConverter() 
            {
                super();
            //this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.ClientView.class));
            this.delegate.getObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
            this.delegate.getObjectMapper().setSerializationInclusion(Include.NON_NULL);
    
        }
    
        // a real message converter that will respond to methods and do the actual work
        private MappingJackson2HttpMessageConverter delegate = new MappingJackson2HttpMessageConverter();
    
        @Override
        public boolean canRead(Class<?> clazz, MediaType mediaType) {
            return delegate.canRead(clazz, mediaType);
        }
    
        @Override
        public boolean canWrite(Class<?> clazz, MediaType mediaType) {
            return delegate.canWrite(clazz, mediaType);
        }
    
        @Override
        public List<MediaType> getSupportedMediaTypes() {
            return delegate.getSupportedMediaTypes();
        }
    
        @Override
        public Object read(Class<? extends Object> clazz,
                HttpInputMessage inputMessage) throws IOException,
                HttpMessageNotReadableException {
            return delegate.read(clazz, inputMessage);
        }
    
        @Override
        public void write(Object obj, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException 
        {
            synchronized(this) 
            {
                String userAgent = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("userAgent");
                if ( userAgent != null ) 
                {
                    switch (userAgent) 
                    {
                    case "IOS" :
                        this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.IOS.class));
                        break;
                    case "Android" :
                        this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.Android.class));
                        break;
                    case "Web" :
                        this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( Views.Web.class));
                        break;
                    default:
                        this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
                        break;
                    }
                }
                else
                {
                    // reset to default view
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
                }
                delegate.write(obj, contentType, outputMessage);
            }
        }
    
    }
    

    Now there is need to tell spring to use this custom json convert by simply putting this in dispatcher-servlet.xml

    <mvc:annotation-driven>
            <mvc:message-converters register-defaults="true">
                <bean id="jsonConverter" class="com.mactores.org.CustomJacksonConverter" >
                </bean>
            </mvc:message-converters>
        </mvc:annotation-driven>
    

    That's how you will able to decide which fields to get serialize.

    Thanx

    0 讨论(0)
  • 2020-12-09 10:50

    In case you don't want to pollute your model with annotations, you could use mixins.

    ObjectMapper mapper = new ObjectMapper();
    SimpleModule simpleModule = new SimpleModule();
    simpleModule.setMixInAnnotation(Student.class, StudentMixin.class);
    mapper.registerModule(simpleModule);
    

    And you want to override id field for example:

    public abstract class StudentMixin {
        @JsonSerialize(using = StudentIdSerializer.class)
        public String id;
    }
    

    Do whatever you need with the field:

    public class StudentIdSerializer extends JsonSerializer<Integer> {
        @Override
        public void serialize(Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeString(String.valueOf(integer * 2));
        }
    }
    
    0 讨论(0)
  • 2020-12-09 10:51

    I faced the same issue, and I solved it with CustomSerializerFactory.

    This approach allows you to ignore some specific field for either for all objects, or for specific types.

    public class EntityCustomSerializationFactory extends CustomSerializerFactory {
    
        //ignored fields
        private static final Set<String> IGNORED_FIELDS = new HashSet<String>(
                Arrays.asList(
                        "class",
                        "value",
                        "some"
                )
        );
    
    
        public EntityCustomSerializationFactory() {
            super();
        }
    
        public EntityCustomSerializationFactory(Config config) {
            super(config);
        }
    
        @Override
        protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) {
            super.processViews(config, builder);
    
            //ignore fields only for concrete class
            //note, that you can avoid or change this check
            if (builder.getBeanDescription().getBeanClass().equals(Entity.class)){
                //get original writer
                List<BeanPropertyWriter> originalWriters = builder.getProperties();
    
                //create actual writers
                List<BeanPropertyWriter> writers = new ArrayList<BeanPropertyWriter>();
    
                for (BeanPropertyWriter writer: originalWriters){
                    String propName = writer.getName();
    
                    //if it isn't ignored field, add to actual writers list
                    if (!IGNORED_FIELDS.contains(propName)){
                        writers.add(writer);
                    }
                }
    
                builder.setProperties(writers);
            }
    
        }
    }
    

    And afterwards you can use it something like the following:

    objectMapper.setSerializerFactory(new EntityCustomSerializationFactory());
    objectMapper.writeValueAsString(new Entity());//response will be without ignored fields
    
    0 讨论(0)
  • 2020-12-09 10:58

    Just because you can not modify classes DOES NOT mean you could not use annotations: just use mix-in annotations. See this blog entry for example (or google for more with "jackson mixin annotations") for how to use this.

    I have specifically used Jackson with protobuf- and thrift-generated classes, and they work pretty well. For earlier Thrift versions, I had to disable discovery of "is-setters", methods Thrift generates to see if a specific property has been explicitly set, but otherwise things worked fine.

    0 讨论(0)
  • 2020-12-09 11:03

    Assuming your Target class is

    public class Student {
        int age;
        String firstName;
        String lastName;
        double average;
        int numSubjects;
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public double getAverage() {
            return average;
        }
    
        public void setAverage(double average) {
            this.average = average;
        }
    
        public int getNumSubjects() {
            return numSubjects;
        }
    
        public void setNumSubjects(int numSubjects) {
            this.numSubjects = numSubjects;
        }
    
    }
    

    You need to write a custom serializer as given below

    public class MyCustomSerializer extends JsonSerializer<Student> {
    
        @Override
        public void serialize(Student value, JsonGenerator jgen,
                SerializerProvider provider) throws IOException,
                JsonProcessingException {
            if (value != null) {
                jgen.writeStartObject();
                jgen.writeStringField("age", "Age: " + value.getAge()); //Here a custom way to render age field is used
                jgen.writeStringField("firstName", value.getFirstName());
                jgen.writeStringField("lastName", value.getLastName());
                jgen.writeNumberField("average", value.getAverage());
                jgen.writeNumberField("numSubjects", value.getNumSubjects());
                //Write other properties
                jgen.writeEndObject();
            }
        }
    
    }
    

    then add it to the ObjectMapper

    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule("custom",
            Version.unknownVersion());
    module.addSerializer(Student.class, new MyCustomSerializer());
    mapper.registerModule(module);
    

    then use it like

    Student s = new Student();
    s.setAge(2);
    s.setAverage(3.4);
    s.setFirstName("first");
    s.setLastName("last");
    s.setNumSubjects(3);
    
    StringWriter sw = new StringWriter();
    mapper.writeValue(sw, s);
    System.out.println(sw.toString());
    

    It will produce a o/p like

    {"age":"Age: 2","firstName":"first","lastName":"last","average":3.4,"numSubjects":3}

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