1

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);
        }
    }
Flyout91
  • 782
  • 10
  • 31

2 Answers2

1

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.

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • 1
    I saw this solution on https://www.baeldung.com/jackson-call-default-serializer-from-custom-serializer. It is exactly what I was asking for, even though it unfortunately is a bit complex for a need that is simple. Thank you. – Flyout91 Dec 20 '19 at 10:46
1

Even though the solution I accepted is correct, I propose an other solution I got from a similar stackoverflow question, and which relies on a trick: make the class to custom-serialize a field of an other class (purely used for wrapping) and use @JsonSerialize on this field. The code is simpler, but you have to create and manipulate the wrapper class. See below:

public class RequestWrapper {

    @JsonUnwrapped // Prevent the field from being wrap with its name: "request"
    @JsonSerialize(using = RequestSerializer.class)
    private final Request request;

    private RequestWrapper(Request request) {
        this.request= request;
    }

    public Request getRequest() {
        return request;
    }

    public static class RequestSerializer extends JsonSerializer<Request> {
        @Override
        public boolean isUnwrappingSerializer() {
            return true; // Indicates that we are creating an "unwrapping" serializer, because we added @JsonUnwrapped
        }

        @Override
        public void serialize(Request request, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeObjectField(request.getClass().getSimpleName(), value);
        }
    }
}
Flyout91
  • 782
  • 10
  • 31