retrieve JsonObject in POST with jersey

18,719

Solution 1

In order to convert JSON to a Java type, there is need to for a MessageBodyReader and a MessageBodyWriter implementation to do the conversion to and from. Since you are using JsonObject which is a GSON type, you can see this implementation. There is a problem with the implementation though, as the readFrom method doesn't compile with Jersey 2. Here is the a fixed version

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;


@Provider
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public final class GsonMessageBodyHandler implements MessageBodyWriter<Object>,
        MessageBodyReader<Object> {

    private static final String UTF_8 = "UTF-8";

    private Gson gson;

    private Gson getGson() {
        if (gson == null) {
            final GsonBuilder gsonBuilder = new GsonBuilder();
            gson = gsonBuilder.create();
        }
        return gson;
    }

    @Override
    public boolean isReadable(Class<?> type, Type genericType,
            java.lang.annotation.Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    @Override
    public Object readFrom(Class<Object> type, Type type1, Annotation[] antns,
            MediaType mt, MultivaluedMap<String, String> mm, InputStream in) 
            throws IOException, WebApplicationException {
        InputStreamReader streamReader = new InputStreamReader(in, UTF_8);
        try {
            Type jsonType;
            if (type.equals(type1)) {
                jsonType = type;
            } else {
                jsonType = type1;
            }
            return getGson().fromJson(streamReader, jsonType);
        } finally {
            streamReader.close();
        }
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, 
            Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    @Override
    public long getSize(Object object, Class<?> type, Type genericType, 
            Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(Object object, Class<?> type, Type genericType, 
            Annotation[] annotations, MediaType mediaType, 
            MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) 
            throws IOException, WebApplicationException {
        OutputStreamWriter writer = new OutputStreamWriter(entityStream, UTF_8);
        try {
            Type jsonType;
            if (type.equals(genericType)) {
                jsonType = type;
            } else {
                jsonType = genericType;
            }
            getGson().toJson(object, jsonType, writer);
        } finally {
            writer.close();
        }
    }
}

Then we just need to register it with both the client and the application. I'm using a standalone test, where you can see the configuration here

final ResourceConfig resourceConfig = new ResourceConfig()
        .packages("jersey.stackoverflow.standalone")
        .register(GsonMessageBodyHandler.class);
...
Client c = ClientBuilder.newClient();
c.register(GsonMessageBodyHandler.class);

Here is the resource class I used for the test

import com.google.gson.JsonObject;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import jersey.stackoverflow.standalone.provider.GsonMessageBodyHandler;

@Path("/gson")
public class GsonResource {

    private final Client client;
    private static final String BASE_URI = "http://localhost:8080/api/gson";

    public GsonResource() {
        client = ClientBuilder.newClient().register(GsonMessageBodyHandler.class);
    }

    @POST
    @Path("/proxy")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response proxyPost(JsonObject json) {
        Response response = client.target(BASE_URI)
                .path("main-resource").request().post(Entity.json(json));
        JsonObject fromMainResource = response.readEntity(JsonObject.class);
        return Response.created(null /* should be a created URI */)
                .entity(fromMainResource).build();
    }

    @POST
    @Path("/main-resource")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response mainResource(JsonObject json) {
        return Response.ok(json).build();
    }  
}

Here's the complete test, which requires this maven dependency

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.URI;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import static jersey.stackoverflow.standalone.Main.BASE_URI;
import jersey.stackoverflow.standalone.provider.GsonMessageBodyHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.Test;

public class GsonProviderTest {

    public static HttpServer startServer() {
        final ResourceConfig resourceConfig = new ResourceConfig()
                .packages("jersey.stackoverflow.standalone")
                .register(GsonMessageBodyHandler.class);
        return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), resourceConfig);
    }

    public static Client getClient() {
        Client c = ClientBuilder.newClient();
        c.register(GsonMessageBodyHandler.class);
        return c;
    }

    @Test
    public void testGetIt() {
        HttpServer server = startServer();
        Client c = getClient();

        c.register(GsonMessageBodyHandler.class);

        String quo = "{\"network\": {\"label\": \"new net 111\",\"cidr\": \"10.20.105.0/24\"}}";

        GsonBuilder builder = new GsonBuilder();
        Gson gson = builder.create();
        JsonParser json = new JsonParser();
        JsonObject jo = (JsonObject) json.parse(quo);

        WebTarget target = c.target("http://localhost:8080/api/gson/proxy");
        Response response = target.request().post(Entity.json(jo));
        String responseString = response.readEntity(String.class);
        System.out.println(responseString);
        response.close();

        c.close();
        server.stop();
    }
}

All the test does is send the JsonObject. Though there isn't any visible conversion to JSON, in any of my code, it is happening behind the scenes by the GsonMessageBodyHandler. If you look at the GsonResource class, you can see the methods don't do anything but send out the JsonObject. In the client test, I read the response as a String, and you can see the result the same as what sent out in the initial request.

Solution 2

There's a simple way to get the JsonObject in com.google.gson.JsonObject type using a post request.

I am assuming that all the dependencies for com.google.gson , jersey and jax-rs are already added.

On the server side you need to have code similar to below :

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

@Path("/api")
public class JersyAPI {


    private JsonParser parser= new JsonParser();

    @POST
    @Path("/pudding")
    @Consumes("application/json")
    public Response postTest(String requestBody){
        Response re = Response.status(200).build();
        try{
            JsonObject inputObjectJson = parser.parse(requestBody).getAsJsonObject();

The code above has a rest endpoint defined with path /api/pudding and it is accepting the Request Body as String. Once you receive the Json as string on server side, com.google.gson.JsonParser can be used to convert it into the com.google.gson.JsonObject directly and this can be used in your program.

To make a request on server side you post request should look like this :

POST /rest/api/pudding HTTP/1.1
Host: localhost:8082
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: c2b087d9-4830-c8a8-2a19-78273a73898c

{
  "id": 1312312,
  "name": "Test",
  "data": { 
            "test" : "data"
        },
}
Share:
18,719
rok
Author by

rok

Updated on June 24, 2022

Comments

  • rok
    rok almost 2 years

    I have some problems in my application, I send a POST request, but I cannot retrieve the JsonObject in my server, this is the code to send:

    String quo = "{\"network\": {\"label\": \"new net 111\",\"cidr\": \"10.20.105.0/24\"}}";
    GsonBuilder builder = new GsonBuilder();
    Gson gson = builder.create();
    JsonParser json = new JsonParser();
    JsonObject jo = (JsonObject)json.parse(quo);
    ClientConfig config = new ClientConfig();
    
    Client client = ClientBuilder.newClient(config);
    
    WebTarget target = client.target("http://localhost:7999/jersey/rest/network/"+tenant_id);
    
    Response oj = target.request().accept(MediaType.APPLICATION_JSON)
            .header("X-Auth-Token", token)
            .post(Entity.json(gson.toJson(jo)));
    

    Trying to retrieve with:

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{tenant_id}")
    public String createNetwork(@HeaderParam(value = "X-Auth-Token") String authToken, 
                            @PathParam(value = "tenant_id") String tenant_id, 
                            JsonObject network){
    
        Response response = client.target(NOVA_ENDPOINT+tenant_id)
                    .request(MediaType.APPLICATION_JSON)
                    .header("X-Auth-Token", authToken)
                    .post(Entity.json(gson.toJson(network)));
        System.out.println("Hello"); 
        String responseJson = response.readEntity(String.class);
    

    JsonObject network seems to be empty, in fact it doesn't execute the method ("Hello is not printed"), the error I get is "Invalid request body" (because the JsonObject is empty I think).. What's wrong with my code?

    Ok, I understood that the problem is related to Json handling such as I'm using Gson. This is my improved code (simplified version) following users suggestion, but I still have problems..

    Client Side:

    package openstack;
    
    import javax.ws.rs.client.Client;
    import javax.ws.rs.client.ClientBuilder;
    import javax.ws.rs.client.Entity;
    import javax.ws.rs.client.WebTarget;
    import javax.ws.rs.core.Response;
    import org.glassfish.grizzly.http.server.HttpServer;
    import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
    import org.glassfish.jersey.server.ResourceConfig;
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonParser;
    import java.net.URI;
    
    
    public class Post {
    
        public static HttpServer startServer() {
        final ResourceConfig resourceConfig = new ResourceConfig()
                .packages("openstack")
                .register(GsonMessageBodyHandler.class);
        return GrizzlyHttpServerFactory.createHttpServer(URI.create("http://localhost:7999/jersey/rest"), resourceConfig);
    }  
        public static void main(String[] args) {
    
        String quo = "{\"keypair\": {\"name\": \"MyKey\"}}";
        HttpServer server = startServer();
    
    
        Client client = ClientBuilder.newClient();
    
        client.register(GsonMessageBodyHandler.class);
    
        GsonBuilder builder = new GsonBuilder();
        Gson gson = builder.create();
        JsonParser json = new JsonParser();
        JsonObject jo = (JsonObject)json.parse(quo);
    
        WebTarget target = client.target("http://localhost:7999/jersey/rest/test/prova");
    
        System.out.println(jo);
        Response oj = target.request().post(Entity.json(jo));
    
        String responseString = oj.readEntity(String.class);
        System.out.println(responseString);
    
        }
    }
    

    Server Side:

    package openstack;
    
    import javax.ws.rs.Consumes;
    import javax.ws.rs.POST;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;
    import javax.ws.rs.client.Client;
    import javax.ws.rs.client.ClientBuilder;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.Response;
    
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonParser;
    
    @Path("/test")
    public class Test {
    
        GsonBuilder builder = new GsonBuilder();
        Gson gson = builder.create();
        Parliament parliament = new Parliament();
        JsonParser json = new JsonParser();
        private final Client client;
    
        public Test() {
            client = ClientBuilder.newClient().register(GsonMessageBodyHandler.class);
        }
    
        @POST
        @Consumes(MediaType.APPLICATION_JSON)
        @Produces(MediaType.APPLICATION_JSON)
        @Path("/prova")
        public Response mymethod(JsonObject keypairsob){
    
            return Response.ok(keypairsob).build();
    
        }
    }
    

    I created a GsonMessageBodyHandler.java in my package with the code suggested below by the user peeskillet. Added jersey-container-grizzly2-http.jar to my web-inf/lib (I don't know how to proper use Maven), but still doesn't work.. what am I missing?

  • rok
    rok over 9 years
    Yes, Json works because I have no problems with GET requests (I'm using Jersey 2.13). I think you are right, it fails somehow to turn the message body in the JsonObject, but why?
  • user384842
    user384842 over 9 years
    Is it possible it's calling one of your other @Path methods instead? Sometimes the resolution of paths can be a little counter-intuitive..
  • rok
    rok over 9 years
    Calling my other path produce the same
  • user384842
    user384842 over 9 years
    Do you have an @Consumes on the class that is inconsistent with json? Perhaps you could post up the whole class?
  • rok
    rok over 9 years
    it doesn't work with consumes too.. I think is something related to Json management, because using String instead works perfectly.. I'll post the entire class soon anyway..
  • rok
    rok over 9 years
    I still have some problems, this is what I did: created GsonMessageBodyHandler.java in my package copying your above code, then put in my code 'client = ClientBuilder.newClient().register(GsonMessageBodyHandler.cl‌​ass);' in both my client and server classes. The server method is executed (I put a debugging output there), but the json is empty, in fact in the client console I see {"type":"jsonObject"} insead of the one I send. Btw I'm going to edit my question adding the new code I have, to be more clear.