Specific MessageBodyWriter for field

2019-03-30 02:21发布

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).

3条回答
劫难
2楼-- · 2019-03-30 02:57

The JAX-RS runtime will only look up one MessageBodyWriter for the object returned by the resource method (see sectioin "4.2.2 Message Body Writer" in the specification), then that single MessageBodyWriter has complete control over the serialization of the entire object graph to be returned to the client.

In order to implement the behavior you wanted, you would need a custom MessageBodyWriter per media type, that is willing to delegate the serialization of a part of the object graph to another writer whenever it encounters a specific type in the graph, and then resume its own logic. Obtaining the delegate writer for the specific type wouldn't be a big problem (inject a javax.ws.rs.ext.Providers and call getMessageBodyWriter()), but I don't think the existing xml/json/etc serializers are implemented with such kind of extensions in mind, so I guess you couldn't just relay on them. Reimplementing an xml marshaller just for this is not an attractive option either.

查看更多
狗以群分
3楼-- · 2019-03-30 03:15

Refer the below post for writing custom message body writer for your Java object's serialization.

http://h2labz.blogspot.in/2014/12/marshalling-java-to-json-in-jax-rs.html

查看更多
神经病院院长
4楼-- · 2019-03-30 03:17

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).

查看更多
登录 后发表回答