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;

9 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?