Search code examples
javajacksonjerseyserialization

Toggle JSON serializers for different endpoints


In Jersey's endpoints I want to return same DTO but serialise it differently by using different serialisers: different Date formats needed.

public class Foo {
    private Date foo;

    public Foo() {
        this.foo = new Date();
    }

    public Date getFoo() {
        return foo;
    }

    public void setFoo(Date foo){
        this.foo = foo;
    }
}

public class MyEndpointsUnix {
    @GET
    @Path("/dateAsUnix")
    public Foo getDateAsUnix() {
        return new Foo();
    }

}

public class MyEndpointsUTC {
    @GET
    @Path("/dateAsUTC")
    public Foo getdateAsUTC() {
        return new Foo();
    }
}

I suppose it should be possible to change serialisers for response manually.


Solution

  • From OOP point of view we can create new class for every kind of view:

    class UnixFoo extends Foo {
    
        private Foo foo;
    
        public UnixFoo(Foo foo) {
            this.foo = foo;
        }
    
        @JsonFormat(pattern = "yyyy-MM-dd")
        @Override
        public Date getFoo() {
            return foo.getFoo();
        }
    
        // other getters
    }
    

    and in our controller we can:

    public class MyEndpointsUnix {
        @GET
        @Path("/dateAsUnix")
        public Foo getDateAsUnix() {
            return new UnixFoo(new Foo());
        }
    }
    

    Of course this solution has a downside that we need to copy our DTO classes. To avoid that we can use Jackson MixIn Annotation. To do that we should create new interface:

    interface UnixFooMixIn {
    
        @JsonFormat(pattern = "yyyy-MM-dd")
        Date getFoo();
    }
    

    and enrich ObjectMapper with it:

    public class MyEndpointsUnix {
        @GET
        @Path("/dateAsUnix")
        public String getDateAsUnix() {
            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            mapper.addMixIn(Foo.class, UtcFooMixIn.class);
    
            return mapper.writeValueAsString(new Foo());
        }
    }
    

    In this case we need to change our method signature and return String. Also we can create this ObjectMapper once and use it as singleton. For each kind of view we need to define new interface and new ObjectMapper instance.