1

I'm attempted to pull parameters from JSON in a POST request. This seems like a very basic process and I've read through numerous posts about this but I'm missing something here as I'm getting an object back but the fields within that object are null. In my POST I have the following JSON...

{
  "client": "1",
  "forTopic": "topic"
}

And here is my POST method inside my servlet...

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
{           
    String requestBody = RESTUtil.getRequestBody (request);
    log.debug (requestBody);

    try
    {
        JAXBContext context = JAXBContext.newInstance (ClientAndTopicParameters.class);

        Unmarshaller unmarshal = context.createUnmarshaller ();

        unmarshal.setProperty (UnmarshallerProperties.MEDIA_TYPE, "application/json");
        unmarshal.setProperty (UnmarshallerProperties.JSON_INCLUDE_ROOT, true);

        ClientAndTopicParameters params = (ClientAndTopicParameters) unmarshal.unmarshal (new StreamSource (new StringReader (requestBody)), ClientAndTopicParameters.class).getValue ();

        log.debug ("params = " + params);
        log.debug ("client = " + params.client);
        log.debug ("forTopic = " + params.forTopic);
    }
    catch (JAXBException e)
    {
        log.error ("Unable to get Client and Topic parameters from POST.", e);
    }
}

Finally, here is my ClientAndTopicParameters class...

@XmlRootElement
public class ClientAndTopicParameters
{
    @XmlElement public String                       client;
    @XmlElement public String                       forTopic;
}

The resulting output is the following...

2018 Aug 24 17:44:55,806 DEBUG [MyServlet                        ] params = mypackage.ClientAndTopicParameters@2995a298
2018 Aug 24 17:44:55,806 DEBUG [MyServlet                        ] client = null
2018 Aug 24 17:44:55,806 DEBUG [MyServlet                        ] forTopic = null

As you can see this pretty basic stuff. I'm assuming I'm missing something small that I'm just not seeing. Welcome any thoughts and insights. For reference I'm using JAXB v2.3.0

Etep
  • 533
  • 7
  • 20
  • JAXB is for marshalling/unmarshalling XML files, not for reading JSON content. You should use Jackson, Gson or something else to read your pojo. [Here](http://www.jsonschema2pojo.org/) you can generate your pojos after checking in the right technology you would like to use to parse the request body, with this generated POJOs your JSONs will be definitely be readable by Jackson/Gson. – m4gic Aug 27 '18 at 12:37
  • I'm not sure this is correct m4gic. I'm using JAXB to marshal and unmarshal JSON for my other objects. Hence the unmarshal.setProperty (UnmarshallerProperties.MEDIA_TYPE, "application/json"); The only difference is that I'm not specifying the XMLRoolElement in this case. Normally I have @XmlRoolElement (name="MyObject"). But in this case the JSON here is the object rather than the object being specified in the JSON. For example... {"MyObject":{JSON DATA...}}. I instead have {JSON DATA...}. So I'm guess I'm looking for how to specify that this JSON is for my object ClientAndTopicParameters. – Etep Aug 28 '18 at 03:51
  • 1
    Hi, yes, I have not known that [jaxb can handle this](https://stackoverflow.com/questions/38789307/how-to-serialize-jaxb-object-to-json-with-jaxb-reference-implementation). So it seems one can use JAXB to marshall/unmarshall JSON IF you use some additional technology, like Jackson or Moxy... These additional technologies can handle JSON on their own, for me it would be clearer to use Jackson alone (as a native JSON parser/serializer) than use JAXB in this way. I think Jackson is much better in this. Using XML annotations to annotate POJOs for JSON processing for me is confusing. – m4gic Aug 28 '18 at 09:12
  • You should add to the question what jaxb version you are using, that would help to reproduce your issue. – m4gic Aug 28 '18 at 09:21
  • Thank you @m4gic. Good point, I'll add the version. Also, I'm chose JAXB so that it handles the JPA entities I'm using. I'll admit though that I have not researched Jackson. I will definitely have a look. – Etep Aug 28 '18 at 18:48

1 Answers1

1

The solution is to serialize your desired object and play with the includeRoot flag. If you set it to false, you will get the desired output.

import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.transform.stream.StreamSource;

import org.eclipse.persistence.jaxb.UnmarshallerProperties;

@XmlRootElement
public class ClientAndTopicParameters
{

    @XmlElement public String                       client;
    @XmlElement public String                       forTopic;


    public static void main(String[] args) {

        try
        {
            boolean includeRoot = true;

            JAXBContext context = JAXBContext.newInstance     (ClientAndTopicParameters.class);
            Unmarshaller unmarshal = context.createUnmarshaller ();
            unmarshal.setProperty (UnmarshallerProperties.MEDIA_TYPE,     "application/json");
            unmarshal.setProperty (UnmarshallerProperties.JSON_INCLUDE_ROOT,     includeRoot);

            parseAndPrint(unmarshal, "{ \"client\": \"1\",  \"forTopic\": \"topic\"}");
            StringWriter sw = marshallDesiredObject(context, includeRoot);
            parseAndPrint(unmarshal, sw.toString());
        }
        catch (JAXBException e)
        {
            System.out.println("Unable to get Client and Topic parameters from POST.");
            e.printStackTrace();
        }
    }

    private static StringWriter marshallDesiredObject(JAXBContext context, boolean includeRoot)
        throws JAXBException, PropertyException {
        Marshaller marshal = context.createMarshaller ();

        marshal.setProperty (UnmarshallerProperties.MEDIA_TYPE, "application/json");
        marshal.setProperty (UnmarshallerProperties.JSON_INCLUDE_ROOT, includeRoot);

        ClientAndTopicParameters cp = new ClientAndTopicParameters();
        cp.client = "1";
        cp.forTopic = "topic";

        StringWriter sw = new StringWriter();
        marshal.marshal(cp, sw);
        return sw;
    }

    private static void parseAndPrint(Unmarshaller unmarshal, String requestBody)
            throws JAXBException {
        System.out.println("requestBody to parse: " + requestBody);
        ClientAndTopicParameters params = unmarshal.unmarshal(new StreamSource (new     StringReader (requestBody )), ClientAndTopicParameters.class).getValue ();
        System.out.println("params = " + params);
        System.out.println("client = " + params.client);
        System.out.println("forTopic = " + params.forTopic);
    }
}

I used these dependencies:

        <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>org.eclipse.persistence.moxy</artifactId>
        <version>2.5.2</version>
    </dependency>
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.2.11</version>
    </dependency>

In your code, this is the only thing you have to change:

unmarshal.setProperty (UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
m4gic
  • 1,461
  • 12
  • 19
  • You have to set the eclipselink's [jaxb context factory](https://stackoverflow.com/questions/20962053/exception-in-thread-main-javax-xml-bind-propertyexception-name-eclipselink-m) too... – m4gic Aug 28 '18 at 09:45
  • Thank you again @m4gic. This was the solution. I knew it had to be something simple. I had actually tried just commenting out that line where I set JSON_INCLUDE_ROOT to true. Figuring it would default to false. Appears this needs to be explicitly set. Simple change and it all works. Thank you again for all your help! – Etep Aug 28 '18 at 18:54