Skip to Main Content
  • Questions
  • Is there way to convert user defined type to JSON?

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question.

Asked: October 12, 2018 - 11:41 am UTC

Last updated: October 23, 2019 - 12:57 pm UTC

Version: 12

Viewed 10K+ times! This question is

You Asked

Is there way to convert user defined types/collections to JSON (the same way as it's possible to convert them to XML via XMLTYPE)?

and Chris said...

I'm not aware of an inbuilt way to do this. One way you could approach is to (from 12.2):

- Convert the type to XML
- Pass this into XMLTable. Convert the element tags to an attribute name column. And their contents to a value column.
- Use json_objectagg to combine all the rows into a single document

Which gives something like:

create or replace type user_type as object (
  attr1 varchar2(100),
  attr2 varchar2(100)
);
/

select *
from   xmltable (
  '/USER_TYPE/*'
  passing xmltype ( user_type ( 'val1', 'val2' ) )
  columns 
    att varchar2(100) path './name()',
    val varchar2(100) path './text()'
);

ATT     VAL    
ATTR1   val1   
ATTR2   val2  

select json_objectagg (
         att value val
       )
from   xmltable (
  '/USER_TYPE/*'
  passing xmltype ( user_type ( 'val1', 'val2' ) )
  columns 
    att varchar2(100) path './name()',
    val varchar2(100) path './text()'
);

JSON_OBJECTAGG(ATTVALUEVAL)       
{"ATTR1":"val1","ATTR2":"val2"} 


UPDATE 23 Oct 2019: 19c added functionality to automatically map user objects to JSON and back again.

create or replace type user_type as object (
  attr1 varchar2(100),
  attr2 varchar2(100)
);
/

with obj as (
  select user_type ( 'this', 'that' ) o from dual
)
  select json_object ( o ) from obj;

JSON_OBJECT(O)                    
{"ATTR1":"this","ATTR2":"that"}  
  
create or replace type user_type_tab
  is table of user_type;
/

with obj as (
  select user_type_tab (
    user_type ( 'this', 'that' ),
    user_type ( 'another this', 'another that' )
  ) o 
  from dual
)
  select json_object ( o ) from obj;

JSON_OBJECT(O)                                                                      
[{"ATTR1":"this","ATTR2":"that"},{"ATTR1":"another this","ATTR2":"another that"}] 

Rating

  (5 ratings)

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

Comments

A reader, October 15, 2018 - 10:57 am UTC

Yeah, but then you'd still need to know the type attributes. I tested and this solution works - http://www.armbruster-it.org/index.php/12-it/pl-sql/12-oracle-xml-and-json-goodies I thought maybe there's something similar already built into the Oracle.
Chris Saxon
October 15, 2018 - 12:30 pm UTC

Only if you have nested tables. For plain types you can add extra attrs just fine:

create or replace type user_type as object (
  attr1     varchar2(100),
  attr2     varchar2(100),
  add_extra varchar2(100),
  attrs     varchar2(100)
);
/

select json_objectagg (
         att value val
       )
from   xmltable (
  '/USER_TYPE/*'
  passing xmltype ( user_type ( 'val1', 'val2', 'new', 'newer' ) )
  columns 
    att varchar2(100) path './name()',
    val varchar2(100) path './text()'
);

JSON_OBJECTAGG(ATTVALUEVAL)                                         
{"ATTR1":"val1","ATTR2":"val2","ADD_EXTRA":"new","ATTRS":"newer"} 

using JSON generation function in Oracle 12.2

Rajeshwaran Jeyabal, October 15, 2018 - 2:03 pm UTC

Team,

As mentioned in the above blog, do we still need to convert data to XML and then to JSON structure, rather than using the JSON generation function like this?

Kindly advice.

demo@ORA12C> select * from ex01_person_tb;

        ID NAME
---------- --------------------
         1 Roger Waters
         2 David Gilmour

demo@ORA12C> select * from ex01_phone_tb;

        ID  PERSON_ID PHONE_NUMBER
---------- ---------- --------------------------------------------------
         1          1 543 454433
         2          1 512 4776443
         3          1 521 6454423
         4          2 212 8332464
         5          2 312 6736423

demo@ORA12C> select json_query( json_object( 'ROWSET' value json_arrayagg( json_object( 'id' value t1.id ,
  2    'name' value t1.name,
  3    'phone_numbers' value t2.txt )  ) ) ,'$' pretty )
  4  from ex01_person_tb t1, (  select person_id,
  5            json_arrayagg ( json_object('id' value id ,
  6                  'phone_number' value phone_number) ) txt
  7          from ex01_phone_tb
  8          group by person_id ) t2
  9  where t1.id = t2.person_id
 10  /

JSON_QUERY(JSON_OBJECT('ROWSET'VALUEJSON_ARRAYAGG(JSON_OBJECT('ID'VALUET1.ID,'NAME'VALUET1.NAME,'PHONE_NUMBERS'VALUET2.TXT))),'$'PR
-----------------------------------------------------------------------------------------------------------------------------------
{
  "ROWSET" :
  [
    {
      "id" : 1,
      "name" : "Roger Waters",
      "phone_numbers" :
      [
        {
          "id" : 1,
          "phone_number" : "543 454433"
        },
        {
          "id" : 3,
          "phone_number" : "521 6454423"
        },
        {
          "id" : 2,
          "phone_number" : "512 4776443"
        }
      ]
    },
    {
      "id" : 2,
      "name" : "David Gilmour",
      "phone_numbers" :
      [
        {
          "id" : 4,
          "phone_number" : "212 8332464"
        },
        {
          "id" : 5,
          "phone_number" : "312 6736423"
        }
      ]
    }
  ]
}


demo@ORA12C>


Connor McDonald
October 16, 2018 - 2:11 am UTC

Yes you could - after converting the nested table structures to relational

A reader, October 16, 2018 - 1:52 pm UTC

Sorry, I wasn't clear enough. Here's the XMLTYPE demo. Would be nice to have the same functionality with JSON.


CREATE OR REPLACE TYPE tt_address AS OBJECT
(
address VARCHAR2(100)
)
/

CREATE OR REPLACE TYPE tt_addresses_arr_typ AS TABLE OF tt_address
/

CREATE OR REPLACE TYPE tt_person_base AS OBJECT
(
NAME VARCHAR2(100)
)
NOT FINAL
/

CREATE OR REPLACE TYPE tt_person UNDER tt_person_base
(
addresses tt_addresses_arr_typ
)
/

CREATE OR REPLACE TYPE tt_person1 UNDER tt_person_base
(
name2 varchar2(100)
)
/


-- then I can do this
SELECT CURSOR (SELECT tt_person(NAME => 'name'
,addresses => tt_addresses_arr_typ(tt_address(address => 'address1')
,tt_address(address => 'address2')))
FROM dual)
FROM dual
/

-- and convert it to XML
SELECT xmltype(CURSOR (SELECT tt_person(NAME => 'name'
,addresses => tt_addresses_arr_typ(tt_address(address => 'address1')
,tt_address(address => 'address2')))
FROM dual))
FROM dual /

-- or like this
DECLARE
l_person tt_person;
l_xml xmltype;
BEGIN
l_person := tt_person(NAME => 'name'
,addresses => tt_addresses_arr_typ(tt_address(address => 'address1')
,tt_address(address => 'address2')));
l_xml := xmltype(l_person);
dbms_output.put_line(l_xml.getclobval);
END;
/


-- or by using EXECUTE IMMEDIATE
DECLARE
l_person tt_person;
l_xml xmltype;
BEGIN
l_person := tt_person(NAME => 'name'
,addresses => tt_addresses_arr_typ(tt_address(address => 'address1')
,tt_address(address => 'address2')));
EXECUTE IMMEDIATE 'begin :l_xml := xmltype(:l_person); end;'
USING OUT l_xml, IN l_person;
dbms_output.put_line(l_xml.getclobval);
END;
/


-- and finally like this, so that it works with any tt_person_base object
-- this is what's currently missing in Oracle's JSON implementation
DECLARE
l_person tt_person;
l_person1 tt_person1;

FUNCTION to_xml(pi_type IN tt_person_base) RETURN xmltype IS
l_xml xmltype;
BEGIN
EXECUTE IMMEDIATE 'begin :xml := xmltype(:pi_type); end;'
USING OUT l_xml, IN pi_type;
RETURN l_xml;
END to_xml;

BEGIN
l_person := tt_person(NAME => 'name'
,addresses => tt_addresses_arr_typ(tt_address(address => 'address1')
,tt_address(address => 'address2')));
dbms_output.put_line(to_xml(l_person).getclobval);

l_person1 := tt_person1(NAME => 'name', name2 => 'name2');
dbms_output.put_line(to_xml(l_person1).getclobval);
END;
/

Chris Saxon
October 16, 2018 - 3:45 pm UTC

Yep, there's no easy way to do this I know of. Someone with better XML skills than me may be able to come up with an XML -> JSON converter.

Or you could always write your own getJSON method function.

If you want in-built functionality, add it to the Database Ideas forum to gather community support:

https://community.oracle.com/community/technology_network_community/database/database-ideas

A reader, October 16, 2018 - 1:55 pm UTC

Oh, there's no need to use execute immediate :)

-- and finally like this, so that it works with any tt_person_base object
-- this is what's currently missing in Oracle's JSON implementation
DECLARE
l_person tt_person;
l_person1 tt_person1;

FUNCTION to_xml(pi_type IN tt_person_base) RETURN xmltype IS
l_xml xmltype;
BEGIN
l_xml := xmltype(pI_type);
RETURN l_xml;
END to_xml;

BEGIN
l_person := tt_person(NAME => 'name'
,addresses => tt_addresses_arr_typ(tt_address(address => 'address1')
,tt_address(address => 'address2')));
dbms_output.put_line(to_xml(l_person).getclobval);

l_person1 := tt_person1(NAME => 'name', name2 => 'name2');
dbms_output.put_line(to_xml(l_person1).getclobval);
END;
/




Chris Saxon
October 16, 2018 - 3:45 pm UTC

Indeed not ;)

Another option?

Dayalan Govender, October 23, 2019 - 9:54 am UTC

Been making use of the APEX_JSON.WRITE for a while, works for me (Still involves converting type->XML->JSON)

FUNCTION get_mytype_json (
l_mytype mytype)
RETURN CLOB
AS
l_xml XMLTYPE;
l_json_output CLOB;
BEGIN
l_xml := xmltype.createXML (l_mytype);

APEX_JSON.initialize_clob_output;
APEX_JSON.write (l_xml);
l_json_output := APEX_JSON.get_clob_output;

APEX_JSON.free_output;

RETURN l_json_output;
END get_mytype_json ;

Chris Saxon
October 23, 2019 - 12:55 pm UTC

Thanks for sharing. Worth noting that 19c has added the ability to map UDTs -> JSON (and back):

create or replace type user_type as object (
  attr1 varchar2(100),
  attr2 varchar2(100)
);
/

with obj as (
  select user_type ( 'this', 'that' ) o from dual
)
  select json_object ( o ) from obj;

JSON_OBJECT(O)                    
{"ATTR1":"this","ATTR2":"that"}  
  
create or replace type user_type_tab
  is table of user_type;
/

with obj as (
  select user_type_tab (
    user_type ( 'this', 'that' ),
    user_type ( 'another this', 'another that' )
  ) o 
  from dual
)
  select json_object ( o ) from obj;

JSON_OBJECT(O)                                                                      
[{"ATTR1":"this","ATTR2":"that"},{"ATTR1":"another this","ATTR2":"another that"}]  

More to Explore

SQL

The Oracle documentation contains a complete SQL reference.