Search code examples
javajsonjacksonwrapperjsonserializer

@JsonSerialize - How to create a wrapper at runtime and use default serialization for the object fields?


I want to add a wrapper which named is determined at runtime, because it depends of the class name (I could use @JsonRootName but I don't want to because I would have to use it on every sub class, which is not efficient).

I suppose I should use @JsonSerialize to override the default serializer, but I want that just to create the wrapper; I don't want to serialize the object fields myself (also I am in an abstract class, so I don't even know the fields of the sub class!). I don't care about them, I just care about the wrapper! So I would like the default serializer to handle those fields for me, or something like that.

@JsonSerialize(using = CustomSerializer.class)
public abstract class Request {

    public static class CustomSerializer extends JsonSerializer<Request > {
        @Override
        public void serialize(Request request, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            // Doing my stuff to determine the wrapper name based on request.class.getSimpleName()
            // Then what should I wright to serialize the fields?
            // Basically I just want a function to generate the same json that the default serializer would generate!

            // I tried the following, but obviously it gives a com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion
            jgen.writeObject(value);

            // Same error for the function below 
            provider.defaultSerializeValue(value, jgen);
        }
    }

Solution

  • To create wrapper serialiser you need to use com.fasterxml.jackson.databind.ser.BeanSerializerModifier class. You can register it using com.fasterxml.jackson.databind.module.SimpleModule. Below example shows end-to-end solution how to do that:

    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.SerializationFeature;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.json.JsonMapper;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
    import com.fasterxml.jackson.databind.util.NameTransformer;
    
    import java.io.IOException;
    import java.util.UUID;
    
    public class JsonPathApp {
    
        public static void main(String[] args) throws Exception {
            SimpleModule wrappersModule = new SimpleModule("requestWrapper");
            wrappersModule.setSerializerModifier(new BeanSerializerModifier() {
                @Override
                public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
                    if (Request.class.isAssignableFrom(beanDesc.getBeanClass())) {
                        return new RequestWrapperJsonSerializer(serializer);
                    }
                    return serializer;
                }
            });
            ObjectMapper mapper = JsonMapper.builder()
                    .enable(SerializationFeature.INDENT_OUTPUT)
                    .addModule(wrappersModule)
                    .build();
    
            System.out.println(mapper.writeValueAsString(new Request1(1, "POST")));
            System.out.println(mapper.writeValueAsString(new Request2(2, UUID.randomUUID())));
        }
    }
    
    class RequestWrapperJsonSerializer extends JsonSerializer<Request> {
    
        private final JsonSerializer baseSerializer;
    
        public RequestWrapperJsonSerializer(JsonSerializer baseSerializer) {
            this.baseSerializer = baseSerializer;
        }
    
        @Override
        public void serialize(Request value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeStartObject();
            gen.writeFieldName(value.getClass().getSimpleName() + "Wrapper");
            gen.writeStartObject();
            baseSerializer.unwrappingSerializer(NameTransformer.NOP).serialize(value, gen, serializers);
            gen.writeEndObject();
            gen.writeEndObject();
        }
    }
    
    abstract class Request {
        private int id;
    
        //constructor, getters, setters, toString
    }
    
    class Request1 extends Request {
        private String body;
    
        //constructor, getters, setters, toString
    }
    
    class Request2 extends Request {
    
        private UUID uuid;
    
        //constructor, getters, setters, toString
    }
    

    Above code prints:

    {
      "Request1Wrapper" : {
        "id" : 1,
        "body" : "POST"
      }
    }
    {
      "Request2Wrapper" : {
        "id" : 2,
        "uuid" : "dd4cccb5-1cf5-4dd4-8bc7-97cb101e5d7d"
      }
    }
    

    Instead unwrappingSerializer method you can use serialize method and remove extra wrapping invocations.