Skip to Main Content
  • Questions
  • Is there any way to convert an complex object type to JSON in 12c

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, evance.

Asked: November 16, 2016 - 5:11 am UTC

Last updated: December 13, 2016 - 3:25 am UTC

Version: 12.1.0.2.0

Viewed 1000+ times

You Asked

Hello Tom

We have a procedure which has an complex object type as output (we didn use Refcursor as we had lot of reusable codes in case of using object).
This data is populated in Application layer for display and the same is moved back to a JSON store (ElasticSearch) for visualization. Is there any option in Oracle 12c similar to PL/JSON, were we could convert the object along with data into a JSON object? I went through the whole of documentation https://docs.oracle.com/database/121/ADXDB/json.htm#ADXDB6257 but was unable to find a solution for the same.

Sample Object structure
create or replace
TYPE t_obj_evnt
IS
  OBJECT
  (
    /* Event Details */
    evnt_id         NUMBER,        
    evnt_dt         DATE,          
    evnt_stat_cd    VARCHAR2(10),  
    user_id         VARCHAR2(18),  
    user_role_cd    VARCHAR2(10),  
    
    /* Application */
    appl_serl_num   VARCHAR2(20), 
    appl_regn_num   VARCHAR2(20), 
    appl_mdl_cd     VARCHAR2(20), 
    appl_mdl_nm     VARCHAR2(100), 
    appl_typ_cd     VARCHAR2(20), 
    appl_typ_nm     VARCHAR2(100),

    /* Application Utilization */
    appl_util           t_obj_util,
    
    /* One to Many data of the event */
    evnt_rpt_doc      t_tbl_rpt_doc,   
    evnt_symp_or_rsn  t_tbl_symp_or_rsn,  
    evnt_cmnt         t_tbl_cmnt,      
    evnt_envrmt       t_tbl_envrmt,    
    evnt_efct         t_tbl_efct,      
    evnt_oper_mode    t_tbl_oper_mode, 
    
    /* Engine Details */
     engn_dtl          t_obj_evnt_engn, 
    
    /* Part Details */
    part_dtl          t_tbl_evnt_part, 
    CONSTRUCTOR FUNCTION t_obj_evnt RETURN SELF AS RESULT 
  );

CREATE OR REPLACE TYPE t_tbl_evnt IS TABLE OF t_obj_evnt;

    PROCEDURE get_pend_evnt_sp(
            po_tbl_pend_evnt_list         OUT   t_tbl_evnt,
            pi_var_es_nm                  IN    VARCHAR2 DEFAULT NULL,
            pi_num_es_jvm_load            IN    NUMBER DEFAULT NULL,
            pi_num_es_cpu_load            IN    NUMBER DEFAULT NULL           
        ); 

and Connor said...

I dont think there is anything natively that does it. The closest I can think of would be:

1) convert object type to xmltype
2) convert xmltype to json

Here's an example of that

SQL> create or replace
  2  type myobj as object
  3    ( a int,
  4      b varchar2(10)
  5    );
  6  /

Type created.

SQL>
SQL> create or replace
  2  type myobj_nt as table of myobj;
  3  /

Type created.

SQL>
SQL> select
  2    myobj_nt(
  3      myobj(1,'Connor'),
  4      myobj(2,'McDonald')
  5   )
  6  from dual;

MYOBJ_NT(MYOBJ(1,'CONNOR'),MYOBJ(2,'MCDONALD'))(A, B)
-------------------------------------------------------
MYOBJ_NT(MYOBJ(1, 'Connor'), MYOBJ(2, 'McDonald'))


So that is our source data. We now convert to XML

SQL> create or replace
  2  type  wrapper as object (
  3    m myobj_nt);
  4  /

Type created.

SQL>
SQL> select
  2    xmltype
  3    ( wrapper(
  4    myobj_nt(
  5      myobj(1,'Connor'),
  6      myobj(2,'McDonald')
  7   )
  8   ))
  9  from dual;

XMLTYPE(WRAPPER(MYOBJ_NT(MYOBJ(1,'CONNOR'),MYOBJ(2,'MCDONALD'))))
------------------------------------------------------------------------------------------------------
<WRAPPER><M><MYOBJ><A>1</A><B>Connor</B></MYOBJ><MYOBJ><A>2</A><B>McDonald</B></MYOBJ></M></WRAPPER>


and now we can convert to JSON

SQL> set serverout on
SQL> declare
  2    l_xml xmltype;
  3  begin
  4
  5    select
  6      xmltype
  7      ( wrapper(
  8      myobj_nt(
  9        myobj(1,'Connor'),
 10        myobj(2,'McDonald')
 11     )
 12     ))
 13    into l_xml
 14    from dual;
 15
 16    apex_json.initialize_clob_output;
 17    apex_json.write(l_xml);
 18    dbms_output.put_line(apex_json.get_clob_output);
 19    apex_json.free_output;
 20  end;
 21  /
{"M":[{"A":1,"B":"Connor"},{"A":2,"B":"McDonald"}]}


PL/SQL procedure successfully completed.


Rating

  (5 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Thanks.

evance Heally, November 16, 2016 - 8:47 am UTC

Thanks Connor. But since APEX_JSON is used, I believe, we need to have Apex 5 installed in the DB. If thats the case, I dont think we would be allowed to do such changes in any of our environment. Is there any other way that I could use without Apex?
Connor McDonald
November 17, 2016 - 2:28 am UTC

There is also an open source PL/JSON package.

http://sourceforge.net/projects/pljson/

Or you could convert from XML to JSON using a stylesheet transformation.

https://www.experts-exchange.com/articles/12232/Creating-JSON-from-Oracle-using-SYS-REFCURSOR.html

I haven't tested either - so use at your own risk

apex_json

Rajeshwaran, Jeyabal, November 16, 2016 - 8:57 am UTC

Though apex_json is not available at the product doc, would you still recommend to use this approach for converting XML to JSON?

http://docs.oracle.com/database/121/ARPLS/toc.htm
Connor McDonald
November 17, 2016 - 2:25 am UTC

Beware of existing bugs

Mikhail Velikikh, November 16, 2016 - 9:17 am UTC

Hello,
ADT to XMLTYPE conversion using XMLTYPE.createXML may construct wrong XML.
Here's an example that I have just executed in 12.1.0.2.161018:
SQL> create or replace type TestMULTIPLENAME_WU as object(first_name varchar2(25))
  2  /

Type created.

SQL> 
SQL> create or replace type TestDUALNAME_WU as object(FirstName varchar2(35))
  2  /

Type created.

SQL> 
SQL> create or replace type TestNAMETYPE_WU as object(DualName TestDualName_WU, MultipleName TestMultipleName_WU)
  2  /

Type created.

SQL> 
SQL> create or replace type TestPERSONTYPE_WU as object(name TestNameType_WU)
  2  /

Type created.

SQL> 
SQL> create or replace type TestMoneyType_WU as object(MTCN varchar2(20), Sender TestPersonType_WU)
  2  /

Type created.

SQL> 
SQL> create or replace type TEST_WU as object(SendData TestMoneyType_WU)
  2  /

Type created.

SQL> 
SQL> 
SQL> declare
  2    l_typ Test_WU;
  3    l_xml xmltype;
  4    l_typ2 test_wu;
  5  begin
  6    l_typ := Test_WU(
  7      SendData => TestMoneyType_WU(
  8                    MTCN => 9620657241,
  9                    Sender => testpersontype_wu(
 10                                name => testnametype_wu(
 11                                          -- changeit
 12                                          --DualName => testDualName_WU('a')/*correct*/,
 13                                          DualName => null/*incorrect*/,
 14                                          --DualName => testDualName_wu(null)/*correct*/,
 15                                          MultipleName => testmultiplename_wu(first_name => 'test_string')
 16                                        )
 17                              )
 18                  )
 19    );
 20    dbms_output.put_line( 'before: '||l_typ.SendData.sender.name.MultipleName.first_name);
 21    l_xml := xmltype.createXML(l_typ);
 22    dbms_output.put_line( 'xml generated: '||l_xml.getStringVal());
 23    l_xml.toObject(l_typ2);
 24    dbms_output.put_line( 'after: '||l_typ2.SendData.sender.name.MultipleName.first_name);
 25  end;
 26  /
before: test_string
xml generated: <TEST_WU><SENDDATA><MTCN>9620657241</MTCN><SENDER><NAME/>
</SENDER></SENDDATA></TEST_WU>
after:

PL/SQL procedure successfully completed.

SQL> 
SQL> select xmltype(
  2           Test_WU(
  3             SendData => TestMoneyType_WU(
  4                           MTCN => 9620657241,
  5                           Sender => testpersontype_wu(
  6                                       name => testnametype_wu(
  7                                                 -- changeit
  8                                                 --DualName => testDualName_WU('a')/*correct*/,
  9                                                 DualName => null/*incorrect*/,
 10                                                 --DualName => testDualName_wu(null)/*correct*/,
 11                                                 MultipleName => testmultiplename_wu(first_name => 'test_string')
 12                                               )
 13                                     )
 14                         )
 15           )
 16         ) result
 17    from dual
 18  /

RESULT
----------------------------------------------------------------------------------------------------------------------
<TEST_WU>
  <SENDDATA>
    <MTCN>9620657241</MTCN>
    <SENDER>
      <NAME/>
    </SENDER>
  </SENDDATA>
</TEST_WU>


1 row selected.


I raised SR 3-12793891731 : ADT TO XMLTYPE CONVERSION DOES NOT WORK CORRECTLY for this issue half a year ago and this issue is further being investigated in an unpublished bug 23566226.
I hope Oracle Support will finally pay attention to this especially after the fact that the issue is still present in 12.2 and there is no other way has been provided in 12.2 to generate JSON from ADT: http://www.liberidu.com/blog/2016/10/04/oracle-database-12-2-0-1-generating-json-in-the-database-using-sql-json-operators/

Help with fixing the "&quot" in the output.

Rajeshwaran, Jeyabal, December 10, 2016 - 6:45 am UTC

demo@ORA12C> create or replace function foo
  2  return sys_refcursor as
  3    c  sys_refcursor;
  4  begin
  5     open c for select empno,ename,hiredate
  6             from scott.emp
  7             where deptno = 10;
  8     return c;
  9  end;
 10  /

Function created.

demo@ORA12C>
demo@ORA12C> CREATE OR REPLACE FUNCTION get_json_fnc(ip_rfc VARCHAR2) RETURN CLOB AS
  2          lhtmloutput   xmltype;
  3          lxsl          LONG;
  4          lxmldata      xmltype;
  5          lcontext      dbms_xmlgen.ctxhandle;
  6          l_ret_clob    CLOB;
  7          desc_cur      NUMBER;
  8          l_descr_tab   dbms_sql.desc_tab2;
  9          l_num_cols    NUMBER;
 10          l_header_clob CLOB;
 11          l_row_data    VARCHAR2(100);
 12          l_ip_rfc      SYS_REFCURSOR;
 13          l_exec_comm   VARCHAR2(250);
 14  BEGIN
 15          l_exec_comm := 'SELECT ' || ip_rfc || ' from dual';
 16
 17          EXECUTE IMMEDIATE l_exec_comm
 18                  INTO l_ip_rfc;
 19
 20          l_header_clob := '{"metadata":[';
 21          desc_cur      := dbms_sql.to_cursor_number(l_ip_rfc);
 22
 23          dbms_sql.describe_columns2(desc_cur
 24                                    ,l_num_cols
 25                                    ,l_descr_tab);
 26
 27          FOR i IN 1 .. l_num_cols
 28          LOOP
 29                  CASE
 30                          WHEN l_descr_tab(i).col_type IN (2
 31                                           ,8) THEN
 32                                  l_row_data := '{"name":"' || l_descr_tab(i)
 33                                               .col_name || '","type":"number"},';
 34                          WHEN l_descr_tab(i).col_type = 12 THEN
 35                                  l_row_data := '{"name":"' || l_descr_tab(i)
 36                                               .col_name || '","type":"date"},';
 37                          ELSE
 38                                  l_row_data := '{"name":"' || l_descr_tab(i)
 39                                               .col_name || '","type":"text"},';
 40                  END CASE;
 41                  dbms_lob.writeappend(l_header_clob
 42                                      ,length(l_row_data)
 43                                      ,l_row_data);
 44          END LOOP;
 45          l_header_clob := rtrim(l_header_clob
 46                                ,',') || '],"data":';
 47
 48          EXECUTE IMMEDIATE l_exec_comm
 49                  INTO l_ip_rfc;
 50          lcontext := dbms_xmlgen.newcontext(l_ip_rfc);
 51          dbms_xmlgen.setnullhandling(lcontext
 52                                     ,1);
 53          lxmldata := dbms_xmlgen.getxmltype(lcontext
 54                                            ,dbms_xmlgen.none);
 55          -- this is a XSL for JSON
 56          lxsl := '<?xml version="1.0" encoding="ISO-8859-1"?>
 57  <xsl:stylesheet version="1.0"
 58    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 59  <xsl:output method="html"/>
 60  <xsl:template match="/">[<xsl:for-each select="/ROWSET/*">
 61    {<xsl:for-each select="./*">
 62      "<xsl:value-of select="name()"/>":"<xsl:value-of select="text()"/>"<xsl:choose>
 63        <xsl:when test="position()!= last()">,</xsl:when>
 64      </xsl:choose>
 65     </xsl:for-each>
 66    }<xsl:choose>
 67        <xsl:when test="position() != last()">,</xsl:when>
 68      </xsl:choose>
 69     </xsl:for-each>
 70  ]}]}</xsl:template></xsl:stylesheet>';
 71
 72          lhtmloutput := lxmldata.transform(xmltype(lxsl));
 73          l_ret_clob  := lhtmloutput.getclobval();
 74          l_ret_clob  := REPLACE(l_ret_clob
 75                                ,'_x0020_'
 76                                ,' ');
 77          dbms_lob.writeappend(l_header_clob
 78                              ,length(l_ret_clob)
 79                              ,l_ret_clob);
 80          RETURN l_header_clob;
 81  EXCEPTION
 82          WHEN OTHERS THEN
 83                  dbms_output.put_line(SQLERRM);
 84                  dbms_output.put_line(dbms_utility.format_error_backtrace);
 85                  RETURN NULL;
 86  END get_json_fnc;
 87  /

Function created.

demo@ORA12C> select get_json_fnc('foo') from dual;

GET_JSON_FNC('FOO')
--------------------------------------------------------------------------------
{"metadata":[{"name":"EMPNO","type":"number"},{"name":"ENAME","type":"text"},{"n
ame":"HIREDATE","type":"date"}],"data":[
  {
    &quot;EMPNO&quot;:&quot;7782&quot;,
    &quot;ENAME&quot;:&quot;CLARK&quot;,
    &quot;HIREDATE&quot;:&quot;09-JUN-1981&quot;
  },
  {
    &quot;EMPNO&quot;:&quot;7839&quot;,
    &quot;ENAME&quot;:&quot;KING&quot;,
    &quot;HIREDATE&quot;:&quot;17-NOV-1981&quot;
  },
  {
    &quot;EMPNO&quot;:&quot;7934&quot;,
    &quot;ENAME&quot;:&quot;MILLER&quot;,
    &quot;HIREDATE&quot;:&quot;23-JAN-1982&quot;
  }
]}]}


1 row selected.

demo@ORA12C>


using the "get_json_fnc" got the output. but could you help me to fix the "&quot" in the output with double quotes(") in place?
Connor McDonald
December 13, 2016 - 3:25 am UTC

I changed this line:

l_ret_clob := dbms_xmlgen.convert(lhtmloutput.getclobval(),dbms_xmlgen.ENTITY_DECODE );


SQL> create or replace function foo
  2  return sys_refcursor as
  3    c  sys_refcursor;
  4  begin
  5     open c for select empno,ename,hiredate
  6             from scott.emp
  7             where deptno = 10;
  8     return c;
  9  end;
 10  /

Function created.

SQL>
SQL> CREATE OR REPLACE FUNCTION get_json_fnc(ip_rfc VARCHAR2) RETURN CLOB AS
  2          lhtmloutput   xmltype;
  3          lxsl          LONG;
  4          lxmldata      xmltype;
  5          lcontext      dbms_xmlgen.ctxhandle;
  6          l_ret_clob    CLOB;
  7          desc_cur      NUMBER;
  8          l_descr_tab   dbms_sql.desc_tab2;
  9          l_num_cols    NUMBER;
 10          l_header_clob CLOB;
 11          l_row_data    VARCHAR2(100);
 12          l_ip_rfc      SYS_REFCURSOR;
 13          l_exec_comm   VARCHAR2(250);
 14  BEGIN
 15          l_exec_comm := 'SELECT ' || ip_rfc || ' from dual';
 16
 17          EXECUTE IMMEDIATE l_exec_comm
 18                  INTO l_ip_rfc;
 19
 20          l_header_clob := '{"metadata":[';
 21          desc_cur      := dbms_sql.to_cursor_number(l_ip_rfc);
 22
 23          dbms_sql.describe_columns2(desc_cur
 24                                    ,l_num_cols
 25                                    ,l_descr_tab);
 26
 27          FOR i IN 1 .. l_num_cols
 28          LOOP
 29                  CASE
 30                          WHEN l_descr_tab(i).col_type IN (2
 31                                           ,8) THEN
 32                                  l_row_data := '{"name":"' || l_descr_tab(i)
 33                                               .col_name || '","type":"number"},';
 34                          WHEN l_descr_tab(i).col_type = 12 THEN
 35                                  l_row_data := '{"name":"' || l_descr_tab(i)
 36                                               .col_name || '","type":"date"},';
 37                          ELSE
 38                                  l_row_data := '{"name":"' || l_descr_tab(i)
 39                                               .col_name || '","type":"text"},';
 40                  END CASE;
 41                  dbms_lob.writeappend(l_header_clob
 42                                      ,length(l_row_data)
 43                                      ,l_row_data);
 44          END LOOP;
 45          l_header_clob := rtrim(l_header_clob
 46                                ,',') || '],"data":';
 47
 48          EXECUTE IMMEDIATE l_exec_comm
 49                  INTO l_ip_rfc;
 50          lcontext := dbms_xmlgen.newcontext(l_ip_rfc);
 51          dbms_xmlgen.setnullhandling(lcontext
 52                                     ,1);
 53          lxmldata := dbms_xmlgen.getxmltype(lcontext,dbms_xmlgen.none);
 54
 55          -- this is a XSL for JSON
 56          lxsl := '<?xml version="1.0" encoding="ISO-8859-1"?>
 57  <xsl:stylesheet version="1.0"
 58    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 59  <xsl:output method="html"/>
 60  <xsl:template match="/">[<xsl:for-each select="/ROWSET/*">
 61    {<xsl:for-each select="./*">
 62      "<xsl:value-of select="name()"/>":"<xsl:value-of select="text()"/>"<xsl:choose>
 63        <xsl:when test="position()!= last()">,</xsl:when>
 64      </xsl:choose>
 65     </xsl:for-each>
 66    }<xsl:choose>
 67        <xsl:when test="position() != last()">,</xsl:when>
 68      </xsl:choose>
 69     </xsl:for-each>
 70  ]}]}</xsl:template></xsl:stylesheet>';
 71
 72          lhtmloutput := lxmldata.transform(xmltype(lxsl));
 73          l_ret_clob  := dbms_xmlgen.convert(lhtmloutput.getclobval(),dbms_xmlgen.ENTITY_DECODE  );
 74          l_ret_clob  := REPLACE(l_ret_clob
 75                                ,'_x0020_'
 76                                ,' ');
 77          dbms_lob.writeappend(l_header_clob
 78                              ,length(l_ret_clob)
 79                              ,l_ret_clob);
 80          RETURN l_header_clob;
 81  EXCEPTION
 82          WHEN OTHERS THEN
 83                  dbms_output.put_line(SQLERRM);
 84                  dbms_output.put_line(dbms_utility.format_error_backtrace);
 85                  RETURN NULL;
 86  END get_json_fnc;
 87  /

Function created.

SQL> sho err
No errors.
SQL>
SQL> set long 50000
SQL> select get_json_fnc('foo') from dual;

GET_JSON_FNC('FOO')
--------------------------------------------------------------------------------
{"metadata":[{"name":"EMPNO","type":"number"},{"name":"ENAME","type":"text"},{"n
ame":"HIREDATE","type":"date"}],"data":[
  {
    "EMPNO":"7782",
    "ENAME":"CLARK",
    "HIREDATE":"09-JUN-81"
  },
  {
    "EMPNO":"7839",
    "ENAME":"KING",
    "HIREDATE":"17-NOV-81"
  },
  {
    "EMPNO":"7934",
    "ENAME":"MILLER",
    "HIREDATE":"23-JAN-82"
  }
]}]}


1 row selected.

SQL>
SQL>


Top 12 things for DBA (12.2)

Rajeshwaran, Jeyabal, December 10, 2016 - 7:10 am UTC

Connor,

with 12.2 is available on cloud and Chris has posted his Top 12 things for Developers.

https://blogs.oracle.com/sql/entry/12_things_developers_will_love

could you upload your slides for Top 12 things for DBA from OOW.


Connor McDonald
December 12, 2016 - 1:18 am UTC

They'll be up there shortly - we're only just back from UKOUG.

More to Explore

CLOB/BLOB/etc

Complete documentation on Securefiles and Large Objects here