Navigation Bar

Friday, March 21, 2025

Parsing a JSON object with Oracle PLSQL

 Reading a simple JSON with PLSQL using TYPES json_array_t and json_object_t

DECLARE
    p_in VARCHAR2(4000) := '[{"student_roll_no":10004},{"student_roll_no":10005}]';
    l_ja json_array_t;
    l_jo json_object_t;
 
BEGIN
    
    l_ja := json_array_t.parse(p_in);
    for i in 0..l_ja.get_size - 1 loop
      l_jo := json_object_t(l_ja.get(i));
      dbms_output.put_line('l_jo ' || l_jo.to_string);
    end loop;

END;
/
The output is as below
l_jo {"student_roll_no":10004}
l_jo {"student_roll_no":10005}
With this script I am able to get each element key-value pair. Our objective is to get each item in the list of keys and each item in the list of values, if I want to do any further data processing or data manipulation. 

For this I try using the JSON_OBJECT get_keys functions.
Example for same is below
DECLARE
  jo          JSON_OBJECT_T;
  ja          JSON_ARRAY_T;
  keys        JSON_KEY_LIST;
  keys_string VARCHAR2(100);
BEGIN
  ja := new JSON_ARRAY_T;
  jo := JSON_OBJECT_T.parse('{"name":"Beda", 
                              "jobTitle":"codmonki", 
                              "projects":["json", "xml"]}');
  keys := jo.get_keys;
  FOR i IN 1..keys.COUNT LOOP
     ja.append(keys(i));
  END LOOP;
  keys_string := ja.to_string;
  DBMS_OUTPUT.put_line(keys_string);
END;
/
The output is as below
["name","jobTitle","projects"]  
Below is another example of a level 2 JSON where I will extract the individual key elements.
declare
  jdoc varchar2(1000) := '{
  "department": "Accounting",
  "employees": [
    {
      "name": "Shelley,Higgins",
      "job": "Accounting Manager"
    },
    {
      "name": "William,Gietz",
      "job": "Public Accountant"
    }
  ]
}';
  jobj json_object_t;
  keys json_key_list;
  jarr json_array_t;
  arrkeys json_key_list;
  arrvals VARCHAR2(1000);
  elem json_element_t;
  value_string VARCHAR2(1000);
begin
 
  jobj := json_object_t ( jdoc );
    
  keys := jobj.get_keys;
  
  for i in 1 .. keys.count loop
    dbms_output.put_line(keys(i));
    if jobj.get(keys(i)).is_array then
      jarr := jobj.get_array(keys(i));
      elem := jarr.get(1);
      arrkeys := treat ( elem as json_object_t ).get_keys;
      
      for j in 1 .. arrkeys.count loop
        dbms_output.put_line('  ' || arrkeys(j));

      end loop;
    end if;
  end loop;
end;
/
Output is as below
department
employees
  name
  job

This still gives me only the keys for the JSON. Our objective is to get keys in one array and the corresponding values in another array.

I try 1 more PLSQL bock as below
DECLARE
   jo          JSON_OBJECT_T;
   ja          JSON_ARRAY_T;
   jv          JSON_ARRAY_T; -- Initialize the array for values
   keys        JSON_KEY_LIST;
   keys_string VARCHAR2(100);
   key_string  VARCHAR2(1000);
   value_string VARCHAR2(1000);
   v_project   VARCHAR2(1000);
   l_ja json_array_t;
   l_po json_object_t;
BEGIN
   -- Initialize arrays
   ja := new JSON_ARRAY_T;
   jv := new JSON_ARRAY_T; -- Initialize jv as well
   
   -- Parse JSON object
   jo := JSON_OBJECT_T.parse('{
       "name":"Beda", 
       "jobTitle":"codmonki", 
       "projects":["json", "xml"]
   }');
   
   -- Retrieve keys
   keys := jo.get_keys;
   
   -- Loop through the keys and retrieve corresponding values
   FOR i IN 1..keys.COUNT LOOP
      key_string := keys(i);
      
      -- Retrieve the value using the get method for each key
      value_string := jo.get(key_string).to_string;
      
      DBMS_OUTPUT.put_line('Key: ' || key_string);
      DBMS_OUTPUT.put_line('Value: ' || value_string);
      
      IF key_string = 'projects' THEN
         l_ja := json_array_t.parse(value_string);
         FOR j IN 0 .. l_ja.get_size - 1 LOOP
            v_project := l_ja.get(j).to_string;
            DBMS_OUTPUT.put_line('Project: ' || v_project); -- Output each project value
         END LOOP;
      END IF;
      
   END LOOP;
   
END;
/
Output is as below
Key: name
Value: "Beda"
Key: jobTitle
Value: "codmonki"
Key: projects
Value: ["json","xml"]
Project: "json"
Project: "xml"

From the example PLSQL blocks above, we can see that parsing a JSON involves 2 steps
1. Parsing the JSON object.
2. Parsing the JSON array if any

Parsing the JSON object.
We first parse the JSON object using JSON_OBJECT_T.parse and assign it to JSON_OBJECT_T type variable jo.
We get the keys of the JSON object using get_keys method of JSON_OBJECT_T.
To get the value of each key element in the object, in a FOR loop with index i, we call
jo.get(keys(i)).to_string
jo := JSON_OBJECT_T.parse('json_object');
	keys := jo.get_keys;
	FOR i IN 1..keys.COUNT LOOP
	  key_string := keys(i);
	  value_string := jo.get(key_string).to_string;
	END LOOP;

Parsing the JSON array
If the JSON_OBJECT, key-value pair has an array, like say an array for employee details.
Here the key is 'employees', and the value is the array of employees.
In this case we parse the JSON array using JSON_ARRAY_T.parse and get the array ja.
For each element j in the array, we get the object. In this example the employee object.
We use the get method of JSON_ARRAY_T, and type cast it to JSOB_OBJECT_T
ja.get(j) and assign it to a JSON object variable.
We then repeat the steps to parse a JSON object.
--assign the array to a JSON_ARRAY_T variable
l_ja := JSON_ARRAY_T.parse(value_string);
 FOR j IN 0..l_ja.get_size - 1 LOOP
	-- Get the individual employee object and type cast it to JSON_OBJECT_T
	l_emp_obj := JSON_OBJECT_T(l_ja.get(j));

	-- Retrieve the keys for the employee object (name, job)
	emp_keys := l_emp_obj.get_keys;

	-- Loop through the employee keys (name, job)
	FOR k IN 1..emp_keys.COUNT LOOP
	   emp_value_string := l_emp_obj.get(emp_keys(k)).to_string;
	END LOOP;
END LOOP;
Example below shows the parsing of an employee object have both level 1 and level 2 data with the dbms_output.put_line displaying 
key-value pairs
employee JSON array
each object of the employee JSON array
key-value pairs of each employee JSON object
DECLARE
   jo             JSON_OBJECT_T;
   keys           JSON_KEY_LIST;
   emp_keys       JSON_KEY_LIST;
   key_string     VARCHAR2(1000);
   value_string   VARCHAR2(1000);
   emp_key_string VARCHAR2(1000);
   emp_value_string VARCHAR2(1000);
   l_ja           JSON_ARRAY_T;
   l_emp_obj      JSON_OBJECT_T; -- For handling individual employee objects
BEGIN

   -- Parse JSON object
   jo := JSON_OBJECT_T.parse('{
      "department": "Accounting",
      "employees": [
         {
            "name": "Shelley,Higgins",
            "job": "Accounting Manager"
         },
         {
            "name": "William,Gietz",
            "job": "Public Accountant"
         }
      ]
   }');
   
   -- Retrieve keys of the main JSON object
   keys := jo.get_keys;
   
   -- Loop through the main keys (department, employees)
   FOR i IN 1..keys.COUNT LOOP
      key_string := keys(i);
      -- Retrieve the value for each key
      value_string := jo.get(key_string).to_string;
      
      DBMS_OUTPUT.put_line('Key: ' || key_string);
      DBMS_OUTPUT.put_line('Value: ' || value_string);
      
      -- Check if the key is 'employees'
      IF key_string = 'employees' THEN
         DBMS_OUTPUT.put_line('Employees JSON array: ' || value_string);
         -- Parse the 'employees' array
         l_ja := JSON_ARRAY_T.parse(value_string);
         
         -- Loop through each employee object in the array
         FOR j IN 0..l_ja.get_size - 1 LOOP
            -- Get the individual employee object
            l_emp_obj := JSON_OBJECT_T(l_ja.get(j));
            DBMS_OUTPUT.put_line('Employee object ' || j || ' ' ||  l_ja.get(j).to_string);
            -- Retrieve the keys for the employee object (name, job)
            emp_keys := l_emp_obj.get_keys;
            
            -- Loop through the employee keys (name, job)
            FOR k IN 1..emp_keys.COUNT LOOP
               emp_key_string := emp_keys(k);
               emp_value_string := l_emp_obj.get(emp_key_string).to_string;
               
               -- Output employee key-value pairs
               DBMS_OUTPUT.put_line('Employee ' || j || ' ' || emp_key_string || ': ' || emp_value_string);
            END LOOP;
         END LOOP;
      END IF;
   END LOOP;
END;
/

Output is as below
Key: department
Value: "Accounting"
Key: employees
Value: [{"name":"Shelley,Higgins","job":"Accounting Manager"},{"name":"William,Gietz","job":"Public Accountant"}]
Employees JSON array: [{"name":"Shelley,Higgins","job":"Accounting Manager"},{"name":"William,Gietz","job":"Public Accountant"}]
Employee object 0 {"name":"Shelley,Higgins","job":"Accounting Manager"}
Employee 0 name: "Shelley,Higgins"
Employee 0 job: "Accounting Manager"
Employee object 1 {"name":"William,Gietz","job":"Public Accountant"}
Employee 1 name: "William,Gietz"
Employee 1 job: "Public Accountant"

One limitation in this parser is that to identify the level 2 data, I am hardcoding the Level 2 JSON tag for it. 
A more generic parser should be able to do so with no hardcoded values and parse any JSON.

For a more generic parser you can refer to 
Using PL/SQL Object Types for JSON 
Obtain all JSON keys from JSON columns

Thought for the day
Commit no offense against the public,
  and do not disgrace yourself among the people.
Do not commit a sin twice;
  not even for one will you go unpunished.
Do not say "He will consider the great number of my gifts,
  and when I make an offering to the most High God, he will accept it`."
Sirach 7:7-9

No comments:

Post a Comment