Relational to JSON With APEX_JSON
APEX_JSON was primarily intended to be used by APEX developers — but there are some hooks that can allow it to be used as a standalone PL/SQL package.
Join the DZone community and get the full member experience.
Join For FreeAPEX_JSON is a PL/SQL API included with Oracle Application Express (APEX) 5.0 that provides utilities for parsing and generating JSON. While APEX_JSON was primarily intended to be used by APEX developers, there are some hooks that can allow it to be used as a standalone PL/SQL package.
Solution
The following solution uses APEX_JSON to create the JSON that represents a department in the HR schema. APEX_JSON basically writes JSON content to a buffer. By default, the buffer used is the HTP buffer in the database, as that's what APEX reads. But as you can see with line 27, it's possible to redirect the output to a CLOB buffer instead. Once we've redirected the output, we can make API calls to open/close objects and arrays and write values to them. When we're done writing out the JSON we can make a call to get_clob_output to get the JSON contents. I've highlighted some of the relevant lines...
create or replace function get_dept_apex_json(
p_dept_id in departments.department_id%type
)
return clob
is
cursor manager_cur (
p_manager_id in employees.employee_id%type
)
is
select *
from employees
where employee_id = manager_cur.p_manager_id;
l_date_format constant varchar2(20) := 'DD-MON-YYYY';
l_dept_rec departments%rowtype;
l_dept_json_clob clob;
l_loc_rec locations%rowtype;
l_country_rec countries%rowtype;
l_manager_rec manager_cur%rowtype;
l_job_rec jobs%rowtype;
begin
apex_json.initialize_clob_output;
select *
into l_dept_rec
from departments
where department_id = get_dept_apex_json.p_dept_id;
apex_json.open_object(); --department
apex_json.write('id', l_dept_rec.department_id);
apex_json.write('name', l_dept_rec.department_name);
select *
into l_loc_rec
from locations
where location_id = l_dept_rec.location_id;
apex_json.open_object('location');
apex_json.write('id', l_loc_rec.location_id);
apex_json.write('streetAddress', l_loc_rec.street_address);
apex_json.write('postalCode', l_loc_rec.postal_code);
select *
into l_country_rec
from countries cou
where cou.country_id = l_loc_rec.country_id;
apex_json.open_object('country');
apex_json.write('id', l_country_rec.country_id);
apex_json.write('name', l_country_rec.country_name);
apex_json.write('regionId', l_country_rec.region_id);
apex_json.close_object(); --country
apex_json.close_object(); --location
open manager_cur(l_dept_rec.manager_id);
fetch manager_cur into l_manager_rec;
if manager_cur%found
then
apex_json.open_object('manager');
apex_json.write('id', l_manager_rec.employee_id);
apex_json.write('name', l_manager_rec.first_name || ' ' || l_manager_rec.last_name);
apex_json.write('salary', l_manager_rec.salary);
select *
into l_job_rec
from jobs job
where job.job_id = l_manager_rec.job_id;
apex_json.open_object('job');
apex_json.write('id', l_job_rec.job_id);
apex_json.write('title', l_job_rec.job_title);
apex_json.write('minSalary', l_job_rec.min_salary);
apex_json.write('maxSalary', l_job_rec.max_salary);
apex_json.close_object(); --job
apex_json.close_object(); --manager
else
apex_json.write('manager', '', p_write_null => true);
end if;
close manager_cur;
apex_json.open_array('employees');
for emp_rec in (
select *
from employees
where department_id = l_dept_rec.department_id
)
loop
apex_json.open_object(); --employee
apex_json.write('id', emp_rec.employee_id);
apex_json.write('name', emp_rec.first_name || ' ' || emp_rec.last_name);
apex_json.write('isSenior', emp_rec.hire_date < to_date('01-jan-2005', 'dd-mon-yyyy'));
apex_json.write('commissionPct', emp_rec.commission_pct, p_write_null => true);
apex_json.open_array('jobHistory');
for jh_rec in (
select job_id,
department_id,
start_date,
end_date
from job_history
where employee_id = emp_rec.employee_id
)
loop
apex_json.open_object(); --job
apex_json.write('id', jh_rec.job_id);
apex_json.write('departmentId', jh_rec.department_id);
apex_json.write('startDate', to_char(jh_rec.start_date, l_date_format));
apex_json.write('endDate', to_char(jh_rec.end_date, l_date_format));
apex_json.close_object(); --job
end loop;
apex_json.close_array(); --jobHistory
apex_json.close_object(); --employee
end loop;
apex_json.close_array(); --employees
apex_json.close_object(); --department
l_dept_json_clob := apex_json.get_clob_output;
apex_json.free_output;
return l_dept_json_clob;
exception
when others
then
if manager_cur%isopen
then
close manager_cur;
end if;
raise;
end get_dept_apex_json;
Output
When passed a departmentId of 10 , the function returns a CLOB populated with JSON that matches the goal 100%.
{
"id": 10,
"name": "Administration",
"location": {
"id": 1700,
"streetAddress": "2004 Charade Rd",
"postalCode": "98199",
"country": {
"id": "US",
"name": "United States of America",
"regionId": 2
}
},
"manager": {
"id": 200,
"name": "Jennifer Whalen",
"salary": 4400,
"job": {
"id": "AD_ASST",
"title": "Administration Assistant",
"minSalary": 3000,
"maxSalary": 6000
}
},
"employees": [
{
"id": 200,
"name": "Jennifer Whalen",
"isSenior": true,
"commissionPct": null,
"jobHistory": [
{
"id": "AD_ASST",
"departmentId": 90,
"startDate": "17-SEP-1995",
"endDate": "17-JUN-2001"
},
{
"id": "AC_ACCOUNT",
"departmentId": 90,
"startDate": "01-JUL-2002",
"endDate": "31-DEC-2006"
}
]
}
]
}
Summary
I really enjoyed working with APEX_JSON — it's my new "go-to" for PL/SQL based JSON generation. APEX_JSON has a very light footprint (it's just a single package) and it takes a minimalistic approach. Rather than compose objects as one would do with PL/JSON, you simply use the package to write JSON to a buffer.
This approach yields some performance benefits, as well. In a basic test where I generated the JSON for every department in the HR schema 100 times in a loop, the APEX_JSON-based solution finished at around 3.5 seconds whereas the PL/JSON based solution took around 17 seconds. That means APEX_JSON about 3.8 times faster than PL/JSON when it comes generating JSON and converting it to a CLOB.
Unfortunately, APEX_JSON is only included with APEX 5.0+. Upgrading your database's APEX instance seems a little extreme if all you want to do is work with JSON (though it is free and doesn't take too long), but if you already have APEX 5.0, then it's a very nice tool to be able to leverage.
Published at DZone with permission of Dan McGhan. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments