Slava Semushin
Slava Semushin

Reputation: 15204

Why Apache CXF converts String to JSON incorrectly?

I faced with unexpected Apache CXF behavior: when I return String from my service it doesn't converted properly to JSON -- instead of adding double quotes around the string, Apache CXF returns it as is.

My service:

@Path(ROOT_URL)
@Produces(MediaType.APPLICATION_JSON)
public class SomeRestService {

    @GET
    @Path(SERVICE_URL)
    public Response getString() {
        return Response.ok("OK").build();
    }

}

Part of Apache CXF configuration:

<jaxrs:server id="RestService" address="/api">
        <jaxrs:serviceBeans>
            <bean class="org.example.project.ws.rest.SomeRestService" />
        </jaxrs:serviceBeans>
        <jaxrs:extensionMappings>
            <entry key="json" value="application/json" />
        </jaxrs:extensionMappings>
</jaxrs:server>

Can you help me to understand why this is working that way and how to tell to CXF return string as really JSON?

Upvotes: 3

Views: 4405

Answers (1)

Donal Fellows
Donal Fellows

Reputation: 137567

JSON is a way of serializing complex objects, which Java's strings are (effectively) not. This means that there is no way to directly serialize a string into a JSON object; the serializer just doesn't know what to map it as and so bails out (or rather it doesn't enable itself for String). There are two possible fixes for this.

Option 1: Use a Different Content Type

The natural mapping for a string is to message content of type text/plain, not application/json. This mapping is built into CXF (and any other JAX-RS implementation too) and it's really easy to make it work. (I prefer to put produces and consumes annotations on individual methods rather than an overall class, but that's personal choice.)

@Path(ROOT_URL)
public class SomeRestService {
    @GET
    @Path(SERVICE_URL)
    @Produces("text/plain")
    public Response getString() {
        return Response.ok("OK").build();
    }
}

Option 2: Wrap the String in a JAXB-Annotated Object

If you really want the data to go as JSON, you need to provide additional metadata. The overwhelmingly-simplest way of doing this by adding a wrapper class that has JAXB annotations, which then allows Jettison (the serialization library used by CXF) to spit the right thing out. In the sample below, I am using a static inner class, because it keeps a (fundamentally boring) class close to the only place that uses it, but YMMV; it does have to be an annotated public “struct” though (i.e., a class with a no-arg constructor and either public fields or suitably trivial getters and setters).

@Path(ROOT_URL)
public class SomeRestService {
    @GET
    @Path(SERVICE_URL)
    @Produces("application/json")
    public Response getServiceMessage() {
        ServiceMessage result = new ServiceMessage();
        result.message = "OK";
        return Response.ok(result).build();
        // Or: return Response.ok(result,"application/json").build();
        // Or: return Response.ok(result).type("application/json").build();
    }

    @XmlRootElement  // This annotation _is_ required, can be customized further
    public static class ServiceMessage {
        @XmlElement  // This annotation isn't required, but documents intention
        public String message;
    }
}

I've omitted building out a full set of constructors for ServiceMessage, which makes using it slightly less convenient than it might be; the code is (almost) purely bare bones minimal.

Upvotes: 1

Related Questions