1

I'm building a RESTful web service. I've been locked in a situation where I'm not able to proceed. I've a DAO (a POJO) that has a JSONObject as a member variable. When I try to make a POST call from client (Postman or user-defined javascript) and try to debug, the value gathered in the getter of the JSONObject is empty ({}) whereas the other members of the class obtain their appropriate values. I've tried annotating the JSONObject and its getter with @XmlElement, @JsonProperty and so on.. Nothing worked.

The class looks like :

package org.somepackage

import javax.xml.bind.annotation.XmlRootElement;

    import org.codehaus.jackson.annotate.JsonAutoDetect;
    import org.codehaus.jackson.annotate.JsonProperty;
    import org.json.JSONObject;


    @XmlRootElement

    public class someClass {
        private String someID;
        private String someName;

        private JSONObject someJsonObject;


        public someClass () {

        }
        public someClass (String id, String name,
                JSONObject jsonObj) {
            someID=id;
            someName=name;
            someJsonObject=jsonObj;

        }
        public String getSomeID() {
            return someID;
        }
        public void setSomeID(String id) {
            this.SomeID= id;
        }
        public String getSomeName() {
            return someName;
        }
        public void setSomeName(String name) {
            this.someName= name;
        }

        public JSONObject getSomeJsonObject() {
            return someJsonObject;
        }
        public void setSomeJsonObject(JSONObject jsonObj) {
            this.someJsonObject= jsonObj;
        }
    }

I appreciate your help! Thanks.


EDIT

Example JSON

{ 
  "name": "ABCD", 
  "ID": "P63784433", 
  "theJSON":{
    "string":"foo",
    "number":5,
    "array":[1,2,3],
    "object":{
      "property":"value‌​",
      "subobj":{
        "arr":["foo","ha"],
        "numero":1
      }
    }
  }
}

DEPENDENCY web.xml dependency on Jackson

                        <dependency>
                                <groupId>org.codehaus.jackson</groupId>
                                <artifactId>jackson-mapper-asl</artifactId>
                                <version>1.9.2</version>
                        </dependency>

                        <dependency>
                                <groupId>org.codehaus.jackson</groupId>
                                <artifactId>jackson-core-asl</artifactId>
                                <version>1.9.2</version>
                        </dependency>

                         <dependency>
                                <groupId>org.codehaus.jackson</groupId>
                                <artifactId>jackson-jaxrs</artifactId>
                                <version>1.9.2</version>
                         </dependency>

                         <dependency>
                                <groupId>org.codehaus.jackson</groupId>
                                <artifactId>jackson-xc</artifactId>
                                <version>1.9.2</version>
                        </dependency>

                        <dependency>
                                <groupId>com.fasterxml.jackson.core</groupId>
                                <artifactId>jackson-core</artifactId>
                                <version>2.6.2</version>
                        </dependency>

RESOURCES AND PROVIDER REGISTER through web.xml

    <!-- Register JAX-RS Application -->
   <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>my.package.MyApplication</param-value>
    </init-param>

    <!-- Register resources and providers under my.package. -->
   <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>my.package</param-value>
    </init-param>

    <!-- Register custom provider  -->
    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>my.package.mapper.ObjectMapperProvider</param-value>
    </init-param>`

MyApplication.java

`@ApplicationPath("/")
public class MyApplication extends ResourceConfig {

public MyApplication() {
    // Register resources and providers using package-scanning.
    packages("my.package");

    register(ObjectMapperProvider.class);


}`
vardhinisuresh27
  • 371
  • 2
  • 6
  • 18
  • Create another nested POJO class to represent the data in the JSONObject, other wise for arbitrary data format, just use a `Map` instead of the `JSONObject` – Paul Samsotha Oct 05 '15 at 07:52
  • Thanks @peeskillet , I followed your advice, but with no luck. The object is still parsed as an empty `JSONObject` no matter what are its contents. Do you know if there's another way to go around this? I need to consume the information as `JSON`, can a `Map` be used for this? – vardhinisuresh27 Oct 06 '15 at 07:25
  • Share body and headers that you are passing with POST API – quintin Oct 07 '15 at 04:27
  • @peeskillet! The JSON structure looks something like this: `{ "name": "ABCD", "ID": "P63784433", "theJSON":{"string":"foo","number":5,"array":[1,2,3],"object":{"property":"value","subobj":{"arr":["foo","ha"],"numero":1}}} }` – vardhinisuresh27 Oct 07 '15 at 06:12
  • @kyle , the body is what I've posted above this comment. Also with regards to the headers, I send back a response of this form: `Response.ok(someString,MediaType.APPLICATION_JSON).status(201).header(CorsHeaderConstants.HEADER_AC_ALLOW_METHODS,"GET POST DELETE PUT").header(CorsHeaderConstants.HEADER_AC_ALLOW_CREDENTIALS,"true").header(CorsHeaderConstants.HEADER_AC_ALLOW_ORIGIN,"*").header(CorsHeaderConstants.HEADER_AC_ALLOW_ORIGIN,"*").allow("OPTIONS").build();` I want to mention, my POST API has been working for other types of POJO,I'm stuck with just the one that contains a JSONObject! – vardhinisuresh27 Oct 07 '15 at 06:17
  • Is this a set format for `"theJSON"` object? Or does it change? If it's set, why not just create some POJOs to map the properties? – Paul Samsotha Oct 07 '15 at 06:18
  • @peeskillet I have an already built external library that parses `"theJSON" ` object . – vardhinisuresh27 Oct 07 '15 at 06:34
  • What Jersey and Jackson version are you using? Please show your dependencies – Paul Samsotha Oct 07 '15 at 06:40
  • @peeskillet Jersey version- 2.16 Dependencies are: `org.codehaus.jackson jackson-mapper-asl 1.9.2 org.codehaus.jackson jackson-core-asl 1.9.2 org.codehaus.jackson jackson-jaxrs 1.9.2 org.codehaus.jackson jackson-xc 1.9.2 com.fasterxml.jackson.core jackson-core 2.6.2` – vardhinisuresh27 Oct 07 '15 at 07:00

1 Answers1

1

The problem is that Jackson doesn't know how to create the JSONObject (at least not without some help). Jackson mainly handle basic type and POJOs. If you want to be able to handle JSONObject (assuming this is the object from org.json), you can add the jackson-datatype-json-org for the Jackson support.

Below is a complete test. Here are the dependencies I used to test

<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20141113</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.16</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-json-org</artifactId>
  <version>2.3.2</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>2.16</version>
    <scope>test</scope>
</dependency>

Note: The Jackson version I am using for jackson-datatype-json-org is the same Jackson version used by jersey-media-json-jackson 2.16. If you are using a different version of this jersey jackson, you will need to make sure the version of Jackson it pulls in is the same version of jackson-datatype-json-org you are using. This way we are not mixing Jackson versions.

Here's the test using Jersey Test Framework

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.json.JSONObject;

import org.junit.Test;
import static junit.framework.Assert.*;

/**
 *
 * @author Paul Samsotha
 */
public class JsonOrgTest extends JerseyTest {

    public static class Model {
        public String firstName;
        public String lastName;
        public JSONObject other;
        // should br private with correct getters and setters
    }

    @Path("model")
    public static class ModelResource {

        @POST
        @Consumes(MediaType.APPLICATION_JSON)
        @Produces(MediaType.APPLICATION_JSON)
        public Response post(Model model) {
            return Response.ok(model).build();
        }
    }

    @Provider
    public static class ObjectMapperProvider implements ContextResolver<ObjectMapper> {

        private final ObjectMapper mapper;

        public ObjectMapperProvider() {
            mapper = new ObjectMapper();
            mapper.registerModule(new JsonOrgModule());
        }

        @Override
        public ObjectMapper getContext(Class<?> type) {
            return mapper;
        }
    }

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(ModelResource.class)
                .register(ObjectMapperProvider.class)
                .register(JacksonFeature.class);
    }

    @Override
    public void configureClient(ClientConfig config) {
        config.register(JacksonFeature.class);
        config.register(ObjectMapperProvider.class);
    }

    @Test
    public void should_return_org_json_data() {
        final String json 
                = "{\n"
                + "  \"firstName\": \"pee\",\n"
                + "  \"lastName\": \"skillet\",\n"
                + "  \"other\": {\n"
                + "    \"age\": 100,\n"
                + "    \"birthday\": \"yesterday\"\n"
                + "  }\n"
                + "}";
        Response response = target("model").request().post(Entity.json(json));
        if (response.getStatus() != 200) {
            System.out.println(response.getStatus() + ": " + response.readEntity(String.class));
            fail("should return data and 200");
        } else {
            Model model = response.readEntity(Model.class);
            JSONObject other = model.other;
            System.out.println(other.toString());
            assertEquals("pee", model.firstName);
            assertEquals("skillet", model.lastName);
            assertEquals(100, other.getInt("age"));
            assertEquals("yesterday", other.getString("birthday"));
        }
    }
}

What you should also do is get rid of all the Jackson dependencies you have in your comment above. You only need one dependency for Jackson JSON support.

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.16</version>
</dependency>

Also notice the ObjectMapperProvider in the test. You will need to this to register the JsonOrgModule with the ObjectMapper in order for Jackson to be able to handle JSONObject. This is important. If you don't have the ContextResolver, the above example will fail.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Thank u very much. As per your advice, I added the `ObjectMapperProvider` class for registering the `JsonOrgModule` with `ObjectMapper`. How do I link it with `someClass` ? – vardhinisuresh27 Oct 28 '15 at 03:24
  • @vardhinisuresh27 You don't need to link anything. As long as the `ObjectMapperProvider` is registered, it should work, as seen in my complete runnable/testable example – Paul Samsotha Oct 28 '15 at 03:29
  • Thank you. But I see from ur test code that the `configure()` function has registration of the `ObjectMapperProvider`. So how do I go about something like that in my case? – vardhinisuresh27 Oct 28 '15 at 03:32
  • I don't know, you have not shown your case. I don't know how you register all your resource classes and providers. Are you using package scanning? If so, the scanning should pick up the `@Provider` annotation and register it for you, as long as the class is in the packages or sub-package listed to be scanned. – Paul Samsotha Oct 28 '15 at 03:33
  • Also keep in mind that you should get rid of all the Jackson dependencies you have in your post. Just use the one I have in my answer – Paul Samsotha Oct 28 '15 at 03:36
  • I've posted the way I've registered resource classes and providers in `web.xml` above as part of my other snippets. Also, as you have told before, I removed all the unnecessary Jackson dependencies! Thanks for your responses. – vardhinisuresh27 Oct 28 '15 at 06:56
  • Thank u very much for your guidance! It works now. I forgot to register `JacksonFeature` along with the `ObjectMapperProvider` – vardhinisuresh27 Oct 28 '15 at 08:15
  • I'm wondering how to make this work outside of glassfish? I have a class extending Application with a list of classes and singletons. I'm adding the ObjectMapperProvider to my list of classes, but JSONObjects still only render as a blank string. – Alkanshel Nov 10 '17 at 06:25
  • @Amalgovinus You need to disable MOXy, which is the default provider. Or you can register JacskonFeature. This will also disabled MOXy implicitly – Paul Samsotha Nov 10 '17 at 06:43
  • JacksonFeature seems to be a glassfish thing? (org.glassfish.jersey.jackson.JacksonFeature) – Alkanshel Nov 13 '17 at 19:09
  • The whole " implements ContextResolver" totally breaks it for me, the JSONObject gets put out as a blank string rather than json. – Alkanshel Nov 14 '17 at 20:34
  • It seems like my class implementing ContextResolver never has its getContext() called. If I don't have the class providing my ObjectMapper implement ContextResolver, then I can GET json properly, but trying to POST or PUT it results in the backend receiving nothing. If I do implement ContextResolver, the value comes through as a blank string both ways. Pretty confusing. Is this ContextResolver stuff only needed for Jersey and not just plain Jackson / Swagger? – Alkanshel Nov 15 '17 at 23:53
  • @Amalgovinus As I said, in Glassfish, MOXy is the default provider. The ContextResolver for ObjectMapper isn't used for MOXy. You need to disabled MOXy if you want to use Jackson – Paul Samsotha Nov 15 '17 at 23:56
  • Sorry for the confusion, I'm not using glassfish / jersey, so I'm guessing MOXy isn't being used either unless I'm misunderstanding. – Alkanshel Nov 16 '17 at 00:05
  • @Amalgovinus Unfortunately this isn't something I can really figure out for you with this little information. I'm not sure why the resolver is not called. If you are using the Jackson "provider", then it _will_ call the resolver. You need to make sure you're using the provider and that it is registered and the one being used. – Paul Samsotha Nov 16 '17 at 00:12
  • You're right that it wasn't, thanks. I got it working without ContextResolver so that must just be jersey thing. – Alkanshel Nov 16 '17 at 02:53
  • You remove dependencies when you are testing? So, for running and for testing you use different pom? – Gangnus Jun 06 '18 at 10:24