Some time ago I noticed this blog from Dan McGhan. In that blog he compares several ways to generate JSON from relational data in a Oracle Database.
I had some spare time, so I tried my own JSON generator, build around 3 nested Oracle types, on the examples he used.
I had no problem generating the exact output he wanted, but the performance was a bit disappointing (almost the same as the PL/JSON solution).
But, as I had some spare time, I started trying to improve the performance. But after a few tries it turned out that I was lucky to get a result at all.
My JSON generator had a gigantic memory leak. I not sure if it’s because of Oracle’s implementation of the nested types I used
create or replace type jd authid current_user as object ( json_type varchar2(1 char) ) not final; / create or replace type ja as table of jd; / create or replace type jv authid current_user under jd ( .... )
but using this script is enough to crash my 12.1.02.0 database
declare x jv; begin for i in 1 .. 12 loop x := jv( 'a', jv( 'b', jv( 'x' ) ) , 'a', jv( 'b', jv( 'x' ) ) ); end loop; end; /
Anyway, I had some spare time and turned my JSON generator in a one package implementation. And using that package I can create the same JSON as Dan is creating with PL/JSON and APEX_JSON.
But with performance a bit better than the APEX_JSON solution.
declare -- function get_dept_as_json( p_dept_id number ) return clob is cursor c_dept is select d.department_id , d.department_name , l.location_id , l.street_address , l.postal_code , c.country_id , c.country_name , c.region_id , m.employee_id , m.first_name || ' ' || m.last_name manager_name , m.salary , j.job_id , j.job_title , j.min_salary , j.max_salary from departments d , locations l , countries c , employees m , jobs j where d.department_id = p_dept_id and l.location_id = d.location_id and c.country_id = l.country_id and m.employee_id (+) = d.manager_id and j.job_id (+) = m.job_id; r_dept c_dept%rowtype; l_jv as_json.tp_json_value; l_emps as_json.tp_json_value; l_hist as_json.tp_json_value; l_date_format varchar2(20) := 'DD-MON-YYYY'; l_rv clob; begin open c_dept; fetch c_dept into r_dept; close c_dept; -- dbms_lob.createtemporary( l_rv, true, dbms_lob.call ); l_jv := as_json.json( 'id', as_json.jv( r_dept.department_id ) , 'name', as_json.jv( r_dept.department_name ) , 'location', as_json.json( 'id', as_json.jv( r_dept.location_id ) , 'streetAddress', as_json.jv( r_dept.street_address ) , 'postalCode', as_json.jv( r_dept.postal_code ) , 'country', as_json.json( 'id', as_json.jv( r_dept.country_id ) , 'name', as_json.jv( r_dept.country_name ) , 'regionId', as_json.jv( r_dept.region_id ) ) ) , 'manager', as_json.json( 'id', as_json.jv( r_dept.employee_id ) , 'name', as_json.jv( r_dept.manager_name ) , 'salary', as_json.jv( r_dept.salary ) , 'job', as_json.json( 'id', as_json.jv( r_dept.job_id ) , 'title', as_json.jv( r_dept.job_title ) , 'minSalary', as_json.jv( r_dept.min_salary ) , 'maxSalary', as_json.jv( r_dept.max_salary ) ) ) ); -- for r_emp in ( select e.employee_id , e.first_name || ' ' || e.last_name name , e.hire_date , e.commission_pct from employees e where e.department_id = r_dept.department_id ) loop l_hist := null; for r_hist in ( select h.job_id , h.department_id , h.start_date , h.end_date from job_history h where h.employee_id = r_emp.employee_id ) loop l_hist := as_json.add_item( l_hist , as_json.json( 'id', as_json.jv( r_hist.job_id ) , 'departmentId', as_json.jv( r_hist.department_id ) , 'startDate', as_json.jv( r_hist.start_date, l_date_format ) , 'endDate', as_json.jv( r_hist.end_date, l_date_format ) ) ); end loop; l_emps := as_json.add_item( l_emps , as_json.json( 'id', as_json.jv( r_emp.employee_id ) , 'name', as_json.jv( r_emp.name ) , 'isSenior', as_json.jv( r_emp.hire_date < to_date( '01-jan-2005', 'dd-mon-yyyy' ) ) , 'commissionPct', as_json.jv( r_emp.commission_pct ) , 'jobHistory', l_hist ) ); end loop; as_json.add_member( l_jv, 'employees', l_emps ); -- l_rv := as_json.stringify( l_jv ); as_json.free; return l_rv; end; begin dbms_output.put_line( get_dept_as_json( 10 ) ); end;
Anton
Hi Anton,
Thanks for the package.
For me it is very useful as the apex_json package is giving me some problems on a 12.1 database and the jsontable functionality is not yet available on 12.1 for creating json.
However I ran into a small issue.
When I use de json_ec function it raises a too many open cursors error at line 1521.
This is happening because the rc ref cursor isn’t closed in the dynamic sql code.
If you add ” close rc;” after the loop statement at line 1530, it is solved.
regards,
Mathieu
Hello Anton,
Thanks very much for this package.
It is very useful when you have a database version older than 12.2.
As I ran into problems with the apex_json package
There is a bug I ran into when I used the json_ec function:
“ORA-01000: maximum open cursors exceeded at line 1524″.
The code will execute some fetching of a ref cursor using execute immediate.
But the ref cursor isn’t closed afterwards.
So add the line ” close rc;” after the loop at line 1530 and it is solved.
regards,
Mathieu
Hi I’m very happy with this resource as_json but I cant update or delete the value of existing member :(.
Someone knows how do it??
Hi Anton,
thank you for the package. I’m finding it invaluable. However, when I have a number less than 1, that is already represented as JSON, such as the ‘Pitch’ value in the JSON below:
{“Item #”:”T9070360″,”d1″:”#4″,”Pitch”: 0.66,”Limit”:”Z3″,”l1″:50,”l2″:17,”l3″:null,”d2″:3.55,”sq”:2.8,”l4″:5,”z”:3,”Drill Ø”:3}
when I add it to a larger object as a member (using:
as_json.add_member(l_jp,’items’, AS_JSON.json(l_ritem)); //the l_ritem contains the item metadata object above
)
, as part of a larger string, then the number loses it’s leading 0:
“itemMetadata”:{“Item #”:”T9070360″,”d1″:”#4″,”Pitch”:.66,”Limit”:”Z3″,”l1″:50,”l2″:17,”l3″:null,”d2″:3.55,”sq”:2.8,”l4″:5,”z”:3,”Drill \u00D8″:3}
I originally wrote a simple function to create the correct JSON number identifier and I had to modify my to_char to fix it on the original string. I hoped that would be the issue. However I can’t find the equivalent in the as_json package as I think it will be a similar problem?
Thanks for your help,
Regards,
Steve
And then I noticed the leading space… I guess the clue was in the line break added in the post above. However as_json still has the same problem, even when the leading space is removed for +ve decimals < 1
Thanks
I fixed the issue, see version 12, https://technology.amis.nl/wp-content/uploads/2015/12/as_json12.txt
Thanks Anton,
It now works as expected.
Best Regards,
Steve
Hi Anton
Thank you for your package AS_JSON which offers valuable insights. I am struggling with a similar task since a couple of days, but my select statements to create the json data contain sub-cursors embedded with the cursor function, as in
select d.*
,cursor (
select e.*
from emp e
where e.deptno = d.deptno
) employee
from dept d ;
Such columns can be described by dbms_sql.describe_columns3, they yield a col_type of 102. I didn’t find a branch processing this very col_type in AS_JSON, but I must confess that I don’t know of any way manipulating columns with type cursor with dbms_sql, as well. According to Tom Kyte, there actually is no way (or was no way in 20123).
On the other hand, DBMS_XMLQUERY, DBMS_XMLGEN and APEX_JSON somehow do the magic. DBMS_XMLQUERY maybee uses Java, but I am pretty sure DBMS_XMLGEN and APEX_JSON don’t. Can you imagine how this can be done ?
Regards
Pascal
According to the documentation apex_json uses xmltype to do that magic, https://docs.oracle.com/cd/E59726_01/doc.50/e39149/apex_json.htm#CHDHHHHH
as_json has a parser for xmltype, so you can use that same trick, as_json.json( xmltype( your_cursor ) )
I will publish a new version of as_json this week. That version will have code to parse a refcursor with embedded cursor.
Thank you for the hint to the documentation.
I wasn’t aware of the xmltype constructor recieving a refcursor before.
And of course, I am looking forward to the new version of as_json. Where will we find it ?
In have updated the links in this blog, https://technology.amis.nl/wp-content/uploads/2015/12/as_json11.txt