I’m writing a function in oracle 19c, which should, on given json_object_t that can have any structure, loop through all the keys, and if key is one of pre-specified, mask the value with asterisk. Those values would be card numbers. The function should then return changed json_object_t.
My problem is that the returned json is unchanged.
Here is my code. The input json is just an example; it should work with any json. Code for now is written as pl/sql block:
DECLARE
v_output CLOB;
v_input_json JSON_OBJECT_T;
v_json_object JSON_OBJECT_T;
PROCEDURE mask_keys(p_json IN OUT NOCOPY JSON_OBJECT_T)
IS
v_keys JSON_KEY_LIST;
v_key VARCHAR2(100);
v_value JSON_ELEMENT_T;
v_json_array JSON_ARRAY_T := JSON_ARRAY_T();
v_index NUMBER;
TYPE param_values_t IS TABLE OF VARCHAR2(100);
v_param_values param_values_t := param_values_t('CardNum', 'CardNumber', 'acctNum', 'acctNumber'); --keys to mask
FUNCTION mask_numeric_value(p_value VARCHAR2) RETURN VARCHAR2 IS --function to mask the value
v_masked_value VARCHAR2(100);
v_value varchar2(100) := p_value;
BEGIN
dbms_output.put_line('function mask_numeric_value - start');
v_value := trim(BOTH '"' from v_value);
IF REGEXP_LIKE(v_value, '^d+$') THEN
v_masked_value := SUBSTR(v_value, 1, 6) || '******' || SUBSTR(v_value, -4);
ELSE
v_masked_value := v_value;
END IF;
RETURN v_masked_value;
END mask_numeric_value;
BEGIN
DBMS_OUTPUT.PUT_LINE('function mask_keys - start');
v_keys := p_json.get_keys;
FOR i IN 1..v_keys.count LOOP
v_key := v_keys(i);
v_value := p_json.get(v_key);
DBMS_OUTPUT.PUT_LINE('Key: ' || v_key || ', Value: ' || v_value.to_string);
IF v_key MEMBER OF v_param_values THEN
dbms_output.put_line('Found');
IF v_value.is_string THEN
dbms_output.put_line('Masking');
p_json.put(v_key, mask_numeric_value(v_value.to_string()));
DBMS_OUTPUT.PUT_LINE('New value: '||p_json.get(v_key).to_string);
END IF;
ELSIF v_value.is_object THEN
DBMS_OUTPUT.PUT_LINE('Value is an object');
DBMS_OUTPUT.PUT_LINE(v_value.to_string);
v_json_object := JSON_OBJECT_T.parse(v_value.to_string());
-- If the element is an object, recursively call mask_keys
mask_keys(v_json_object);
ELSIF v_value.is_array THEN
DBMS_OUTPUT.PUT_LINE('Value is an array');
v_json_array := JSON_ARRAY_T.parse(v_value.to_string());
FOR j IN 0..v_json_array.get_size() - 1 LOOP
-- If the array element is an object, recursively call mask_keys
IF v_json_array.get(j).is_object THEN
DBMS_OUTPUT.PUT_LINE('Element is an object');
v_json_object := JSON_OBJECT_T.parse(v_json_array.get(j).to_string());
mask_keys(v_json_object);
END IF;
END LOOP;
END IF;
END LOOP;
END mask_keys;
BEGIN
v_input_json := JSON_OBJECT_T.parse('{
"success": true,
"payload": {
"authSumCnt": "1",
"CardNum": "7712343649057813",
"authSum": [
{
"CardNumber": "9512343649057813",
"otherKey": "otherValue"
},
{
"acctNum": "1234567890123456",
"anotherKey": "anotherValue",
"nestedArray": [
{
"nestedKey": "nestedValue",
"acctNumber": "1234567890123456"
}
]
}
]
}
}');
mask_keys(v_input_json);
v_output := v_input_json.to_clob;
DBMS_OUTPUT.PUT_LINE(v_output);
END;
the return v_output is the same as input:
{
"success": true,
"payload": {
"authSumCnt": "1",
"CardNum": "7712343649057813",
"authSum": [
{
"CardNumber": "9512343649057813",
"otherKey": "otherValue"
},
{
"acctNum": "1234567890123456",
"anotherKey": "anotherValue",
"nestedArray": [
{
"nestedKey": "nestedValue",
"acctNumber": "1234567890123456"
}
]
}
]
}
}
2
Answers
The problem is that you are parsing the string values to new JSON objects, and those are then independent of the original input parameter.
So rather than:
just get the actual object:
and instead of:
get the original array and it’s element, as an object:
So in full:
Which produces (formatted):
fiddle
For the fun and ≥23c only, take advantage of the JS regexp supporting back reference: