Consuming JSON object in Jersey service
Asked Answered
U

7

32

I've been Googling my butt off trying to find out how to do this: I have a Jersey REST service. The request that invokes the REST service contains a JSON object. My question is, from the Jersey POST method implementation, how can I get access to the JSON that is in the body of the HTTP request?

Any tips, tricks, pointers to sample code would be greatly appreciated.

Thanks...

--Steve

Untie answered 2/11, 2009 at 17:8 Comment(2)
am facing the similiar issue. is there any solution to use low-level access to JSONObject?Dibri
#29918016Dugout
A
12

I'm not sure how you would get at the JSON string itself, but you can certainly get at the data it contains as follows:

Define a JAXB annotated Java class (C) that has the same structure as the JSON object that is being passed on the request.

e.g. for a JSON message:

{
  "A": "a value",
  "B": "another value"
}

Use something like:

@XmlAccessorType(XmlAccessType.FIELD)
public class C
{
  public String A;
  public String B;
}

Then, you can define a method in your resource class with a parameter of type C. When Jersey invokes your method, the JAXB object will be created based on the POSTed JSON object.

@Path("/resource")
public class MyResource
{
  @POST
  public put(C c)
  {
     doSomething(c.A);
     doSomethingElse(c.B);
  }
}
Absorbed answered 2/11, 2009 at 17:48 Comment(3)
Unfortunately, the JSON string is basically being used more as a dictionary than as a real POJO. I'd rather not have to create a new POJO for the JSON objects.Untie
Have you looked at using a MessageBodyReader (jackson.codehaus.org/javadoc/jax-rs/1.0/javax/ws/rs/ext/…). Not tried it myself, but you might be able to get in at that level and convert the JSON stream to a map etc.Absorbed
Actually it turns out that its much simpler than I thought. I can get the body of the HTTP request as a string argument, and then process it using any JSON library (currently using Google GSON) to convert the request data into local objects.Untie
I
16

As already suggested, changing the @Consumes Content-Type to text/plain will work, but it doesn't seem right from an REST API point of view.

Imagine your customer having to POST JSON to your API but needing to specify the Content-Type header as text/plain. It's not clean in my opinion. In simple terms, if your API accepts JSON then the request header should specify Content-Type: application/json.

In order to accept JSON but serialize it into a String object rather than a POJO you can implement a custom MessageBodyReader. Doing it this way is just as easy, and you won't have to compromise on your API spec.

It's worth reading the docs for MessageBodyReader so you know exactly how it works. This is how I did it:

Step 1. Implement a custom MessageBodyReader

@Provider
@Consumes("application/json")
public class CustomJsonReader<T> implements MessageBodyReader<T> {
  @Override
  public boolean isReadable(Class<?> type, Type genericType,
      Annotation[] annotations,MediaType mediaType) {
    return true;
  }

  @Override
  public T readFrom(Class<T> type, Type genericType, Annotation[] annotations,
      MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
      InputStream entityStream) throws IOException, WebApplicationException {

    /* Copy the input stream to String. Do this however you like.
     * Here I use Commons IOUtils.
     */
    StringWriter writer = new StringWriter();
    IOUtils.copy(entityStream, writer, "UTF-8");
    String json = writer.toString();

    /* if the input stream is expected to be deserialized into a String,
     * then just cast it
     */
    if (String.class == genericType)
      return type.cast(json);

    /* Otherwise, deserialize the JSON into a POJO type.
     * You can use whatever JSON library you want, here's
     * a simply example using GSON.
     */
    return new Gson().fromJson(json, genericType);
  }
}

The basic concept above is to check if the input stream is expected to be converted to a String (specified by Type genericType). If so, then simply cast the JSON into the specified type (which will be a String). If the expected type is some sort of POJO, then use a JSON library (e.g. Jackson or GSON) to deserialize it to a POJO.

Step 2. Bind your MessageBodyReader

This depends on what framework you're using. I find that Guice and Jersey work well together. Here's how I bind my MessageBodyReader in Guice:

In my JerseyServletModule I bind the reader like so --

bind(CustomJsonReader.class).in(Scopes.SINGLETON);

The above CustomJsonReader will deserialize JSON payloads into POJOs as well as, if you simply want the raw JSON, String objects.

The benefit of doing it this way is that it will accept Content-Type: application/json. In other words, your request handler can be set to consume JSON, which seems proper:

@POST
@Path("/stuff")
@Consumes("application/json") 
public void doStuff(String json) {
  /* do stuff with the json string */
  return;
}
Icaria answered 7/2, 2013 at 12:23 Comment(0)
D
15

Jersey supports low-level access to the parsed JSONObject using the Jettison types JSONObject and JSONArray.

<dependency>
    <groupId>org.codehaus.jettison</groupId>
    <artifactId>jettison</artifactId>
    <version>1.3.8</version>
</dependency>

For example:

{
  "A": "a value",
  "B": "another value"
}


@POST
@Path("/")
@Consumes(MediaType.APPLICATION_JSON) 
public void doStuff(JSONObject json) {
  /* extract data values using DOM-like API */
  String a = json.optString("A");
  Strong b = json.optString("B");
  return;
}

See the Jersey documentation for more examples.

Dogear answered 26/4, 2013 at 14:40 Comment(2)
This does not work for me. I get... Unrecognized field "A" (class org.json.JSONObject), not marked as ignorable ...and if I add the following... mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); ...Then the object comes through empty {}Cambrai
this is not work. i am getting 415 error. Is there any solution and what dependency to be added for low level json access?Dibri
A
12

I'm not sure how you would get at the JSON string itself, but you can certainly get at the data it contains as follows:

Define a JAXB annotated Java class (C) that has the same structure as the JSON object that is being passed on the request.

e.g. for a JSON message:

{
  "A": "a value",
  "B": "another value"
}

Use something like:

@XmlAccessorType(XmlAccessType.FIELD)
public class C
{
  public String A;
  public String B;
}

Then, you can define a method in your resource class with a parameter of type C. When Jersey invokes your method, the JAXB object will be created based on the POSTed JSON object.

@Path("/resource")
public class MyResource
{
  @POST
  public put(C c)
  {
     doSomething(c.A);
     doSomethingElse(c.B);
  }
}
Absorbed answered 2/11, 2009 at 17:48 Comment(3)
Unfortunately, the JSON string is basically being used more as a dictionary than as a real POJO. I'd rather not have to create a new POJO for the JSON objects.Untie
Have you looked at using a MessageBodyReader (jackson.codehaus.org/javadoc/jax-rs/1.0/javax/ws/rs/ext/…). Not tried it myself, but you might be able to get in at that level and convert the JSON stream to a map etc.Absorbed
Actually it turns out that its much simpler than I thought. I can get the body of the HTTP request as a string argument, and then process it using any JSON library (currently using Google GSON) to convert the request data into local objects.Untie
Q
7

This gives you access to the raw post.

@POST
@Path("/")
@Consumes("text/plain") 
@Produces(MediaType.APPLICATION_JSON)
public String processRequset(String pData) {
    // do some stuff, 
    return someJson;
}
Quince answered 12/6, 2012 at 13:20 Comment(1)
This was a slick, easy way to pass JSON from my client and not have the server/client need to know about the objects that I am passing. I just used GSON to serialize it out and attach it to the post. Worked great! Thanks.Repugn
G
0

Submit/POST the form/HTTP.POST with a parameter with the JSON as the value.

@QueryParam jsonString

public desolveJson(jsonString)

Goldenseal answered 2/11, 2009 at 20:23 Comment(1)
I thought about doing that. It's just not quite the way I'd like to go. There are a few toolkits out there that drop the complete JSON into the actual body of the HTTP request, and I'd like to follow that pattern if possible.Untie
S
0

Some of the answers say a service function must use consumes=text/plain but my Jersey version is fine with application/json type. Jackson and Jersey version is jackson-core=2.6.1, jersey-common=2.21.0.

@POST
@Path("/{name}/update/{code}")
@Consumes({ "application/json;charset=UTF-8" })
@Produces({ "application/json;charset=UTF-8" })
public Response doUpdate(@Context HttpServletRequest req, @PathParam("name") String name, 
      @PathParam("code") String code, String reqBody) {
  System.out.println(reqBody);

  StreamingOutput stream = new StreamingOutput() {
    @Override public void write(OutputStream os) throws IOException, WebApplicationException {
      ..my fanzy custom json stream writer..
    }
  };

  CacheControl cc = new CacheControl();
  cc.setNoCache(true);
  return Response.ok().type("application/json;charset=UTF-8")
    .cacheControl(cc).entity(stream).build();
}

Client submits application/json request with a json request body. Servlet code may parse string to JSON object or save as-is to a database.

Sundowner answered 5/2, 2020 at 14:59 Comment(0)
A
0

SIMPLE SOLUTION:

If you just have a simple JSON object coming to the server and you DON'T want to create a new POJO (java class) then just do this.

The JSON I am sending to the server

{
    "studentId" : 1
}

The server code:

    //just to show you the full name of JsonObject class
    import javax.json.JsonObject; 

    @Path("/")
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response deleteStudent(JsonObject json) {
        //Get studentId from body <-------- The relevant part 
        int studentId = json.getInt("studentId");
        
        //Return something if necessery
        return Response.ok().build();
    }
Allotropy answered 3/11, 2021 at 8:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.