Navigation Bar

Wednesday, March 26, 2025

Parsing and updating a JSON object using PLSQL

 Create the sample JSON objects and data

CREATE TABLE j_purchaseorder
  (id          VARCHAR2 (32) NOT NULL PRIMARY KEY,
   date_loaded TIMESTAMP (6) WITH TIME ZONE,
   po_document VARCHAR2 (4000)
   CONSTRAINT ensure_json CHECK (po_document is json));
Insert sample data into j_purchaseorder table
INSERT INTO j_purchaseorder
  VALUES (
    SYS_GUID(),
    to_date('30-DEC-2014'),
    '{"PONumber"             : 1600,
      "Reference"            : "ABULL-20140421",
      "Requestor"            : "Alexis Bull",
      "User"                 : "ABULL",
      "CostCenter"           : "A50",
      "ShippingInstructions" :
        {"name"    : "Alexis Bull",
         "Address" : {"street"  : "200 Sporting Green",
                      "city"    : "South San Francisco",
                      "state"   : "CA",
                      "zipCode" : 99236,
                      "country" : "United States of America"},
         "Phone"   : [{"type" : "Office", "number" : "909-555-7307"},
                      {"type" : "Mobile", "number" : "415-555-1234"}]},
      "Special Instructions" : null,
      "AllowPartialShipment" : true,
      "LineItems"            :
        [{"ItemNumber" : 1,
          "Part"       : {"Description" : "One Magic Christmas",
                          "UnitPrice"   : 19.95,
                          "UPCCode"     : 13131092899},
          "Quantity"   : 9.0},
         {"ItemNumber" : 2,
          "Part"       : {"Description" : "Lethal Weapon",
                          "UnitPrice"   : 19.95,
                          "UPCCode"     : 85391628927},
          "Quantity"   : 5.0}]}');

INSERT INTO j_purchaseorder
  VALUES (
    SYS_GUID(),
    to_date('31-DEC-2015'),
    '{"PONumber"             : 2600,
      "Reference"            : "SBELL-20141017",
      "Requestor"            : "Sarah Bell",
      "User"                 : "SBELL",
      "CostCenter"           : "A50",
      "ShippingInstructions" : {"name"    : "Sarah Bell",
                                "Address" : {"street"  : "200 Sporting Green",
                                             "city"    : "South San Francisco",
                                             "state"   : "CA",
                                             "zipCode" : 99236,
                                             "country" : "United States of America"},
                                "Phone"   : "983-555-6509"},
      "Special Instructions" : "Courier",
      "LineItems"            :
        [{"ItemNumber" : 1,
          "Part"       : {"Description" : "Making the Grade",
                          "UnitPrice"   : 20,
                          "UPCCode"     : 27616867759},
          "Quantity"   : 8.0},
         {"ItemNumber" : 2,
          "Part"       : {"Description" : "Nixon",
                          "UnitPrice"   : 19.95,
                          "UPCCode"     : 717951002396},
          "Quantity"   : 5},
         {"ItemNumber" : 3,
          "Part"       : {"Description" : "Eric Clapton: Best Of 1981-1999",
                          "UnitPrice"   : 19.95,
                          "UPCCode"     : 75993851120},
          "Quantity"   : 5.0}]}'); 
Create the function to modify JSON attributes and add new attributes. 
In this function I am modifying the PO Number and adding up the total order quantity and price.
CREATE OR REPLACE FUNCTION modify_order(purchaseOrder IN VARCHAR2) RETURN VARCHAR2 IS
  po_obj        JSON_OBJECT_T;
  li_arr        JSON_ARRAY_T;
  li_item       JSON_ELEMENT_T;
  li_obj        JSON_OBJECT_T;
  unitPrice     NUMBER;
  quantity      NUMBER;
  ponumber      NUMBER;
  totalPrice    NUMBER := 0;
  totalQuantity NUMBER := 0;
BEGIN
  po_obj := JSON_OBJECT_T.parse(purchaseOrder);
  li_arr := po_obj.get_Array('LineItems');
  ponumber := po_obj.get_String('PONumber');
  ponumber := ponumber + 100;
  po_obj.put('PONumber', ponumber);
  FOR i IN 0 .. li_arr.get_size - 1 LOOP
    li_obj := JSON_OBJECT_T(li_arr.get(i));
    quantity := li_obj.get_Number('Quantity');
    unitPrice := li_obj.get_Object('Part').get_Number('UnitPrice');
    totalPrice := totalPrice + (quantity * unitPrice);
    totalQuantity := totalQuantity + quantity;
  END LOOP;
  
  po_obj.put('totalQuantity', totalQuantity);
  po_obj.put('totalPrice', totalPrice);
  RETURN po_obj.to_string;
END;  
Run the update statement on j_purchaseorder by calling the modify_order function.
UPDATE j_purchaseorder SET (po_document) = modify_order(po_document);
If I select data for the exisiting PONumber I get 0 records. Below is the modified JSON with the new PONumber.
SELECT po.po_document.PONumber FROM j_purchaseorder po WHERE po.po_document.PONumber = 1700;
{
    "Reference": "ABULL-20140421",
    "Requestor": "Alexis Bull",
    "User": "ABULL",
    "CostCenter": "A50",
    "ShippingInstructions": {
        "name": "Alexis Bull",
        "Address": {
            "street": "200 Sporting Green",
            "city": "South San Francisco",
            "state": "CA",
            "zipCode": 99236,
            "country": "United States of America"
        },
        "Phone": [
            {
                "type": "Office",
                "number": "909-555-7307"
            },
            {
                "type": "Mobile",
                "number": "415-555-1234"
            }
        ]
    },
    "Special Instructions": null,
    "AllowPartialShipment": true,
    "LineItems": [
        {
            "ItemNumber": 1,
            "Part": {
                "Description": "One Magic Christmas",
                "UnitPrice": 19.95,
                "UPCCode": 13131092899
            },
            "Quantity": 9
        },
        {
            "ItemNumber": 2,
            "Part": {
                "Description": "Lethal Weapon",
                "UnitPrice": 19.95,
                "UPCCode": 85391628927
            },
            "Quantity": 5
        }
    ],
    "PONumber": 1700,
    "totalQuantity": 14,
    "totalPrice": 279.3
}
Here we see that using JSON_PUT is updating the JSON attribute PONumber, but appending the tag to the end of the JSON.
This is changing the JSON structure. We need to find a method to update the JSON without changing the order of the JSON attributes.

If there are built in JSON methods in PLSQL, to modify a JSON value without altering the structure that still I need to check. An alternate approach would be to rebuild the JSON and keep adding to the new JSON the updated values. This will make the program a little more complex, but it will do as a work around.

In the below example I am having the Employee Department object, with an array of employees in that department.

Sample JSON
{
    "department": "Accounting",
    "employees": [
        {
            "name": "Shelley,Higgins",
            "job": "Accounting Manager"
        },
        {
            "name": "William,Gietz",
            "job": "Public Accountant"
        }
    ]
}  
In this I want to update the department name from "Accounting" to "Accounting and Finance" and for employee Shelley Higgins her job profile is changed from "Accounting Manager" to "Accounting and Finance Manager". 
Below is the code for the same.
DECLARE
   jo             JSON_OBJECT_T;
   jo_1           JSON_OBJECT_T;
   new_json       CLOB;
   keys           JSON_KEY_LIST;
   emp_keys       JSON_KEY_LIST;
   elem_1         VARCHAR2(100);
   key_string     VARCHAR2(1000);
   value_string   VARCHAR2(1000);
   emp_key_string VARCHAR2(1000);
   emp_value_string VARCHAR2(1000);
   l_ja           JSON_ARRAY_T;
   l_ja_1         JSON_ARRAY_T;
   l_emp_obj      JSON_OBJECT_T; -- For handling individual employee objects
   l_emp_obj_1      JSON_OBJECT_T;
BEGIN

   -- Parse JSON object
   jo := JSON_OBJECT_T.parse('{
      "department": "Accounting",
      "employees": [
         {
            "name": "Shelley,Higgins",
            "job": "Accounting Manager"
         },
         {
            "name": "William,Gietz",
            "job": "Public Accountant"
         }
      ]
   }');
   jo_1 :=  JSON_OBJECT_T(); --initialize the new department JSON object

   
   -- Retrieve keys of the main JSON object
   keys := jo.get_keys;
   
   -- Loop through the main keys (department, employees)
   FOR i IN 1..keys.COUNT LOOP
      key_string := keys(i);
      -- Retrieve the value for each key
      value_string := jo.get(key_string).to_string;
      
      DBMS_OUTPUT.put_line('Key: ' || key_string);
      DBMS_OUTPUT.put_line('Value: ' || value_string);
      IF value_string = '"Accounting"'
      THEN
        value_string := 'Accounting and Finance'; -- Change the department name to Accounting and Finance
      END IF;
      dbms_output.put_line('value_string ' || value_string);
      IF key_string != 'employees' THEN
        jo_1.put(key_string, value_string);  -- In a loop add the key value pair to the new JSON object jo_1
      -- Check if the key is 'employees'
      ELSIF key_string = 'employees' THEN     -- Check in the JSON object for the employees array
         DBMS_OUTPUT.put_line('Employees JSON array: ' || value_string);
         -- Parse the 'employees' array
         l_ja := JSON_ARRAY_T.parse(value_string);
         
         -- Loop through each employee object in the array
         
         l_ja_1 := JSON_ARRAY_T();   --initialize the new employees JSON array 
         FOR j IN 0..l_ja.get_size - 1 LOOP
            -- Get the individual employee object
            l_emp_obj := JSON_OBJECT_T(l_ja.get(j)); --assign each employee data in the array to the employee object.
            
            DBMS_OUTPUT.put_line('Employee object ' || j || ' ' ||  l_ja.get(j).to_string);
            -- Retrieve the keys for the employee object (name, job)
            emp_keys := l_emp_obj.get_keys;
            
            -- Loop through the employee keys (name, job)
            l_emp_obj_1 := JSON_OBJECT_T();  --Initialize the temporary employee object
            FOR k IN 1..emp_keys.COUNT LOOP
               emp_key_string := emp_keys(k);
--               DBMS_OUTPUT.put_line('key string  ' || k || ' ' || emp_key_string );
               emp_value_string := l_emp_obj.get(emp_key_string).to_string;
               IF emp_key_string = 'job' AND emp_value_string = '"Accounting Manager"' -- build the logic here to modify the employee object data
               THEN
                  emp_value_string := 'Accounting and Finance Manager';  -- change job profile to Accounting and Finance Manager
               END IF;
               emp_value_string := TRIM (BOTH '"' FROM (emp_value_string)); --trim the "" from the value element beofre assigning to the temporary employee object
               l_emp_obj_1.put(emp_key_string, emp_value_string); --assign the modified key value pair to the temporary employee object
          
               -- Output employee key-value pairs
  --             DBMS_OUTPUT.put_line('Employee ' || j || ' ' || emp_key_string || ': ' || emp_value_string);
            END LOOP;
            l_ja_1.append(l_emp_obj_1); -- append the temporary employee object to the new employees JSON array
  --          DBMS_OUTPUT.put_line('Employee object 1 ' || l_emp_obj_1.stringify);
          END LOOP;
         jo_1.put(key_string, l_ja_1); -- once the new employees JSON array is build assign it to the new department JSON object
      END IF;
      
   END LOOP;
   
   new_json := jo_1.stringify;
 
   dbms_output.put_line('json obj' || new_json); --this will show the modified department JSON with the structure maintained as is.
END;
/
Below is the modified department JSON
{
    "department": "Accounting and Finance",
    "employees": [
        {
            "name": "Shelley,Higgins",
            "job": "Accounting and Finance Manager"
        },
        {
            "name": "William,Gietz",
            "job": "Public Accountant"
        }
    ]
}  
This is not a generic parser and it has certain limitations, like it assumes that the JSON will be an object.
If the input JSON is an array this block will fail. To make the parser more generic we can put a check in the beginning for the object like if it is a JSON object or a JSON array. 
We will first understand need to understand some of the common JSON methods and object types.

To be continued ...

References


Thought for the day
Do not grow weary when you pray;
  do not neglect to give alms
Do not ridicule a person who is embittered in spirit,
  for there is One who humbles and exalts
Do not devise a lie against your brother
  or do the same to a friend.
Refuse to utter any lie,
  for it is a habit that results in no good.
Sirach 7:10-13

No comments:

Post a Comment