Navigation Bar

Thursday, May 8, 2025

A generic JSON parser and JSON generator using a config table

 In my earlier post I have shown how to parse a JSON nested upto any level and update any tag in the JSON by parsing each element in the JSON. The limitation of the parser was that it was able to handle JSONs having only object type elements in it. 

A generic multi level JSON object parse and update script using PLSQL

In this version of the parser, I have enhanced the parser to parse though an array of objects and an array of strings also. The script can parse a JSON nested upto multiple levels and can traverse though all types of JSON elements upto any level of nesting.
Again I mention that this parser and generator will not work for all types of JSONs. It is just an approach that I have come up with to parse and generate a JSON. In the process I have tried to make it flexible to parse and generate most types and shapes of JSONs. To make it a fool proof logic will involve a lot more coding and testing. It can be used as a base for someone to build on and enhance to make it more generic for all types of JSONs.
A question arises, If there are already built in oracle functions like JSON_MERGEPATCH, JSON_TRANSOFRM for updating JSONs, why parse and traverse a JSON.
One reason can be that if there are a large number of JSONs to be modified, then handling the same programatically through a well build JSON parsing and udpate logic will be faster.
This again needs to be analyzed on a case to case base.

For this, we modify the JSON_MATRIX table and add two more columns to it. The PARENT_JSON_TYPE and JSON_SRL_NO. 
CREATE TABLE JSON_MATRIX_CONFIG
(
JSON_LEVEL	NUMBER,                                -- indicates the level of nesting of the json tag/element
JSON_KEY	VARCHAR2(50 BYTE),                     -- the json key  
JSON_TYPE	VARCHAR2(50 BYTE),                     -- indicates where the json element is an array/object/key-value pair
JSON_VALUE	VARCHAR2(4000 BYTE),                   -- the value of the particular json key
PARENT_KEY	VARCHAR2(100 BYTE),                    -- the parent key for the json tag
PARENT_JSON_TYPE	VARCHAR2(50 BYTE),             -- the parent json type for the json tag. It can be an object or an array 
LEVEL_SEQ	NUMBER,                                -- indicates the sequence of the element in the json type
JSON_SRL_NO	NUMBER                                 -- the sequential srl no of the tag in the JSON
);
Below is a sample JSON which contains a combination of arrays and objects and multiple levels of nesting.
{
    "JsonList": {
        "JsonObj": {
            "objtest1": {
                "test1key1": "test1value1",
                "test1key2": "test1value2"
            },
            "ID": "1",
            "ObjName": "jsonParser",
            "objtest": {
                "key1": "value1",
                "objtest2": {
                    "test2key1": "test2value1",
                    "test2key2": "test2value2"
                },
                "key2": "value2"
            },
            "nestedarray": [
                "json1",
                "xml1"
            ]
        },
        "keyvaluetest": "markup",
        "projects": [
            "json",
            "xml"
        ]
    },
    "employees": [
        {
            "name": "Shelley,Higgins",
            "job": "Accounting Manager"
        },
        {
            "name": "William,Gietz",
            "job": "Public Accountant"
        }
    ]
}
The logic is similar to that used in the earlier example given in the link above.  It has been enhanced to traverse a JSON array.  For each element it will check if the element is an object, an array or a key-value pair and insert the information like the level of nesting in JSON, the json key, the json type, the json value, the parent key, the parent json type, the sequence of the element at that level, the the srl no of the tag in the JSON.
create or replace PACKAGE json_traversal_update_obj_arr
AS

v_type        VARCHAR2(10);
traversal_cnt NUMBER := 0;
num_json_srl_num   NUMBER := 0;
new_run  BOOLEAN := TRUE;
v_arr_obj_flg  VARCHAR2(1) := 'N';
num_arr_obj_cnt NUMBER := 0;
   TYPE object_array_key IS VARRAY(100) OF VARCHAR2(100);
   v_object_array_key object_array_key := object_array_key();
   
   TYPE obj_json_type IS VARRAY(100) OF VARCHAR2(100);
   v_arr_json_typ obj_json_type := obj_json_type();
   
PROCEDURE PARSE_JSON(p_json_input CLOB);
PROCEDURE json_object_traversal(p_json_input CLOB);
PROCEDURE json_array_traversal ( 
   json_document_in   IN CLOB);
PROCEDURE UPDATE_JSON_VALUE(p_key  VARCHAR2, p_level  NUMBER, p_new_value VARCHAR2);
PROCEDURE UPDATE_JSON_NEW_VALUE(p_srl_no  NUMBER, p_value  VARCHAR2, p_new_value VARCHAR2);
PROCEDURE gnrt_json;

END;

Below is package body for the same
create or replace PACKAGE BODY json_traversal_update_obj_arr
AS 

PROCEDURE UPDATE_JSON_VALUE(p_key  VARCHAR2, p_level  NUMBER, p_new_value VARCHAR2)
AS

BEGIN
  UPDATE JSON_MATRIX_CONFIG
  SET JSON_VALUE = p_new_value
  WHERE JSON_LEVEL = p_level
    AND JSON_KEY = p_key;

END;

PROCEDURE UPDATE_JSON_NEW_VALUE(p_srl_no  NUMBER, p_value  VARCHAR2, p_new_value VARCHAR2)
AS

BEGIN
  UPDATE JSON_MATRIX_CONFIG
      SET JSON_VALUE = p_new_value
  WHERE JSON_VALUE = p_value
    AND JSON_SRL_NO = p_srl_no;

END;

FUNCTION IS_JSON_TYPE (p_str  CLOB)
RETURN VARCHAR2 IS
--  l_str            CLOB := '["grape","orange","mango","banana"]'; --'test string'; --'["grape","orange","mango","banana"]'; --'{"employee_id" : 100, "employee_name" : "Stephen King"}';
  l_arr            JSON_ARRAY_T;
  l_obj            JSON_OBJECT_T;
  json_type        VARCHAR2(10);
 
BEGIN
  BEGIN
    l_arr := JSON_ARRAY_T(p_str);
    json_type := 'array';
  EXCEPTION
    WHEN OTHERS THEN
    BEGIN
        l_obj := JSON_OBJECT_T(p_str);
        json_type := 'object';
    EXCEPTION
       WHEN OTHERS THEN 
           json_type := 'string';
    END;   
  END;
  
--  dbms_output.dbms_output.put_line('json_type ' || json_type || ' : ' ||  p_str);
  RETURN json_type;
END;

PROCEDURE PARSE_JSON(p_json_input CLOB)
AS 
BEGIN
  traversal_cnt := 0;
  num_json_srl_num := 0;
  v_arr_json_typ := obj_json_type();
  v_object_array_key := object_array_key();
  IF IS_JSON_TYPE(p_json_input) = 'object'
  THEN
    v_type := 'object';
    
    json_traversal_update_obj_arr.json_object_traversal(p_json_input);
  ELSIF IS_JSON_TYPE(p_json_input) = 'array'
  THEN
    v_type := 'array';
    json_traversal_update_obj_arr.json_array_traversal(p_json_input);
  ELSE
    dbms_output.put_line('json_type string : ' || p_json_input );
  END IF;
END PARSE_JSON;



PROCEDURE json_object_traversal(p_json_input CLOB)
AS
   jo                    JSON_OBJECT_T;
   
   keys           JSON_KEY_LIST;
   key_string     VARCHAR2(1000);
   value_string   VARCHAR2(1000);
   v_json_type     VARCHAR2(10);
--   num_level_seq      NUMBER := 0;     
   nested_object_type   VARCHAR2(10);
 
BEGIN

      traversal_cnt := traversal_cnt + 1;
--      dbms_output.put_line('json_object_traversal ' || traversal_cnt);
      
   -- Parse JSON object
      
      jo := JSON_OBJECT_T.parse(p_json_input);
   -- Retrieve keys of the main JSON object
      keys := jo.get_keys;
   
   -- Loop through the main keys (department, employees)
--   dbms_output.put_line('key_count ' || keys.COUNT);
   FOR i IN 1..keys.COUNT LOOP
     num_json_srl_num := num_json_srl_num + 1;
     key_string := keys(i);

      -- Retrieve the value for each key
     value_string := jo.get(key_string).to_string;
--     dbms_output.put_line('key_string ' || key_string);
--     dbms_output.put_line('value_string ' || value_string);
     v_json_type := IS_JSON_TYPE(value_string);

  
        
 --    dbms_output.put_line('v_json_type ' || v_json_type);
     CASE 
       WHEN
          IS_JSON_TYPE(value_string) = 'object'
         THEN

              v_arr_json_typ.EXTEND;
              v_arr_json_typ(traversal_cnt) := 'object';
              v_object_array_key.EXTEND;
              v_object_array_key(traversal_cnt) := key_string;
              INSERT INTO JSON_MATRIX_CONFIG(json_level, json_key, json_type, level_seq, json_srl_no) VALUES (traversal_cnt, key_string, 'object', i, num_json_srl_num);

     IF traversal_cnt > 1 
     THEN
        UPDATE JSON_MATRIX_CONFIG
        SET PARENT_KEY = v_object_array_key(traversal_cnt-1),
        PARENT_JSON_TYPE = v_arr_json_typ(traversal_cnt-1)
      WHERE JSON_LEVEL = traversal_cnt
        AND PARENT_KEY IS NULL;
     END IF; 
              
              JSON_OBJECT_TRAVERSAL(value_string);
  
         WHEN IS_JSON_TYPE(value_string) = 'string'
         THEN
          --dbms_output.put_line('type string ' || key_string || ' ' || value_string);
          IF v_arr_obj_flg = 'Y'
          THEN
             NULL; 
        --    dbms_output.put_line('arr obj true ' || num_arr_obj_cnt || ' : ' ||  key_string || ' ' || value_string);
          END IF;
          value_string := TRIM (BOTH '"' FROM (value_string));
          
          v_object_array_key.EXTEND;
          v_object_array_key(traversal_cnt) := key_string;
          v_arr_json_typ.EXTEND;
          v_arr_json_typ(traversal_cnt) := 'string';
          IF v_arr_obj_flg = 'Y'
          THEN
            INSERT INTO JSON_MATRIX_CONFIG (json_level, json_key, json_type, json_value, level_seq, json_srl_no) VALUES (traversal_cnt, key_string, 'string', value_string, num_arr_obj_cnt, num_json_srl_num);
          ELSE
            INSERT INTO JSON_MATRIX_CONFIG (json_level, json_key, json_type, json_value,  level_seq, json_srl_no) VALUES (traversal_cnt, key_string, 'string', value_string, i, num_json_srl_num);
          END IF;
         WHEN IS_JSON_TYPE(value_string) = 'array'
         THEN
           
            v_arr_json_typ.EXTEND;
            v_arr_json_typ(traversal_cnt) := 'array';
            v_object_array_key.EXTEND;
            v_object_array_key(traversal_cnt) := key_string;
            INSERT INTO JSON_MATRIX_CONFIG (json_level, json_key, json_type, level_seq,  json_srl_no) VALUES (traversal_cnt, key_string, 'array',  i, num_json_srl_num);
            JSON_ARRAY_TRAVERSAL(value_string);

         ELSE 
            DBMS_OUTPUT.put_line ( 
               '*** No match for type on object index ' || i);    
      END CASE;
   END LOOP;

     IF traversal_cnt > 1 
     THEN
        UPDATE JSON_MATRIX_CONFIG
        SET PARENT_KEY = v_object_array_key(traversal_cnt-1),
        PARENT_JSON_TYPE = v_arr_json_typ(traversal_cnt-1)
      WHERE JSON_LEVEL = traversal_cnt
        AND PARENT_KEY IS NULL;
     END IF;       
   traversal_cnt := traversal_cnt - 1;

END json_object_traversal;


PROCEDURE json_array_traversal ( 
   json_document_in   IN CLOB
   ) 
IS 
   l_array     json_array_t;
   l_array_str VARCHAR2(4000);
   l_object    json_object_t; 
   l_keys      json_key_list; 
   l_element   json_element_t;
   num_arr_level_seq      NUMBER := 0;
BEGIN 
   l_array := json_array_t.parse (json_document_in); 
   traversal_cnt := traversal_cnt + 1;
  -- dbms_output.put_line('json_object_traversal array ' || traversal_cnt);
 --  put_line ('Traverse: ' || l_array.stringify ()); 
 
   FOR indx IN 0 .. l_array.get_size - 1 
   LOOP 
    num_json_srl_num := num_json_srl_num + 1;
 
      CASE 
         WHEN l_array.get (indx).is_string 
         THEN
            num_arr_level_seq := num_arr_level_seq + 1;
     --       put_line('index ' || indx || ' value ' || l_array.get (indx).to_string || ' is a string');
            l_array_str := l_array.get (indx).to_string;
            l_array_str := TRIM (BOTH '"' FROM (l_array_str));
            v_arr_json_typ.EXTEND;
            v_arr_json_typ(traversal_cnt) := 'string';
            
         INSERT INTO JSON_MATRIX_CONFIG (json_level, json_key, json_type, json_value, level_seq, json_srl_no) VALUES (traversal_cnt, null, 'string',l_array_str , num_arr_level_seq, num_json_srl_num);
         WHEN l_array.get (indx).is_object 
         THEN 
            v_arr_obj_flg := 'Y';
            num_arr_obj_cnt := num_arr_obj_cnt + 1;

            l_object := TREAT (l_array.get (indx) AS json_object_t); 
     --       dbms_output.put_line(l_object.to_string || ' is an object');
            traversal_cnt := traversal_cnt - 1; --decrease the JSON level as it has already been incremented
            num_json_srl_num := num_json_srl_num - 1;
            l_keys := l_object.get_keys;
            JSON_OBJECT_TRAVERSAL(l_object.to_string);
            traversal_cnt := traversal_cnt + 1; --INCREMENT the JSON level as it has already been decremented
         WHEN l_array.get (indx).is_array 
         THEN 
            num_arr_level_seq := num_arr_level_seq + 1;
--            dbms_output.put_line(l_array.get (indx).stringify || ' is an array'); -- call json_array_traversal again
            
            v_arr_json_typ.EXTEND;
            v_arr_json_typ(traversal_cnt) := 'array';
            
            INSERT INTO JSON_MATRIX_CONFIG (json_level, json_key, json_type, json_value, level_seq, json_srl_no) VALUES (traversal_cnt, null, 'array', l_array.get (indx).to_string, num_arr_level_seq, num_json_srl_num);
            json_array_traversal ( 
               TREAT (l_array.get (indx) AS json_array_t).stringify ()); 
         ELSE 
            DBMS_OUTPUT.put_line ( 
               '*** No match for type on array index ' || indx); 
      END CASE; 
   END LOOP;
   num_arr_obj_cnt := 0;
   v_arr_obj_flg := 'N';
     IF traversal_cnt > 1 
     THEN
        UPDATE JSON_MATRIX_CONFIG
        SET PARENT_KEY = v_object_array_key(traversal_cnt-1),
        PARENT_JSON_TYPE = v_arr_json_typ(traversal_cnt-1)
      WHERE JSON_LEVEL = traversal_cnt
        AND PARENT_KEY IS NULL;
     END IF;
   traversal_cnt := traversal_cnt - 1;
END;

PROCEDURE gnrt_json
IS
  CURSOR C0
  IS
    SELECT JSON_KEY, JSON_SRL_NO min_srl_no,
    (SELECT NVL(MIN(JSON_SRL_NO),999) FROM json_matrix_config b WHERE PARENT_KEY IS NULL AND b.JSON_SRL_NO > a.JSON_SRL_NO) max_srl_no,
    JSON_VALUE, JSON_TYPE
    FROM json_matrix_config a
    WHERE PARENT_KEY IS NULL
    ORDER BY LEVEL_SEQ;

  CURSOR C1 (min_srl_no NUMBER, max_srl_no  NUMBER)
  IS
      SELECT DISTINCT a.JSON_LEVEL, a.PARENT_KEY, a.JSON_TYPE, b.JSON_SRL_NO
    FROM json_matrix_config a, json_matrix_config b
    WHERE a.JSON_SRL_NO >= min_srl_no AND a.JSON_SRL_NO < max_srl_no
    AND b.JSON_KEY = a.PARENT_KEY
    ORDER BY a.JSON_LEVEL DESC, b.JSON_SRL_NO;

    
  CURSOR C2(p_parent_key   VARCHAR2, p_srl_no NUMBER)
  IS
    SELECT PARENT_KEY, PARENT_JSON_TYPE, JSON_KEY, JSON_VALUE, JSON_TYPE, LEVEL_SEQ, JSON_SRL_NO
    FROM json_matrix_config
    WHERE PARENT_KEY = p_parent_key
    AND JSON_SRL_NO > p_srl_no
    ORDER BY  JSON_SRL_NO,LEVEL_SEQ;
    
    jo_temp        JSON_OBJECT_T := JSON_OBJECT_T();
    jo_temp_1        JSON_OBJECT_T := JSON_OBJECT_T();
    jo_temp_final        JSON_OBJECT_T := JSON_OBJECT_T();
    l_arr JSON_ARRAY_T := JSON_ARRAY_T();
    l_arr_1 JSON_ARRAY_T := JSON_ARRAY_T();
    l_arr_final JSON_ARRAY_T := JSON_ARRAY_T();
    tmp_str    VARCHAR2(4000);
    tmp_obj_str    VARCHAR2(4000);
    tmp_arr_str    VARCHAR2(4000);
    tmp_json_key    VARCHAR2(100);
    prev_level_seq  NUMBER := 0;
    prev_parent_key   VARCHAR2(100); 
    prev_srl_no     NUMBER := 0;
    key_value   BOOLEAN := FALSE;
BEGIN
  FOR rec_srl IN C0
  LOOP --{
    FOR pkey IN C1(rec_srl.min_srl_no, rec_srl.max_srl_no)
    LOOP
        jo_temp := JSON_OBJECT_T();
        jo_temp_1 := JSON_OBJECT_T();
        l_arr := JSON_ARRAY_T();
        prev_level_seq := 0;
        key_value := FALSE;

        FOR rec IN C2(pkey.PARENT_KEY, pkey.JSON_SRL_NO)
        LOOP
           IF rec.JSON_TYPE = 'object'
           THEN
	   
              IF rec.LEVEL_SEQ < prev_level_seq 
                    THEN 
                    CONTINUE;
              END IF;
              SELECT JSON_VALUE
              INTO tmp_obj_str
              FROM json_matrix_config
              WHERE
              JSON_TYPE = rec.JSON_TYPE
              AND JSON_KEY = rec.JSON_KEY
              AND JSON_SRL_NO = rec.JSON_SRL_NO;

--              put_line ('rec.JSON_KEY ' || rec.JSON_KEY || ' rec.JSON_TYPE ' || rec.JSON_TYPE || ' rec.JSON_SRL_NO ' || rec.JSON_SRL_NO);
--              put_line('tmp_obj_str ' || tmp_obj_str);
 --             tmp_obj_str := rec.JSON_VALUE;
              tmp_obj_str := TRIM (BOTH '"' FROM (tmp_obj_str));
              jo_temp_1 := JSON_OBJECT_T(tmp_obj_str);
              jo_temp.put(rec.JSON_KEY,jo_temp_1);
           ELSIF rec.JSON_TYPE = 'array'
           THEN
                  IF rec.LEVEL_SEQ < prev_level_seq 
                    THEN
                    CONTINUE;
                  END IF;
              SELECT JSON_VALUE
              INTO tmp_arr_str
              FROM json_matrix_config
              WHERE
              JSON_TYPE = rec.JSON_TYPE
              AND JSON_KEY = rec.JSON_KEY
              AND JSON_SRL_NO = rec.JSON_SRL_NO;
           
--              tmp_arr_str := rec.JSON_VALUE;
              tmp_arr_str := TRIM (BOTH '"' FROM (tmp_arr_str));
              l_arr_1 := JSON_ARRAY_T(tmp_arr_str);
              jo_temp.put(rec.JSON_KEY,l_arr_1);              
           ELSIF rec.JSON_TYPE = 'string'
           THEN
              IF rec.JSON_KEY IS NULL
              THEN
--                  put_line ('prev_level_seq ' || prev_level_seq || ' rec.LEVEL_SEQ ' || rec.LEVEL_SEQ || ' rec.JSON_VALUE ' || rec.JSON_VALUE);
--                  put_line ('rec.JSON_KEY ' || rec.JSON_KEY || ' rec.JSON_VALUE ' || rec.JSON_VALUE );
                  tmp_str := rec.JSON_VALUE;
                  tmp_str := TRIM (BOTH '"' FROM (tmp_str));
                  IF rec.LEVEL_SEQ <= prev_level_seq 
                    THEN 
                    CONTINUE;
                  END IF;
                  put_line ('tmp_str ' || tmp_str );
                  l_arr.append(tmp_str);
              ELSE
                  key_value := TRUE;
                  IF prev_level_seq <> 0 AND prev_level_seq <> rec.LEVEL_SEQ AND rec.PARENT_JSON_TYPE = 'array'-- it is an object array
                  THEN
 --                   put_line ('prev_level_seq ' || prev_level_seq);  
 --                   put_line ('jo_temp ' || jo_temp.to_string);
                    l_arr.append(jo_temp);  
                    jo_temp := JSON_OBJECT_T();
                  END IF;
                  IF prev_srl_no + 1 = rec.JSON_SRL_NO AND prev_parent_key = rec.PARENT_KEY
                  THEN
                    IF rec.LEVEL_SEQ < prev_level_seq 
                    THEN 
                      CONTINUE;
                    END IF;
                  ELSIF rec.LEVEL_SEQ <= prev_level_seq 
                    THEN 
                    CONTINUE;
                  END IF;
                  tmp_str := rec.JSON_VALUE;
                  tmp_str := TRIM (BOTH '"' FROM (tmp_str));
                  jo_temp.put(rec.JSON_KEY, tmp_str);
--                  put_line('jo_temp in loop' || jo_temp.to_string);
              END IF;
          END IF;
          prev_level_seq := rec.LEVEL_SEQ;
          prev_parent_key := rec.PARENT_KEY;
          prev_srl_no := rec.JSON_SRL_NO;
        END LOOP;
        
        IF NOT l_arr.IS_NULL
        THEN  
          IF key_value = TRUE  
          THEN
            l_arr.append(jo_temp);  
          END IF;
          tmp_arr_str := l_arr.to_string;
          tmp_arr_str := TRIM (BOTH '"' FROM (tmp_arr_str));
          
          UPDATE json_matrix_config a
          SET JSON_VALUE = tmp_arr_str
          WHERE JSON_KEY = pkey.PARENT_KEY
            AND JSON_TYPE = 'array'
            AND LEVEL_SEQ = (
              SELECT MAX(LEVEL_SEQ)
              FROM json_matrix_config b
              WHERE JSON_KEY = pkey.PARENT_KEY
                AND JSON_TYPE = 'array')
             AND JSON_LEVEL = pkey.JSON_LEVEL - 1
             AND JSON_SRL_NO = pkey.JSON_SRL_NO;
        END IF;
        
--        dbms_output.put_line('temp json ' || jo_temp.to_string);
        tmp_obj_str := jo_temp.to_string;
        tmp_obj_str := TRIM (BOTH '"' FROM (tmp_obj_str));
        
        UPDATE json_matrix_config a
        SET JSON_VALUE = tmp_obj_str
        WHERE JSON_KEY = pkey.PARENT_KEY
          AND JSON_TYPE = 'object'
          AND LEVEL_SEQ = (
            SELECT MAX(LEVEL_SEQ)
            FROM json_matrix_config b
            WHERE JSON_KEY = pkey.PARENT_KEY
              AND JSON_TYPE = 'object')
           AND JSON_LEVEL = pkey.JSON_LEVEL - 1
           AND JSON_SRL_NO = pkey.JSON_SRL_NO;  
           
 
    END LOOP;
       
  --     dbms_output.put_line('final json str ' || tmp_obj_str);
      IF rec_srl.JSON_TYPE = 'string'
      THEN
        jo_temp_final.put(rec_srl.JSON_KEY,rec_srl.JSON_VALUE);
      END IF;  
      IF rec_srl.JSON_TYPE = 'object'
      THEN
--       dbms_output.put_line('final object json str ' || tmp_obj_str);
        SELECT JSON_VALUE
        INTO tmp_obj_str
        FROM json_matrix_config
        WHERE PARENT_KEY IS NULL
        AND JSON_TYPE = rec_srl.JSON_TYPE
        AND JSON_KEY = rec_srl.JSON_KEY;
        
        tmp_obj_str := TRIM (BOTH '"' FROM (tmp_obj_str));
        
        jo_temp_1 := JSON_OBJECT_T(tmp_obj_str);
          jo_temp_final.put(rec_srl.JSON_KEY,jo_temp_1);
      END IF;
--      
      IF rec_srl.JSON_TYPE = 'array'
      THEN
        SELECT JSON_VALUE
        INTO tmp_arr_str
        FROM json_matrix_config
        WHERE PARENT_KEY IS NULL
        AND JSON_TYPE = rec_srl.JSON_TYPE
        AND JSON_KEY = rec_srl.JSON_KEY; 

       tmp_arr_str := TRIM (BOTH '"' FROM (tmp_arr_str));
       l_arr_1 := JSON_ARRAY_T(tmp_arr_str);
       jo_temp_final.put(rec_srl.JSON_KEY,l_arr_1);
--          l_arr_final.append(l_arr_1);
      END IF;

  END LOOP; --} end C0
  dbms_output.put_line('final obj json ' || jo_temp_final.to_string);
  tmp_arr_str := TRIM (BOTH '"' FROM (jo_temp_final.to_string));
  INSERT INTO JSON_MATRIX_CONFIG ( json_key, json_value) VALUES ( 'final json', tmp_arr_str);

END;

END json_traversal_update_obj_arr;
The JSON matrix table populated is as below
JSON_LEVELJSON_KEYJSON_TYPEJSON_VALUEPARENT_KEYPARENT_JSON_TYPELEVEL_SEQJSON_SRL_NO
1JsonListobject{"JsonObj":{"objtest1":{"test1key1":"test1value1update","test1key2":"test1value2"},"ID":"1","ObjName":"jsonParserUpdate","objtest":{"key1":"value101","objtest2":{"test2key1":"test2value1","test2key2":"test2value2"},"key2":"value2"},"nestedarray":["json1","xml1"]},"keyvaluetest":"markup","projects":["jsonupdate","xml"]}  11
2JsonObjobject{"objtest1":{"test1key1":"test1value1update","test1key2":"test1value2"},"ID":"1","ObjName":"jsonParserUpdate","objtest":{"key1":"value101","objtest2":{"test2key1":"test2value1","test2key2":"test2value2"},"key2":"value2"},"nestedarray":["json1","xml1"]}JsonListobject12
3objtest1object{"test1key1":"test1value1update","test1key2":"test1value2"}JsonObjobject13
4test1key1stringtest1value1updateobjtest1object14
4test1key2stringtest1value2objtest1object25
3IDstring1JsonObjobject26
3ObjNamestringjsonParserUpdateJsonObjobject37
3objtestobject{"key1":"value101","objtest2":{"test2key1":"test2value1","test2key2":"test2value2"},"key2":"value2"}JsonObjobject48
4key1stringvalue101objtestobject19
4objtest2object{"test2key1":"test2value1","test2key2":"test2value2"}objtestobject210
5test2key1stringtest2value1objtest2object111
5test2key2stringtest2value2objtest2object212
4key2stringvalue2objtestobject313
3nestedarrayarray["json1","xml1"]JsonObjobject514
4 stringjson1nestedarrayarray115
4 stringxml1nestedarrayarray216
2keyvalueteststringmarkupJsonListobject217
2projectsarray["jsonupdate","xml"]JsonListobject318
3 stringjsonupdateprojectsarray119
3 stringxmlprojectsarray220
1employeesarray[{"name":"Shelley,Higgins","job":"Accounting Manager"},{"name":"William,Gietz,Sr","job":"Public Accountant"}]  221
2namestringShelley,Higginsemployeesarray122
2jobstringAccounting Manageremployeesarray123
2namestringWilliam,Gietz,Sremployeesarray224
2jobstringPublic Accountantemployeesarray225
 final json {"JsonList":{"JsonObj":{"objtest1":{"test1key1":"test1value1update","test1key2":"test1value2"},"ID":"1","ObjName":"jsonParserUpdate","objtest":{"key1":"value101","objtest2":{"test2key1":"test2value1","test2key2":"test2value2"},"key2":"value2"},"nestedarray":["json1","xml1"]},"keyvaluetest":"markup","projects":["jsonupdate","xml"]},"employees":[{"name":"Shelley,Higgins","job":"Accounting Manager"},{"name":"William,Gietz,Sr","job":"Public Accountant"}]}   
PLSQL block to parse, update and regenerate the JSON.
DECLARE
  P_JSON_INPUT CLOB;
BEGIN

  
  P_JSON_INPUT := '{
    "JsonList": {
        "JsonObj": {
            "objtest1": {
                "test1key1": "test1value1",
                "test1key2": "test1value2"
            },
            "ID": "1",
            "ObjName": "jsonParser",
            "objtest": {
                "key1": "value1",
                "objtest2": {
                    "test2key1": "test2value1",
                    "test2key2": "test2value2"
                },
                "key2": "value2"
            },
            "nestedarray": [
                "json1",
                "xml1"
            ]
        },
        "keyvaluetest": "markup",
        "projects": [
            "json",
            "xml"
        ]
    },
    "employees": [
        {
            "name": "Shelley,Higgins",
            "job": "Accounting Manager"
        },
        {
            "name": "William,Gietz",
            "job": "Public Accountant"
        }
    ]
}';

  EXECUTE IMMEDIATE 'TRUNCATE TABLE JSON_MATRIX_CONFIG';


  JSON_TRAVERSAL_UPDATE_OBJ_ARR.PARSE_JSON(
    P_JSON_INPUT => P_JSON_INPUT
  );
  
JSON_TRAVERSAL_UPDATE_OBJ_ARR.UPDATE_JSON_NEW_VALUE(7, 'jsonParser', 'jsonParserUpdate');
JSON_TRAVERSAL_UPDATE_OBJ_ARR.UPDATE_JSON_NEW_VALUE(9, 'value1', 'value101');
JSON_TRAVERSAL_UPDATE_OBJ_ARR.UPDATE_JSON_NEW_VALUE(17, 'markupup', 'markupupdate');
JSON_TRAVERSAL_UPDATE_OBJ_ARR.UPDATE_JSON_NEW_VALUE(4, 'test1value1', 'test1value1update');
JSON_TRAVERSAL_UPDATE_OBJ_ARR.UPDATE_JSON_NEW_VALUE(19, 'json', 'jsonupdate');
JSON_TRAVERSAL_UPDATE_OBJ_ARR.UPDATE_JSON_NEW_VALUE(24, 'William,Gietz', 'William,Gietz,Sr');

    JSON_TRAVERSAL_UPDATE_OBJ_ARR.gnrt_json;

--rollback; 
END;

The new JSON with the updated values
{
    "JsonList": {
        "JsonObj": {
            "objtest1": {
                "test1key1": "test1value1update",
                "test1key2": "test1value2"
            },
            "ID": "1",
            "ObjName": "jsonParserUpdate",
            "objtest": {
                "key1": "value101",
                "objtest2": {
                    "test2key1": "test2value1",
                    "test2key2": "test2value2"
                },
                "key2": "value2"
            },
            "nestedarray": [
                "json1",
                "xml1"
            ]
        },
        "keyvaluetest": "markup",
        "projects": [
            "jsonupdate",
            "xml"
        ]
    },
    "employees": [
        {
            "name": "Shelley,Higgins",
            "job": "Accounting Manager"
        },
        {
            "name": "William,Gietz,Sr",
            "job": "Public Accountant"
        }
    ]
}
Thus we see that the parser has worked for a JSON having multiple levels of nesting, objects and arrays.
For this parser to work for JSONs of all types, shapes and sizes some tweaking of the code will need to be done, to make it work for different scenarios.

There are few bugs in this code which need to be ironed out to make it a more robust parser for most json types.
For Example, I try it our for 2 random JSONs. It has worked fine for one JSON and slight mismatch in values for the second, though the JSON structure has been retained.

For now I leave these bugs in the parser/generator as is, for the reader of this post to tweak and fix. You can give me the fix as a comment, and I will be glad incorporate it in the code.

I try the same with another sample JSON taken from
https://support.oneskyapp.com/hc/en-us/articles/208047697-JSON-sample-files link -- and the JSON parsing and JSON generation logic have worked fine.
DECLARE
  P_JSON_INPUT CLOB;
BEGIN

  
  P_JSON_INPUT := '{
    "quiz": {
        "sport": {
            "q1": {
                "question": "Which one is correct team name in NBA?",
                "options": [
                    "New York Bulls",
                    "Los Angeles Kings",
                    "Golden State Warriros",
                    "Huston Rocket"
                ],
                "answer": "Huston Rocket"
            }
        },
        "maths": {
            "q1": {
                "question": "5 + 7 = ?",
                "options": [
                    "10",
                    "11",
                    "12",
                    "13"
                ],
                "answer": "12"
            },
            "q2": {
                "question": "12 - 8 = ?",
                "options": [
                    "1",
                    "2",
                    "3",
                    "4"
                ],
                "answer": "4"
            }
        }
    }
}';

  EXECUTE IMMEDIATE 'TRUNCATE TABLE JSON_MATRIX_CONFIG';


  JSON_TRAVERSAL_UPDATE_OBJ_ARR.PARSE_JSON(
    P_JSON_INPUT => P_JSON_INPUT
  );
  

    JSON_TRAVERSAL_UPDATE_OBJ_ARR.gnrt_json;

--rollback; 
END;
The output of this JSON run, has the same structure and data as the input JSON.
{
    "quiz": {
        "sport": {
            "q1": {
                "question": "Which one is correct team name in NBA?",
                "options": [
                    "New York Bulls",
                    "Los Angeles Kings",
                    "Golden State Warriros",
                    "Huston Rocket"
                ],
                "answer": "Huston Rocket"
            }
        },
        "maths": {
            "q1": {
                "question": "5 + 7 = ?",
                "options": [
                    "10",
                    "11",
                    "12",
                    "13"
                ],
                "answer": "12"
            },
            "q2": {
                "question": "12 - 8 = ?",
                "options": [
                    "1",
                    "2",
                    "3",
                    "4"
                ],
                "answer": "4"
            }
        }
    }
}

Below JSON which is getting parsed, but there is a mismatch in the output and input JSON for the JSON which has got recreated.
The JSON_TRAVERSAL_UPDATE_OBJ_ARR.gnrt_json has to be revisited. The cursors which are selecting the data at each level for recreating the JSON have to be re written to select the correct data for each JSON sub object being generated. Currently these queries are selecting more data for each loop of parsing, and so a lot of IFs and ELSEs have been put in the code to skip irrelevant data for each set of data being processsed. This is making the program too logikesh. 
Adding more logic to the code is will only add to the complexity and will impact the JSON generation logic for existing JSONs which are working.For instance, the LineItems array of objects is fetching the data for both the objects in the array for each iteration. The cursor query needs to be modified to handle such a scenarion.
Secondly there is a bug in the traversal logic in the case of an array of objects containing objects and tag-value pairs - LineItems - where in the LEVEL_SEQ column is not getting properly populated for such types of array-object combinations, resulting in errors in the final output.
Once these issues are ironed out the JSON generator code will be cleaner and will work for a wider range of JSON types and sizes with various levels of nesting. 
DECLARE
  P_JSON_INPUT CLOB;
BEGIN

  
  P_JSON_INPUT := '{
    "Reference": "ABULL-20140421",
    "Requestor": "Alexis Bull",
    "User": "ABULL",
    "CostCenter": "A50",
    "ShippingInstructions": {
        "name": "Alexis Bull",
        "Address": {
            "street": "200 Sporting Green",
            "city": "South San Francisco",
            "state": "CA",
            "zipCode": 99236,
            "country": "United States of America"
        },
        "Phone": [
            {
                "type": "Office",
                "number": "909-555-7307"
            },
            {
                "type": "Mobile",
                "number": "415-555-1234"
            }
        ]
    },
    "Special Instructions": null,
    "AllowPartialShipment": true,
    "LineItems": [
        {
            "ItemNumber": 1,
            "Part": {
                "Description": "One Magic Christmas",
                "UnitPrice": 19.95,
                "UPCCode": 13131092899
            },
            "Quantity": 9
        },
        {
            "ItemNumber": 2,
            "Part": {
                "Description": "Lethal Weapon",
                "UnitPrice": 19.95,
                "UPCCode": 85391628927
            },
            "Quantity": 5
        }
    ],
    "PONumber": 1700,
    "totalQuantity": 14,
    "totalPrice": 279.3
}';

  EXECUTE IMMEDIATE 'TRUNCATE TABLE JSON_MATRIX_CONFIG';


  JSON_TRAVERSAL_UPDATE_OBJ_ARR.PARSE_JSON(
    P_JSON_INPUT => P_JSON_INPUT
  );
  
    JSON_TRAVERSAL_UPDATE_OBJ_ARR.gnrt_json;

--rollback; 
END;
The output JSON is as below which has same structure and format as input but some differences in values for few tags. For such scenarios the json parser and json generation logic has to be revisited.

{
    "Reference": "ABULL-20140421",
    "Requestor": "Alexis Bull",
    "User": "ABULL",
    "CostCenter": "A50",
    "ShippingInstructions": {
        "name": "Alexis Bull",
        "Address": {
            "street": "200 Sporting Green",
            "city": "South San Francisco",
            "state": "CA",
            "zipCode": "99236",
            "country": "United States of America"
        },
        "Phone": [
            {
                "type": "Office",
                "number": "909-555-7307"
            },
            {
                "type": "Mobile",
                "number": "415-555-1234"
            }
        ]
    },
    "Special Instructions": "null",
    "AllowPartialShipment": "true",
    "LineItems": [
        {
            "ItemNumber": "1",
            "Part": {
                "Description": "Lethal Weapon",
                "UnitPrice": "19.95",
                "UPCCode": "85391628927"
            }
        },
        {
            "Part": {
                "Description": "Lethal Weapon",
                "UnitPrice": "19.95",
                "UPCCode": "85391628927"
            }
        }
    ],
    "PONumber": "1700",
    "totalQuantity": "14",
    "totalPrice": "279.3"
}
Thus overall we see that the parser and JSON generator logic is working fine for a few multi level nested JSONs of different structures. There may be some issues for some JSONs like the one above and for this it will need to be handled on a case to case basis. Thank You !!!


God's Word for the day
Unless the LORD builds the house, 
  those who build it labor in vain.
Unless the LORD guards the city, 
  the guards keep watch in vain.
It is in vain you rise up early, 
  and go late to rest,
Eating the bread of anxious toil;
  for he gives sleep to His beloved.
Psalm 127:1-2

No comments:

Post a Comment