Wednesday, September 17, 2008

Parsing JSON with PeopleCode

A lot of web services return results in JSON format rather than XML. Is it possible to parse JSON in PeopleCode? Can you consume JSON web services uisng PeopleCode? Absolutely. My first attempt at parsing JSON in PeopleCode used eval and the Rhino JavaScript scripting engine as documented in my post Scripting PeopleSoft. Because the Bean Scripting Framework's BSFEngine.eval method returns a java.lang.Object, I was left in a state of painful Java Reflection (executing each call using Java Reflection). Looking over the json.org website, I took note of the collection of Java JSON parsers. After choosing the org.json parser. I again found myself having to deal with the pain of Java Reflection (and, most definitely, I was left wishing PeopleCode had a JavaCast function). Rather than deal with the Java reflection required to create an instance of a JSONObject or JSONArray, I chose an easier route: write a helper class to construct JSON objects. Here is the source:

package yourcompany.json;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class ParseHelper {
private ParseHelper() {
}

public static JSONObject objectFromString(String json) throws JSONException {
return new JSONObject(json);
}

public static JSONArray arrayFromString(String json) throws JSONException {
return new JSONArray(json);
}
}

If my JSON looks like

{
"EMPLID": "E1234",
"NAME": "Marion,Jim",
"DIRECTS": [
{
"EMPLID": "E5678",
"NAME": "Doe,John"
},
{
"EMPLID": "E2468",
"NAME": "Doe,Jane"
}
]
}

Then I can enumerate the directs array using PeopleCode like:

Local string &json_data = "my JSON string...";

REM ** use static helper class to avoid ugly Java reflection;
Local JavaObject &json = GetJavaClass("yourcompany.json.ParseHelper").objectFromString(&json_data);
Local JavaObject &directsArr = &json.getJSONArray("DIRECTS");
Local number &length = &directsArr.length();
Local number &directsIdx = 0;

For &directsIdx = 0 To &length - 1
Local JavaObject &direct = &directsArr.getJSONObject(&directsIdx);
&logger.debug("DIRECTS [" | &directsIdx | "] " | &direct.get("NAME").toString());
End-For;

14 comments:

Ciphersbak said...

Hi Jim,

Gr8 :) blog on JSON. I was wanting to see something like that for some time...Coz people were really talking about JSON rather than XML...

Thank you for your inputs.

All the best

Thank you

Abhay said...

Awesome post Jim!

I was wondering if there is a way to read the JSON that is being returned by a third-party webservice.
In the above example, you are building the string. I was able to use this test code to test that I am able to read a JSON that I created in Peoplecode.

But what would be the best way to read the JSON (using Peoplecode) that the third-party is returning when we make a call to them.
We are on Tools 8.52

Thanks much!!

Jim Marion said...

@Abhay, it is the same. Are you using %IntBroker.ConnectorRequestUrl? It returns a string. You would just call objectFromString passing in the result of ConnectorRequestUrl.

My PeopleTools Tips and Techniques book has examples of using the JSON.simple parser instead of the org.json parser. I found JSON.simple to be a little easier to use from PeopleCode.

The Documents module in PeopleTools 8.53 supports JSON parsing, so in 8.53 you do not need a special library to make this happen.

Abhay said...

Thank you once again, @Jim!! I was not using ConnectorRequestURL, but now am. :)
Works like a charm! Will definitely check out your book..

Frank Staheli said...

Jim:

This has been extremely helpful. I got one of our Java guys to create a class very similar to the code example that you showed. I put his jar file (as well as json-jena-1.0.jar that he referred to in his code) in /opt/psoft/ptools/usr/classes, and it's working great for calling a web service and parsing out the records that I want.

One thing that took me a while to figure out--in one case I needed to go multiple levels into the JSON tree. So, using this snippet of JSON:

"task": {
"id": 5,
"type": "TASK",
"completionUrl": null,
"expectedCompletion": "2013-11-01T00:00:00-0600",
"studentReadFlag": false,
"createdDateTime": "2013-10-29T12:53:48-0600",
"creator": {
"personId": "328263412",
"name": "Goodman, Tayler"
},
"student": {
"personId": "112210202",
"name": "Ithaca, Johhny Appleseed"

in order to get the student name, I had to declare a JavaObject for both the task and the student, before I could get the student's name, as in the following:

Local JavaObject &task = &result.getJSONObject("task"); /* get the task branch */
Local JavaObject &student = &task.getJSONObject("student");
&strStudentName = &student.get("name").toString();

Thanks again!

Jim Marion said...

@Frank, thank you for sharing.

Srini said...

Some of the custom Java methods seems to be missing . getJSONArray for example. Further, I also found a tutorial that talks about customizing JSON.simple, may be useful for the community.
Read / Parse JSON in PeopleCode, with Changes to JSON.Simple

Mathumitha said...

Hi Jim,

We are using tools 8.53 and trying to parse the json returned from the google search api
http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=
I am using document to handle the json response but it runs to an infinite loop. What may be missing? is it a problem with document structure?

Thank you

Jim Marion said...

@Mathumitha, first, congratulations on creating a Document structure that matches someone else's JSON. I assume you are iterating over your collections and it is in the iterating that you experience the infinite loop?

phillypatel said...

Jim, this is amazing help! Thank you so much for your contribution to the PeopleSoft space. My only suggest is do you have a link that can help people with the java setup that is required along with this development? I was lucky like @Frank to have someone help me with that aspect but not sure everyone has that benefit.

Unknown said...

Hi Jim,
You are so helpful! I'm wondering if you have tested with peopletools 8.59 now.
I am not sure how to de-serialize the JSON data I have in a REST json Response.
Thanks!

Jim Marion said...

As of 8.57, this approach still works, but isn't necessary. We now have a built-in PeopleCode JsonParser. I recommend using the native PeopleCode version going forward. It is much faster.

Parth said...

We are on 8.58 and planning to consume JSON string using API. Can someone please help me how to consume JSON using API. This will be invoked through Single -Signon PeopleCode.

Jim Marion said...

@Parth, the modern approach would be to use:

Local JsonParser &p = CreateJsonParser();
Local JsonObject &j;

&p.parse(&jsonStr);

&j = &p.GetJsonObject();

This is assuming your JSON is in a string. If your JSON is in a Document-based message, then the code would be different.