Jackson: How to add custom property to the JSON without modifying the POJO

后端 未结 11 1241
南方客
南方客 2020-11-27 03:10

I am developing a REST interface for my app using Jackson to serialize my POJO domain objects to JSON representation. I want to customize the serialization for some types to

相关标签:
11条回答
  • 2020-11-27 03:52

    I needed this ability as well; in my case, to support field expansion on REST services. I ended up developing a tiny framework to solve this problem, and it's open sourced on github. It's also available in the maven central repository.

    It takes care of all the work. Simply wrap the POJO in a MorphedResult, and then add or remove properties at will. When serialized, the MorphedResult wrapper disappears and any 'changes' appear in the serialized JSON object.

    MorphedResult<?> result = new MorphedResult<>(pojo);
    result.addExpansionData("my_extra_field", "some data");
    

    See the github page for more details and examples. Be sure to register the libraries 'filter' with Jackson's object mapper like so:

    ObjectMapper mapper = new ObjectMapper();
    mapper.setFilters(new FilteredResultProvider());
    
    0 讨论(0)
  • 2020-11-27 03:53

    Jackson 2.5 introduced the @JsonAppend annotation, which can be used to add "virtual" properties during serialization. It can be used with the mixin functionality to avoid modifying the original POJO.

    The following example adds an ApprovalState property during serialization:

    @JsonAppend(
        attrs = {
            @JsonAppend.Attr(value = "ApprovalState")
        }
    )
    public static class ApprovalMixin {}
    

    Register the mixin with the ObjectMapper:

    mapper.addMixIn(POJO.class, ApprovalMixin.class);
    

    Use an ObjectWriter to set the attribute during serialization:

    ObjectWriter writer = mapper.writerFor(POJO.class)
                              .withAttribute("ApprovalState", "Pending");
    

    Using the writer for serialization will add the ApprovalState field to the ouput.

    0 讨论(0)
  • 2020-11-27 03:54

    We can use reflection to get all the fields of the object you want to parse.

    @JsonSerialize(using=CustomSerializer.class)
    class Test{
      int id;
      String name;
      String hash;
    }    
    

    In custom serializer, we have our serialize method like this :

            @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();
    
            }
    
    0 讨论(0)
  • 2020-11-27 03:57

    Since (I think) Jackson 1.7 you can do this with a BeanSerializerModifier and extending BeanSerializerBase. I've tested the example below with Jackson 2.0.4.

    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();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-27 04:04

    After looking more on the Jackson source code I concluded that it's simply impossible to achieve without writing my own BeanSerializer, BeanSerializerBuilder and BeanSerializerFactory and provide some extension points like:

    /*
    /**********************************************************
    /* 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
    }
    

    Unfortunately I had to copy and paste entire Jackson's BeanSerializer source code to MyCustomBeanSerializer because the former is not developed for extensions declaring all the fields and some important methods (like serialize(...)) as final

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