8

I am using Jersey to implement RESTful webservice. Now the MediaType in which I return data is JSON.

@GET
@Produces({MediaType.APPLICATION_JSON })
public Response service() {
    return Response
            .ok(entity)
            .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
            .build();
}

Here I set CONTENT_TYPE to json and my entity will be converted to json by Jersey framework.

Now I want to customize my json response.

Eg: I want to remove the empty elements or change the keys name of my Json object. Default Jersey's Json conversion:

{
   "cinter" : {
     "state" : 1,
     "checks" : 10,
   }
}

What I want:

{
   "cin" : {
     "st" : 1,
     "cs" : 10,
   }
}

I know I can use Jackson library's my own ObjectMapper to customize my Json according to my needs.

But is this the standard way to do it if I want JSON conversion in a different way than Jersey's default conversion ??

Or can I change paramaters in Jersey's ObjectMapper ??

Should I be using my own ObjectMapper ?

Siddharth Trikha
  • 2,648
  • 8
  • 57
  • 101
  • What you POJO looks like? Because it can easily be solved with @JsonProperty(name="cit") and so on above field names. If you cant change you pojo, then yep, you have to use custom Mixin or serializer on you `ObjectMapper` – varren May 20 '16 at 07:44
  • @My POJO has JAXB annotations (@XmlRootElement) ans yes I can't change them. So for every different response I should configure `ObjectMapper` differently ? Any idea about `ContextResolver` ? – Siddharth Trikha May 20 '16 at 08:27
  • You can't _change_ them, but can you _add_ annotations? If you can, `@JsonProperty` on the properties and `@JsonRootName` on the class with solve your problem – Paul Samsotha May 20 '16 at 11:25

1 Answers1

5

Here is my thoughts about your options. First of all

So for every different response I should configure ObjectMapper differently ?

If you want to use both json versions in different places like this

public Response getObject()           // returns {"cinter" : {"state" : 1,"checks" : 10}}
public Response getShortNamesObject() // returns {"cin" : {"st" : 1,"cs" : 10}}

Than yep, you have to use multiple ObjectMappers.

But if you just want to use 1 representation everywhere, then you probably will be able to setup Jackson once with custom mixin for you classes. Anyway here is how you can do both options: And lets look at simple case with just 1 json version needed

public class TestBean {
    private String name;
    private int id;
    //getters and setters
}

public interface TestBeanMixin {
    @JsonProperty("short_field_name")
    String getName();
    @JsonProperty("short_field_id")
    int getId();
}

@Provider
@Priority(1)
public class MixInJacksonJsonProvider extends JacksonJaxbJsonProvider {
    private static final ObjectMapper mapper = createMapper();

    public MixInJacksonJsonProvider() {
        setMapper(mapper);
    }

    private static ObjectMapper createMapper() {
        final ObjectMapper result = new ObjectMapper();
        result.addMixIn(TestBean.class, TestBeanMixin.class);
        return result;
    }
}

This code will produce short names for you POJO fields everywhere. and to implement different behavior for different request we have to add new custom annotation like this:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface MixIn {}

Controller will look like this:

@Path("test")
public class MyResource {

    @GET 
    @MixIn // <== Here is important part
    @Produces(MediaType.APPLICATION_JSON )
    public Response  getShortName() {
        return Response.ok(demoObj()).build();
    }

    @POST
    @Produces(MediaType.APPLICATION_JSON )
    public Response  postLongName() {
        return Response.ok(demoObj()).build();
    }
}

And our MixInJacksonJsonProvider will have 2 more @Override:

    //.. same as before

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return super.isReadable(type, genericType, annotations, mediaType) && hasMixInAnnotation(annotations);
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return super.isWriteable(type, genericType, annotations, mediaType) && hasMixInAnnotation(annotations);
    }
    public static boolean hasMixInAnnotation(Annotation[] annotations){
        for(Annotation annotation: annotations){
            if (annotation instanceof MixIn){
                return true;
            }
        }
        return false;
    }
}

Here is demo code for you to play: https://github.com/varren/jersey2-jacksonsetup/tree/master/src/main/java/ru/varren

varren
  • 14,551
  • 2
  • 41
  • 72
  • Along with short names, I will also like to have other configurations like `objectMapper.WRAP_ROOT_VALUE` in some Pojos and maybe not in others. So will all configurations provided in `ObjectMapper` be available through annotations in MixIn classes? – Siddharth Trikha May 23 '16 at 04:06
  • @SiddharthTrikha it really depends on what you want. If you want 1 pojo to have same representation all the time with WRAP_ROOT for example and other pojo without, you can do that with just 1 objectmapper. You can change your class serializer or add annotations to your class or use MixIn. It doesn't matter really. But if you need to have multiple json versions of the same POJO, then you have to create multiple object mappers and use different mixins/serializers for them + use custom annotations in your controller to specify which one should use customized version. – varren May 23 '16 at 11:22