spring-bootjacksonjackson2jackson-modules

Using @JsonUnwrapped and having a custom Serializer (StdSerializer<T>)


I need to write a custom Serializer for one of my entities "Payment" and I have to implement it by extending StdSerializer:

class Payment {
}

class PaymentSerializer extends StdSerializer<Payment> {

    public PaymentSerializer() {
        this(null);
    }

    public PaymentSerializer(Class<Payment> t) {
        super(t);
    }

    @Override
    public void serialize(Payment value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        // some logics
    }

}

Since I use Spring, I register this Serializer so Spring could identify it:

@Bean
public Jackson2ObjectMapperBuilder serializersObjectMapperBuilder() {
    SimpleModule module = new SimpleModule();
    module.addSerializer(Payment.class, applicationContext.getBean(PaymentSerializer.class));
    return new Jackson2ObjectMapperBuilder().modules(module);
}

Now I have a controller that returns data back to the client and it uses this Serializer without any problem:

@RestController
@RequestMapping("/payment")
class PaymentController {

    @GetMapping
    public List<Payment> getAll() {
        return Arrays.asList(new Payment());
    }

}

Since now, my Serializer works fine and everything is good.

The problem is with another entity "Order" which has Payment as a property with @JsonUnwrapped:

class Order {

    @JsonUnwrapped
    private Payment payment;

}

I need to unwrap the Payment inside the Order and I want to use the same PaymentSerializer but the problem is when I use this custom Serializer, the @JsonUnwrapped annotation will be ignored and the output will be something like this:

{
  "payment": {
    .....
  }
}

As I mentioned, I want to eliminate the "payment" field and unwrap it. I know that for emulating @JsonUnwrapped for a custom Serializer I need to extend UnwrappingBeanSerializer class, but as I mentioned at first, I need the standard Serializer too.

Changing my entity models is not an option for me.

Is there any way to accomplish this?

I use Spring Boot 2.1.3.RELEASE which I believe uses Jackson 2.9


Solution

  • I propose the following way to solve issue: We can compose two StdSerializer in single custom serializer.

    @Component
    class PaymentSerializer extends StdSerializer<Payment> {
    
        private final JsonSerializer<Payment> delegate = new UnwrappingPaymentSerializer(NameTransformer.NOP);
    
        public PaymentSerializer() {
            this(null);
        }
    
        public PaymentSerializer(Class<Payment> t) {
            super(t);
        }
    
        @Override
        public void serialize(Payment value, JsonGenerator generator, SerializerProvider provider) throws IOException {
            generator.writeStartObject();
            this.delegate.serialize(value, generator, provider);
            generator.writeEndObject();
        }
    
        @Override
        public JsonSerializer<Payment> unwrappingSerializer(final NameTransformer nameTransformer) {
            return new UnwrappingPaymentSerializer(nameTransformer);
        }
    
    }
    

    And unwrapping:

    public class UnwrappingPaymentSerializer extends JsonSerializer<Payment> {
    
        private NameTransformer transformer;
    
        public UnwrappingPaymentSerializer(NameTransformer transformer) {
            this.transformer = transformer;
        }
    
        @Override
        public void serialize(Payment value, JsonGenerator generator, SerializerProvider provider) throws IOException {
            // some logics
        }
    
        @Override
        public boolean isUnwrappingSerializer() {
            return true;
        }
    }
    

    As result you have single serializer for serialization and unwrapping. For more details see: https://michael-simons.github.io/simple-meetup/unwrapping-custom-jackson-serializer

    Let me know if solution is not applicable, and I will try to propose other way depends on project requirements.