Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specific MessageBodyWriter for field

Say I have a data class in a JAX-RS 1 environment (RestEasy 2 with the Jackson provider) like this:

class Foo {
   int id;
   String name;
   Bar bar;

   ...
}

with Bar being:

class Bar {
   int one;
   String two;
}

Now I want to have Bar serialized in a special way (perhaps depending on the media type that was requested (or depending the phase of the moon), I would write a MessageBodyWriter<Bar>

@Provider
@Produces("application/json")
public class BarWriter implements MessageBodyWriter<Bar> {
   ...
}

which works very well if Bar is requested on its own like in

@GET @Path("bar")  
public Bar getBar() { return new Bar(...); }

But when I request Foo as in

@GET @Path("foo")  
public Foo getFoo() { return new Foo(...); }

the message body writer is ignored.

Now what I want is that this MessageBodyWriter is also used when I return Foo or a List<Bar>

I think the latter can be achieved by just writing a custom MessageBodyWriter for the List case, but for the former case I can't write a message body writer for all my application classes that contain a Bar field.

Any ideas on how to solve this? I was also trying to use a Jackson serializer on the Bar instance, but it looks like this is not even registered by RestEasy (and then, I think that way is too fragile anyway).

like image 900
Heiko Rupp Avatar asked Mar 05 '13 17:03

Heiko Rupp


1 Answers

Unfortunately, this is not how message body writers work. The JAX-RS implementation will locate a writer, to be used in serialization, based on the type being returned from your resource method. So in your case, with a custom writer defined for Bar, with this resource method:

@GET @Path("bar")  
public Bar getBar() { return new Bar(...); }

the JAX-RS provider will serialize Bar using your custom writer. However for this resource method:

@GET @Path("foo")  
public Foo getFoo() { return new Foo(...); }

you do not have a custom writer defined, and serialization will be handled by the first matching (default) provider that can handle the combination of return class and content-type. A key thing to remember is that, unlike typical JSON and XML serialization libraries, JAX-RS entity providers are not recursive. Aka, for a given object A being returned in a resource method, the provider will attempt to locate a custom writer only for A, and not for any of the types included in A as variables.

Since you are using Jackson though, why not just define a custom serializer for your Bar class? That will handle pretty much every scenario you described:

public class BarSerializer extends JsonSerializer<Bar> {

    @Override
    public void serialize(final Bar value, final JsonGenerator jgen,
            final SerializerProvider provider) throws IOException,
            JsonProcessingException {

        jgen.writeStartObject();
        jgen.writeFieldName("myBar");
        jgen.writeString(value.getTwo());
        jgen.writeEndObject();
    }
}

You tell Jackson to use this custom serializer thusly:

@JsonSerialize(using=BarSerializer.class)
class Bar {
   int one;
   String two;
}

Lastly, don't forget that if you anticipate getting JSON back in the same form as you serialized, that you will also need a custom JsonDeserializer.

To get it to work, you need the jackson-mapper and jackson-jaxrs jars in your classpath (and probably the jackson-core one as well).

like image 126
Perception Avatar answered Sep 17 '22 23:09

Perception