'ORACLE - JSON To Key Value Pair Table

Is there any way to obtain a table with key/value pairs from a CLOB Json Column?

The idea here is to get these values, on a dynamic way. Because the CLOB column does not always contain the same structure.

I've created a function that does this, however since it literally parses the json string, when we use it in a table with many records its very slow. And by very slow I mean like 2-5 records per second, i know it's terrible.

The Oracle tools (v.12c) do not provide a dynamic way to obtain the json tags/values, we have always to specify the paths.

I've been digging all around without any luck. Any thoughts?



Solution 1:[1]

Here's a variant that will walk a nested structure.

SQL> drop FUNCTION PROCESS_JSON_DOCUMENT
  2  /

Function dropped.

SQL> drop TYPE NV_PAIR_TABLE
  2  /

Type dropped.

SQL> drop TYPE NV_PAIR_T
  2  /

Type dropped.

SQL> create or replace TYPE NV_PAIR_T as object (
  2    JSON_PATH  VARCHAR2(4000),
  3    VALUE      VARCHAR2(4000)
  4  )
  5  /

Type created.

SQL> create or replace TYPE NV_PAIR_TABLE
  2  as TABLE of NV_PAIR_T
  3  /

Type created.

SQL> create or replace FUNCTION PROCESS_JSON_DOCUMENT(P_JSON_PATH VARCHAR2, P_JSON_DOCUMENT VARCHAR2)
  2  return NV_PAIR_TABLE PIPELINED
  3  as
  4    V_JSON_OBJECT    JSON_OBJECT_T := JSON_OBJECT_T(P_JSON_DOCUMENT);
  5    V_KEY_LIST       JSON_KEY_LIST := V_JSON_OBJECT.get_keys();
  6    V_KEY_NAME       VARCHAR2(4000);
  7    V_JSON_PATH      VARCHAR2(4000);
  8    V_CHILD_DOCUMENT VARCHAR2(4000);
  9  begin
 10    for i in 1..V_KEY_LIST.count loop
 11      V_KEY_NAME := V_KEY_LIST(i);
 12      if (V_JSON_OBJECT.get_type(V_KEY_LIST(i)) <> 'OBJECT') then
 13          pipe row (NV_PAIR_T(P_JSON_PATH || '.' || V_KEY_NAME,V_JSON_OBJECT.get_string(V_KEY_NAME)));
 14      else
 15        V_JSON_PATH := P_JSON_PATH || '.' || V_KEY_NAME;
 16        V_CHILD_DOCUMENT := V_JSON_OBJECT.get_object(V_KEY_NAME).to_string();
 17        for j in (select * from TABLE(PROCESS_JSON_DOCUMENT(V_JSON_PATH, V_CHILD_DOCUMENT))) loop
 18          pipe row (NV_PAIR_T(J.JSON_PATH,J.VALUE));
 19        end loop;
 20      end if;
 21    end loop;
 22  end;
 23  /

Function created.

SQL> column JSON_PATH format A32
SQL> column VALUE format A32
SQL> select *
  2   from TABLE(PROCESS_JSON_DOCUMENT('$','{"A":"AA", "B":"BB", "C":"CC", "X" : {"A":"AA", "B":"BB", "C":"CC"}}'))
  3  /
$.A                              AA
$.B                              BB
$.C                              CC
$.X.A                            AA
$.X.B                            BB
$.X.C                            CC

6 rows selected.

SQL>

Solution 2:[2]

I have below json not able to parse above function. I want to parse data tag value

   {
 "data": [
   {
     "6": {
       "value": "test1"
     },
     "7": {
       "value": "test2"
     },
     "8": {
       "value": ""
     },
     "9": {
       "value": 1
     },
     "10": {
       "value": "test5"
     },
     "11": {
       "value": ""
     }
   },
   {
     "6": {
       "value": "test2"
     },
     "7": {
       "value": "test3"
     },
     "8": {
       "value": ""
     },
     "9": {
       "value": 2
     },
     "10": {
       "value": "test5"
     },
     "11": {
       "value": ""
     }
   },
   {
     "6": {
       "value": "test1"
     },
     "7": {
       "value": "test2"
     },
     "8": {
       "value": ""
     },
     "9": {
       "value": 1
     },
     "10": {
       "value": "test5"
     },
     "11": {
       "value": ""
     }
   },
   {
     "6": {
       "value": "test2"
     },
     "7": {
       "value": "test3"
     },
     "8": {
       "value": ""
     },
     "9": {
       "value": 2
     },
     "10": {
       "value": "test5"
     },
     "11": {
       "value": ""
     }
   }
 ],
 "fields": [
   {
     "id": 9,
     "label": "COL1",
     "type": "numeric"
   },
   {
     "id": 6,
     "label": "COL2",
     "type": "text"
   },
   {
     "id": 7,
     "label": "COL3",
     "type": "text"
   },
   {
     "id": 8,
     "label": "COL4",
     "type": "text"
   },
   {
     "id": 10,
     "label": "COL5",
     "type": "text"
   },
   {
     "id": 11,
     "label": "COL_DATE6",
     "type": "date"
   }
 ],
 "metadata": {
   "numFields": 6,
   "numRecords": 4,
   "skip": 0,
   "totalRecords": 4
 }

}

Solution 3:[3]

The following works to stop the execution of the loop and restart it. It's using a separate done variable to check in the loop.

However, note that this only works because sleep is using setTimeout during which the handlers from jQuery can be triggered. Without that it wouldn't work as JS is single-threaded, see this question. The stop handler would not be called and the loop run forever.

let done = false;
let a = 0;
let code = `while(!done){ 
await sleep(1000);
cons('step:'+a);
a++;
}`;

$('span').click(e => {
    const target = $(e.target).text()
    if (target === 'start') {
        done = false
        a = 0
        cons('start'); 
        eval("(async () => {" + code + "})()");
    }
    if (target === 'stop') {
        done = true
    }
});

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

function cons(s)
{
    $('div').html($('div').html()+ s +'<br>');
}
span { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>start</span> <span>stop</span>
<div>...<br></div>

Solution 4:[4]

Added at the request to show an example with a return variable and what is wrong with it. In order for it to work correctly, it must be inserted into each line of code in order to be able to interrupt the execution of eval at really any time, otherwise part of the code is still executed after clicking on the stop.

start stop
...
start
step:0
next:0
step:1
next:1
step:2
stop <-!!!!!
next:2 <-!!!!!

let a = 0;
let run = 0;
let code = `while(true){ 
if (run != 100) return;
cons('step:'+a);
await sleep(5000);
cons('next:'+a);
a++;
}`;

$('span').click(e => {
    if ($(e.target).text() == 'start') {
    run = 100;
  cons('start'); 
  eval("(async () => {" + code + "})()");
  }
  if ($(e.target).text() == 'stop') {
    run = 0;
    cons('stop');
  }
});

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

function cons(s)
{
    $('div').html($('div').html()+ s +'<br>');
}
span { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>start</span> <span>stop</span>
<div>...<br></div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mark d drake
Solution 2 sudip dutta
Solution 3 matthiasgiger
Solution 4 iNji 555