JSON Parsing Using PeopleTools JsonParser
By Chris Malek | Tue, Jul 17, 2018
As I wrote in JSON Parsing using PeopleCode Classes Starting in PeopleTools 8.55.11 , there is now a built in way of generating and parsing JSON in PeopleCode. This is great and removes external dependencies like the one I released: JSONtoXML Application Class - An Alternative Method to Parse JSON in Peoplecode.
The other option was to use the PeopleTools Document
technology. However, I have no kind words for the document
technology. It is limited, frustrating, buggy and cannot parse dynamic content. APIs often return dynamic nested JSON objects and you need to be able to programmatically inspect and respond to the data returned. In my experience the document
object could not do that. I wrote about some of those in JSON Parsing Limitations in 8.53
In this article I want to show an example using the built in JsonParser
class to parse a sample JSON document. As of the writing of this article the API is NOT documented because…well….I have no idea!
Animal JSON Document
In the first few examples, we will start with this JSON that I randomly pulled it from this LearnWebCode Github repository.
Some items to notice:
- It has a “top level array”.
- This pattern is often returned in API’s where you are searching for data and you get back an array of matches.
- In each array element, there are nested
food
objects that are JSON arrays.
[
{
"name": "Meowsy",
"species" : "cat",
"foods": {
"likes": ["tuna", "catnip"],
"dislikes": ["ham", "zucchini"]
}
},
{
"name": "Barky",
"species" : "dog",
"foods": {
"likes": ["bones", "carrots"],
"dislikes": ["tuna"]
}
},
{
"name": "Purrpaws",
"species" : "cat",
"foods": {
"likes": ["mice"],
"dislikes": ["cookies"]
}
}
]
Find Favorite Foods by Name
In this example, we parse the JSON and loop through each animal and get their name and their favorite foods.
We are also using the DataDumper package I released which allows you to quickly write debug files.
We are storing our JSON to be parsed in the message catalog 22000, 1. Of course in a real world example, you would be pulling this from a web service or perhaps a file.
Here is the code that will parse out the JSON file.
- We loop over each root array object and populate
&AnimalName
with the animal name. - We also concatenate all the favorite foods for each animal into a comma separated variable called
&favoriteFoods
import CHG_DEBUG:dataDumper;
Local CHG_DEBUG:dataDumper &z = create CHG_DEBUG:dataDumper("json_unit.txt");
Local JsonParser &parser = CreateJsonParser();
Local string &jsonString = MsgGetExplainText(22000, 1, "");
Local boolean &bParseResult = &parser.Parse(&jsonString);
If &bParseResult = True Then
/* Parse Success - Valid JSON */
&z.dump("Parse Was Successful");
Local JsonObject &jObj = &parser.GetRootObject();
Local JsonArray &rootArray;
&rootArray = &jObj.GetJsonArray("");
If &rootArray.Length() < 0 Then
&z.dump("root array is null");
Else
Local integer &i, &j;
Local JsonObject &rootObject;
Local string &AnimalName, &favoriteFoods;
Local JsonObject &foodObject;
Local JsonArray &jsonArrayFavoriteFoods;
For &i = 1 To &rootArray.Length();
&z.indentLevel = 0;
&z.dump("&i : " | &i);
&rootObject = &rootArray.GetJsonObject(&i);
&AnimalName = &rootObject.GetAsString("name");
&z.indentLevel = 2;
&z.dump("&AnimalName: " | &AnimalName);
&foodObject = &rootObject.GetJsonObject("foods");
&jsonArrayFavoriteFoods = &foodObject.GetJsonArray("likes");
If All(&jsonArrayFavoriteFoods) Then
For &j = 1 To &jsonArrayFavoriteFoods.Length()
If &j = 1 Then
&favoriteFoods = &jsonArrayFavoriteFoods.GetAsString(&j);
Else
&favoriteFoods = &favoriteFoods | ", " | &jsonArrayFavoriteFoods.GetAsString(&j);
End-If;
End-For;
End-If;
&z.indentLevel = 3;
&z.dump("&favoriteFoods: " | &favoriteFoods);
End-For;
End-If;
Else
/* Parse Failure */
&z.dump("Parse failed");
End-If;
The debug files that is generated is:
Parse Was Successful
&i : 1
&AnimalName: Meowsy
&favoriteFoods: tuna, catnip
&i : 2
&AnimalName: Barky
&favoriteFoods: bones, carrots
&i : 3
&AnimalName: Purrpaws
&favoriteFoods: mice
Extracting Booleans
Let’s look at a different example of how are can extract out JSON boolean values into PeopleSoft boolean values.
We are going to modify our JSON slightly and add a boolean property called canBark
.
[
{
"name": "Meowsy",
"species" : "cat",
"canBark": false,
"foods": {
"likes": ["tuna", "catnip"],
"dislikes": ["ham", "zucchini"]
}
},
{
"name": "Barky",
"species" : "dog",
"canBark": true,
"foods": {
"likes": ["bones", "carrots"],
"dislikes": ["tuna"]
}
},
{
"name": "Purrpaws",
"species" : "cat",
"canBark": false,
"foods": {
"likes": ["mice"],
"dislikes": ["cookies"]
}
}
]
Now the goal of our code is to find the names of animals that “can bark”.
import CHG_DEBUG:dataDumper;
Local CHG_DEBUG:dataDumper &z = create CHG_DEBUG:dataDumper("json_unit.txt");
Local JsonParser &parser = CreateJsonParser();
Local string &jsonString = MsgGetExplainText(22000, 1, "");
Local boolean &bParseResult = &parser.Parse(&jsonString);
If &bParseResult = True Then
/* Parse Success - Valid JSON */
&z.dump("Parse Was Successful");
Local JsonObject &jObj = &parser.GetRootObject();
Local JsonArray &rootArray;
Local boolean &bCanBark;
&rootArray = &jObj.GetJsonArray("");
If &rootArray.Length() < 0 Then
&z.dump("root array is null");
Else
Local integer &i;
Local JsonObject &rootObject;
Local string &AnimalName;
For &i = 1 To &rootArray.Length();
&z.indentLevel = 0;
&z.dump("&i : " | &i);
&rootObject = &rootArray.GetJsonObject(&i);
&bCanBark = &rootObject.GetBooleanProperty("canBark");
If &bCanBark Then
&AnimalName = &rootObject.GetAsString("name");
&z.indentLevel = 2;
&z.dump("&AnimalName: " | &AnimalName | " - It can bark");
End-If;
End-For;
End-If;
Else
/* Parse Failure */
&z.dump("Parse failed");
End-If;
Debug output file
Parse Was Successful
&i : 1
&i : 2
&AnimalName: Barky - It can bark
&i : 3
Passing Invalid JSON
If your program gets some invalid JSON, it seems that the Parse
method on the JsonParser
class will throw
an error so you really need to add a try-catch block around any parsing code.
Here is invalid JSON document that is missing a comma after the “age” property.
{
"name": "John",
"age": 30
"car": "Tesla"
}
The updated PeopleCode to respond to an invalid JSON document is:
import CHG_DEBUG:dataDumper;
Local CHG_DEBUG:dataDumper &z = create CHG_DEBUG:dataDumper("json_unit.txt");
Local JsonParser &parser = CreateJsonParser();
Local string &jsonString = MsgGetExplainText(22000, 1, "");
try
If &parser.Parse(&jsonString) Then;
/* Parse Success - Valid JSON */
&z.dump("Parse Was Successful");
Else
/* Parse Failure */
&z.dump("Parse failed");
End-If;
catch Exception &e
&z.dump("****There was an Error ***");
&z.dump(&e.ToString());
end-try;
The dump file would have contained the following output which was generated in the catch
.
****There was an Error ***
Invalid token [ 8 ] at position [ 45 ], Expected tokens are: [ , ] (262,2117) CMALEK_JSON.MAIN.GBL.default.1900-01-01.Step01.OnExecute PCPC:307 Statement:5
Parsing Dynamic JSON
Let’s try to dynamically parse a JSON that we may not know what we get back.
This example is by no means complete. The methods are completely undocumented so I have been trying to figure them out. Here we will write some code to try to dynamically extract the values. It seems we can only pull out the string values. What I can’t figure out in this example is how to look at each property and determine if it is a boolean
, string
, etc and then parse the value into a native PeopleCode data type. What I am looking for is something like “GetPropertyType” that I can do an evaluate
on and take different actions based on the JSON value type.
{
"first": "firstValue",
"second": "second value",
"third": [
"foo",
"bar",
"baz"
],
"forth": true,
"fifth": 6.2,
"sixth": null,
"seventh": {
"foo": "bar"
}
}
import CHG_DEBUG:dataDumper;
Local CHG_DEBUG:dataDumper &z = create CHG_DEBUG:dataDumper("json_unit.txt");
Local JsonParser &parser = CreateJsonParser();
Local string &jsonString = MsgGetExplainText(22000, 1, "");
try
If &parser.Parse(&jsonString) Then;
/* Parse Success - Valid JSON */
&z.dump("Parse Was Successful");
Local JsonObject &jObj = &parser.GetRootObject();
Local integer &i;
Local JsonObject &jObjCurrent;
Local string &propName, &propValue;
For &i = 1 To &jObj.ChildCount
&z.whiteSpace(2);
&z.dump("&i = " | &i);
&propName = &jObj.GetPropertyNameAt(&i);
&z.dump("&propName : " | &propName);
If &jObj.IsJsonArray(&propName) Then
&z.dump("This is a json array ");
Else
If &jObj.IsJsonObject(&propName) Then
&z.dump("This is a json object ");
Else
&propValue = &jObj.GetAsString(&propName);
&z.dump("&propValue : " | &propValue);
End-If;
End-If;
End-For;
Else
/* Parse Failure */
&z.dump("Parse failed");
End-If;
catch Exception &e
&z.dump("****There was an Error ***");
&z.dump(&e.ToString());
end-try;
The output file would look something like this:
Parse Was Successful
&i = 1
&propName : first
&propValue : firstValue
&i = 2
&propName : second
&propValue : second value
&i = 3
&propName : third
This is a json array
&i = 4
&propName : forth
&propValue : true
&i = 5
&propName : fifth
&propValue : 6.200000000000000
&i = 6
&propName : sixth
&propValue : null
&i = 7
&propName : seventh
This is a json object
Summary
As you can see we are getting better support for JSON parsing. I would like to see some more documentation on this. This document will likely grow over time with examples.
Article Categories
Chris Malek
Chris Malek is a PeopleTools® Technical Consultant with two decades of experience working on PeopleSoft enterprise software projects. He is available for consulting engagements.
About Chris Work with ChrisPeopleSoft Simple Web Services (SWS)
Introducing a small but powerful PeopleSoft bolt-on that makes web services very easy. If you have a SQL statement, you can turn that into a web service in PeopleSoft in a few minutes.
Integration Broker - The Missing Manual
I am in the process of writing a book called "Integration Broker - The Missing Manual" that you can read online.