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

后端 未结 11 1240
南方客
南方客 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:42

    For my use case, I could use a much simpler way. In a the base class I have for all my "Jackson Pojos" I add:

    protected Map<String,Object> dynamicProperties = new HashMap<String,Object>();
    
    ...
    
    
    public Object get(String name) {
        return dynamicProperties.get(name);
    }
    
    // "any getter" needed for serialization    
    @JsonAnyGetter
    public Map<String,Object> any() {
        return dynamicProperties;
    }
    
    @JsonAnySetter
    public void set(String name, Object value) {
        dynamicProperties.put(name, value);
    }
    

    I can now deserialize to Pojo, work with fields and reserialize witjout losing any properties. I can also add/change non pojo properties:

    // Pojo fields
    person.setFirstName("Annna");
    
    // Dynamic field
    person.set("ex", "test");
    

    (Got it from Cowtowncoder)

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

    You can do this (previous version did not work with Jackson after 2.6, but this works with Jackson 2.7.3):

    public static class CustomModule extends SimpleModule {
        public CustomModule() {
            addSerializer(CustomClass.class, new CustomClassSerializer());
        }
    
        private static class CustomClassSerializer extends JsonSerializer {
            @Override
            public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
                //Validate.isInstanceOf(CustomClass.class, value);
                jgen.writeStartObject();
                JavaType javaType = provider.constructType(CustomClass.class);
                BeanDescription beanDesc = provider.getConfig().introspect(javaType);
                JsonSerializer<Object> serializer = BeanSerializerFactory.instance.findBeanSerializer(provider,
                        javaType,
                        beanDesc);
                // this is basically your 'writeAllFields()'-method:
                serializer.unwrappingSerializer(null).serialize(value, jgen, provider);
                jgen.writeObjectField("my_extra_field", "some data");
                jgen.writeEndObject();
            }
        }
    }
    

    Update:

    I tried it out with Jackson 2.9.0 and 2.9.6 and it worked as expected with both. Perhaps try this out: http://jdoodle.com/a/z99 (run it locally - jdoodle apparently can't handle Jackson).

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

    Another and perhaps the most simple solution:

    Make serialisation a 2-step process. First create a Map<String,Object> like:

    Map<String,Object> map = req.mapper().convertValue( result, new TypeReference<Map<String,Object>>() {} );
    

    then add the properties you want like:

    map.put( "custom", "value" );
    

    then serialise this to json:

    String json = req.mapper().writeValueAsString( map );
    
    0 讨论(0)
  • 2020-11-27 03:46

    We can extend BeanSerializer, but with little trick.

    First, define a java class to wrapper your POJO.

    @JsonSerialize(using = MixinResultSerializer.class)
    public class MixinResult {
    
        private final Object origin;
        private final Map<String, String> mixed = Maps.newHashMap();
    
        @JsonCreator
        public MixinResult(@JsonProperty("origin") Object origin) {
            this.origin = origin;
        }
    
        public void add(String key, String value) {
            this.mixed.put(key, value);
        }
    
        public Map<String, String> getMixed() {
            return mixed;
        }
    
        public Object getOrigin() {
            return origin;
        }
    
    }
    

    Then,implement your custom serializer.

    public final class MixinResultSerializer extends BeanSerializer {
    
        public MixinResultSerializer() {
            super(SimpleType.construct(MixinResult.class), null, new BeanPropertyWriter[0], new BeanPropertyWriter[0]);
        }
    
        public MixinResultSerializer(BeanSerializerBase base) {
            super(base);
        }
    
        @Override
        protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException {
            if (bean instanceof MixinResult) {
                MixinResult mixin  = (MixinResult) bean;
                Object      origin = mixin.getOrigin();
    
                BeanSerializer serializer = (BeanSerializer) provider.findValueSerializer(SimpleType.construct(origin.getClass()));
    
                new MixinResultSerializer(serializer).serializeFields(origin, gen, provider);
    
                mixin.getMixed().entrySet()
                        .stream()
                        .filter(entry -> entry.getValue() != null)
                        .forEach((entry -> {
                            try {
                                gen.writeFieldName(entry.getKey());
                                gen.writeRawValue(entry.getValue());
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }));
            } else {
                super.serializeFields(bean, gen, provider);
            }
    
        }
    
    }
    

    This way, we can handle the case that origin object using jackson annotations to custom serialize behavior.

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

    Though this question is already answered, I found another way that requires no special Jackson hooks.

    static class JsonWrapper<T> {
        @JsonUnwrapped
        private T inner;
        private String extraField;
    
        public JsonWrapper(T inner, String field) {
            this.inner = inner;
            this.extraField = field;
        }
    
        public T getInner() {
            return inner;
        }
    
        public String getExtraField() {
            return extraField;
        }
    }
    
    static class BaseClass {
        private String baseField;
    
        public BaseClass(String baseField) {
            this.baseField = baseField;
        }
    
        public String getBaseField() {
            return baseField;
        }
    }
    
    public static void main(String[] args) throws JsonProcessingException {
        Object input = new JsonWrapper<>(new BaseClass("inner"), "outer");
        System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(input));
    }
    

    Outputs:

    {
      "baseField" : "inner",
      "extraField" : "outer"
    }
    

    For writing collections, you can simply use a view:

    public static void main(String[] args) throws JsonProcessingException {
        List<BaseClass> inputs = Arrays.asList(new BaseClass("1"), new BaseClass("2"));
        //Google Guava Library <3
        List<JsonWrapper<BaseClass>> modInputs = Lists.transform(inputs, base -> new JsonWrapper<>(base, "hello"));
        System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(modInputs));
    }
    

    Output:

    [ {
      "baseField" : "1",
      "extraField" : "hello"
    }, {
      "baseField" : "2",
      "extraField" : "hello"
    } ]
    
    0 讨论(0)
  • 2020-11-27 03:48

    Inspired from what wajda said and written in this gist:

    Here is how to add a listener for bean serialization in jackson 1.9.12. In this example, the listerner is considered as a Chain Of Command which interface is :

    public interface BeanSerializerListener {
        void postSerialization(Object value, JsonGenerator jgen) throws IOException;
    }
    

    MyBeanSerializer.java:

    public class MyBeanSerializer extends BeanSerializerBase {
        private final BeanSerializerListener serializerListener;
    
        protected MyBeanSerializer(final BeanSerializerBase src, final BeanSerializerListener serializerListener) {
            super(src);
            this.serializerListener = serializerListener;
        }
    
        @Override
        public void serialize(final Object bean, final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonGenerationException {
            jgen.writeStartObject();
            if (_propertyFilterId != null) {
                serializeFieldsFiltered(bean, jgen, provider);
            } else {
                serializeFields(bean, jgen, provider);
            }
    
            serializerListener.postSerialization(bean, jgen);
    
            jgen.writeEndObject();
        }
    }
    

    MyBeanSerializerBuilder.java:

    public class MyBeanSerializerBuilder extends BeanSerializerBuilder {
        private final BeanSerializerListener serializerListener;
    
        public MyBeanSerializerBuilder(final BasicBeanDescription beanDesc, final BeanSerializerListener serializerListener) {
            super(beanDesc);
            this.serializerListener = serializerListener;
        }
    
        @Override
        public JsonSerializer<?> build() {
            BeanSerializerBase src = (BeanSerializerBase) super.build();
            return new MyBeanSerializer(src, serializerListener);
        }
    }
    

    MyBeanSerializerFactory.java:

    public class MyBeanSerializerFactory extends BeanSerializerFactory {
    
        private final BeanSerializerListener serializerListener;
    
        public MyBeanSerializerFactory(final BeanSerializerListener serializerListener) {
            super(null);
            this.serializerListener = serializerListener;
        }
    
        @Override
        protected BeanSerializerBuilder constructBeanSerializerBuilder(final BasicBeanDescription beanDesc) {
            return new MyBeanSerializerBuilder(beanDesc, serializerListener);
        }
    }
    

    The last class below shows how to provide it using Resteasy 3.0.7:

    @Provider
    public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
        private final MapperConfigurator mapperCfg;
    
        public ObjectMapperProvider() {
            mapperCfg = new MapperConfigurator(null, null);
            mapperCfg.setAnnotationsToUse(new Annotations[]{Annotations.JACKSON, Annotations.JAXB});
            mapperCfg.getConfiguredMapper().setSerializerFactory(serializerFactory);
        }
    
        @Override
        public ObjectMapper getContext(final Class<?> type) {
            return mapperCfg.getConfiguredMapper();
        }
    }
    
    0 讨论(0)
提交回复
热议问题