0

Using Oracle 12.2

Goal: Convert XML data in a CLOB to JSON

Desire: Use built in functionality of Oracle (does it even exist?)

I'm under the impression that Oracle CAN convert the XML to JSON on it's own. However, I am unable to find a straight forward answer or example in SO or Google and have never done something like this so I'm really uncertain how to proceed.

In Python it is 2 lines of code but am trying to put this into a Procedure on Oracle.

I don't have any XSLT files. I say files because looking at some of the historical data in the system I'm seeing 4 different formats of XML used and stored.

Need to pull all of the XML data and push as JSON to another table for testing.

Tried making sense from:

Any information would be greatly appreciated.

Updated sample xml to json

<elementA>
  <firstName>snoopy</firstName>
  <lastName>brown</lastName>
  <favoriteNumbers>
    <value>1</value>
    <value>2</value>
    <value>3</value>
  </favoriteNumbers>
</elementA>

Expect direct translation

{
  elementA:{
    firstName:'snoopy',
    lastName:'brown'
    favoriteNumbers:{
      value:[1,3,4]
    ]}
  }
}
edjm
  • 4,830
  • 7
  • 36
  • 65
  • Please [edit] the question with a [MRE] including: the XML files you want to convert; and the expected output. If you have XML with a root element and a single depth of tags below that then this question is a duplicate of [How can i convert xml to json in oracle?](https://stackoverflow.com/a/44673911/1509264). If you are asking for arbitrary XML with nested tags of arbitrary depths then the answer is probably going to be that it is not possible; but there may be edge cases where it is possible if you want a specific JSON format for the output. – MT0 May 05 '23 at 13:35
  • There are 4 formats that I can see stored in the database table column ranging from e.g. – edjm May 05 '23 at 13:59
  • As already said in the answer, SQL is built for fixed structures and a parser has to know the structure of result set to validate the statement. PL/SQL is also statically typed language, that doesn't allow you to deserialize objects of dynamic structure. However, Oracle has [`dbms_xmldom`](https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_XMLDOM.html#GUID-140D1569-48E6-4845-8568-964CC295263C) package that allows you to get the structure of your XML input. It also has `json_element_t` type with all required subtypes to build JSON dynamically. – astentx May 05 '23 at 19:53
  • Combining both of them will allow you to build dynamic JSON from arbitrary XML input, however implementation of all possible cases may require too much development effort, especially to account multiple namespaces – astentx May 05 '23 at 19:54

2 Answers2

1

You are not going to find a two-line solution as per python. Instead, you are going to need to parse the XML and generate the JSON. For that you can use XMLTABLE and JSON functions:

SELECT x2j.json
FROM   table_name t
       CROSS JOIN LATERAL (
         SELECT JSON_OBJECT(
                  KEY 'firstName'       VALUE x.firstName,
                  KEY 'lastName'        VALUE x.lastName,
                  KEY 'favoriteNumbers' VALUE JSON_OBJECT(
                      KEY 'value' VALUE f.favoriteNumbers
                    )
                ) AS json
         FROM   XMLTABLE(
                  '/elementA'
                  PASSING XMLTYPE(t.xml_data)
                  COLUMNS
                    firstName VARCHAR2(200) PATH './firstName',
                    lastName  VARCHAR2(200) PATH './lastName',
                    favoriteNumbers XMLTYPE PATH './favoriteNumbers'
                ) x
                CROSS JOIN LATERAL (
                  SELECT JSON_ARRAYAGG(value) AS favoriteNumbers
                  FROM   XMLTABLE(
                           '/favoriteNumbers/value'
                           PASSING x.favoriteNumbers
                           COLUMNS
                             value NUMBER PATH '.'
                         )
                ) f
       ) x2j

Which, for the sample data:

CREATE TABLE table_name (xml_data) AS 
SELECT '<elementA>
  <firstName>snoopy</firstName>
  <lastName>brown</lastName>
  <favoriteNumbers>
    <value>1</value>
    <value>2</value>
    <value>3</value>
  </favoriteNumbers>
</elementA>' FROM DUAL;

Outputs:

JSON
{"firstName":"snoopy","lastName":"brown","favoriteNumbers":{"value":[1,2,3]}}

Having said that you won't get a 2-line solution like python, you can technically get a 1-line solution in Oracle:

SELECT x2j.json FROM table_name t CROSS JOIN LATERAL (SELECT JSON_OBJECT(KEY 'firstName' VALUE x.firstName, KEY 'lastName' VALUE x.lastName, KEY 'favoriteNumbers' VALUE JSON_OBJECT( KEY 'value' VALUE f.favoriteNumbers ) ) AS json FROM XMLTABLE( '/elementA' PASSING XMLTYPE(t.xml_data) COLUMNS firstName VARCHAR2(200) PATH './firstName', lastName VARCHAR2(200) PATH './lastName', favoriteNumbers XMLTYPE PATH './favoriteNumbers' ) x CROSS JOIN LATERAL ( SELECT JSON_ARRAYAGG(value) AS favoriteNumbers FROM XMLTABLE( '/favoriteNumbers/value' PASSING x.favoriteNumbers COLUMNS value NUMBER PATH '.' ) ) f ) x2j

But that is just the same query as above with all the white-spaces concatenated. Number of lines of code is not necessarily a helpful metric to measure code by.

fiddle

MT0
  • 143,790
  • 11
  • 59
  • 117
  • it just clicked well some of it did. Looking at your other post I thought the '/xml/*' was a keyword for Oracle itself but it appears to be the first tag encountered. Just as you showed above with . However, what would you put for something like because it is complaining about ORA-19228:xpst008 - undeclared identifier: prefix 'xyz' local-name 'xyz:elementA' – edjm May 05 '23 at 14:51
  • @edjm Use [`XMLNAMESPACES`](https://stackoverflow.com/a/67089667/1509264) to define the namespaces. – MT0 May 05 '23 at 14:56
  • @MTO does the solution work on CLOBS in addition to varchar2 fields? – edjm May 05 '23 at 15:08
  • @edjm Yes, [fiddle](https://dbfiddle.uk/vfZOWs04) – MT0 May 05 '23 at 15:50
  • @MTO Correct me if I am wrong but it appears that in order to really utilized the built in functionality of Oracle one must know the paths of all things within the XML in order to define the select for the JSON. – edjm May 05 '23 at 16:05
  • 1
    @edjm You are not wrong; outside of JSON and XML, Oracle SQL does not support arbitrary data structures and there is not a direct conversion from XML to JSON. So you need to convert from XML to a known structure and then from the known structure to JSON. You may be able to do it in PL/SQL or by embedding a Java package into the database but I don't see an arbitrary method for SQL. – MT0 May 05 '23 at 17:55
0

Oracle has released APEX 22.2 which is fully supported through Oracle Support Services on all Editions (EE, SE2, SE, and SE1) of the Oracle database, 12.1.0.2 or higher with a valid Oracle Database Technical Support agreement.

For this example APEX 5 installation is enough to get started.

With that you can use the simple procedure given below to get the JSON object

          declare
             l_xml xmltype;
           begin
          
              select
                xmltype
            ( wrapper(
            myobj_nt(
             myobj(1,'Connor'),
                myobj(2,'McDonald')
            )
             ))
           into l_xml
           from dual;
        
           apex_json.initialize_clob_output;
           apex_json.write(l_xml);
           dbms_output.put_line(apex_json.get_clob_output);
           apex_json.free_output;
         end;

if you want an alternative you can use the below but performance wise it is slower for large dataset.Also you need to implement this custom JSON nest units

        with edge_data as (
        select x.* 
          FROM tmp_xml t
            , XMLTable(
              'declare function local:getChildren($e as node(), $pID as xs:string?) as element()*
               {
                 for $i at $p in $e/(child::*|attribute::*)
                 let $ID := if ($pID) then concat($pID,".",$p) else "1"
                return element r
                {
                   element node_id {$ID}
                 , element parent_node_id {$pID}
                 , element node_name {name($i)}
                 , if ($i instance of attribute())
                     then ( element node_value {data($i)}, element node_kind {"attribute"} )
                     else ( element node_value {$i/text()}, element node_kind {"element"} )
                 }
                 | local:getChildren($i,$ID)
               }; (: :)
               local:getChildren($d,())'
                passing t.xml_data as "d"
               columns node_id         varchar2(100)   path 'node_id'
                    , node_name       varchar2(30)    path 'node_name'
                     , node_value      varchar2(2000)  path 'node_value'
                      , parent_node_id  varchar2(100)   path 'parent_node_id'
                     , node_kind       varchar2(30)    path 'node_kind'
        ) x
        )
        select  JSONNest( 
                 JSONNestItem(
                   level
                 , json_object(
                     'name' value node_name
                   , 'value' value node_value
                   )
                , 'nested_info'
                )
              ) as result
        
        from edge_data t
        where node_kind in ('element', 'attribute', 'text')
        connect by prior node_id = parent_node_id
        start with parent_node_id is null ;

Sample output:-

  {"name":"project","value":null,"child_info":[{"name":"projectNumber","value":"311927"},{"name":"projectType","value":"BUILD"},{"name":"lineOfBusiness","value":"COMMERCIAL"},{"name":"projectStatus","value":"PROGRASS"},{"name":"summary","value":null,"child_info":[{"name":"creationDate","value":"08/02/2016"},{"name":"workflowStateDate","value":null},{"name":"effectiveDate","value":"01/01/2014"},{"name":"clientRequested","value":"FALSE"},{"name":"mandatoryReview","value":"FALSE"},{"name":"internalProject","value":"FALSE"},{"name":"clientType","value":"Permanent"},{"name":"description","value":"Test Data 2"},{"name":"appliesTo","value":null,"child_info":[{"name":"Retail","value":"TRUE"},{"name":"Mail","value":"TRUE"}]}]}]} 
psaraj12
  • 4,772
  • 2
  • 21
  • 30