Oracle 19c遍历JSON_OBJECT_T掩码指定键值无效问题排查
Oracle 19c PL/SQL JSON掩码处理不生效问题
问题背景
在Oracle 19c中编写PL/SQL块,目标是遍历任意结构的JSON_OBJECT_T,当遇到指定的卡号类键(CardNum、CardNumber、acctNum、acctNumber)时,将对应值做掩码处理(保留前6位和后4位,中间用******代替),最终返回修改后的JSON_OBJECT_T。但实际执行后,返回的JSON与输入完全一致,未完成预期修改。
原始代码
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'); --需要掩码的键 FUNCTION mask_numeric_value(p_value VARCHAR2) RETURN VARCHAR2 IS --掩码处理函数 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()); -- 如果是对象,递归调用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 -- 如果数组元素是对象,递归调用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;
输入示例JSON
{ "success": true, "payload": { "authSumCnt": "1", "CardNum": "7712343649057813", "authSum": [ { "CardNumber": "9512343649057813", "otherKey": "otherValue" }, { "acctNum": "1234567890123456", "anotherKey": "anotherValue", "nestedArray": [ { "nestedKey": "nestedValue", "acctNumber": "1234567890123456" } ] } ] } }
输出结果(与输入一致)
{ "success": true, "payload": { "authSumCnt": "1", "CardNum": "7712343649057813", "authSum": [ { "CardNumber": "9512343649057813", "otherKey": "otherValue" }, { "acctNum": "1234567890123456", "anotherKey": "anotherValue", "nestedArray": [ { "nestedKey": "nestedValue", "acctNumber": "1234567890123456" } ] } ] } }
问题原因及修复方案
核心问题
代码处理嵌套对象和数组时存在关键错误:将嵌套对象/数组解析成新的JSON_OBJECT_T/JSON_ARRAY_T实例,修改后未把更新后的实例写回原JSON结构。比如处理嵌套对象时,用v_json_object := JSON_OBJECT_T.parse(v_value.to_string());创建新对象,递归修改后,新对象的变化不会同步到原p_json中的v_value;数组处理同理,修改数组内的对象后,未将更新后的对象放回数组,也未把数组写回原JSON。
修复后的代码
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; TYPE param_values_t IS TABLE OF VARCHAR2(100); v_param_values param_values_t := param_values_t('CardNum', 'CardNumber', 'acctNum', 'acctNumber'); --需要掩码的键 FUNCTION mask_numeric_value(p_value VARCHAR2) RETURN VARCHAR2 IS --掩码处理函数 v_masked_value VARCHAR2(100); v_value varchar2(100) := p_value; BEGIN -- v_value.to_string()返回不带引号的纯字符串,无需去除" 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 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); IF v_key MEMBER OF v_param_values THEN IF v_value.is_string THEN p_json.put(v_key, mask_numeric_value(v_value.to_string())); END IF; ELSIF v_value.is_object THEN -- 直接转换为JSON_OBJECT_T获取引用,无需重新解析 v_json_object := TREAT(v_value AS JSON_OBJECT_T); -- 递归修改,引用对象的变化会直接同步到原结构 mask_keys(v_json_object); ELSIF v_value.is_array THEN v_json_array := TREAT(v_value AS JSON_ARRAY_T); FOR j IN 0..v_json_array.get_size() - 1 LOOP IF v_json_array.get(j).is_object THEN v_json_object := TREAT(v_json_array.get(j) AS JSON_OBJECT_T); 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;
关键修复点
- 直接获取引用,避免重新解析:使用
TREAT(v_value AS JSON_OBJECT_T)和TREAT(v_value AS JSON_ARRAY_T)获取原对象/数组的引用,递归修改时所有变化直接作用于原JSON结构。 - 移除多余的引号处理:
JSON_ELEMENT_T.to_string()返回不带引号的纯字符串,原代码的trim(BOTH '"' from v_value)会破坏卡号值,导致掩码逻辑失效。 - 数组处理优化:直接操作数组元素的引用,修改后自动同步到原数组,无需额外写回操作。
修复后输出示例
{ "success": true, "payload": { "authSumCnt": "1", "CardNum": "771234******7813", "authSum": [ { "CardNumber": "951234******7813", "otherKey": "otherValue" }, { "acctNum": "123456******3456", "anotherKey": "anotherValue", "nestedArray": [ { "nestedKey": "nestedValue", "acctNumber": "123456******3456" } ] } ] } }
内容的提问来源于stack exchange,提问作者charly99
相关产品推荐
相关产品推荐

