You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

关键修复点

  1. 直接获取引用,避免重新解析:使用TREAT(v_value AS JSON_OBJECT_T)和TREAT(v_value AS JSON_ARRAY_T)获取原对象/数组的引用,递归修改时所有变化直接作用于原JSON结构。
  2. 移除多余的引号处理:JSON_ELEMENT_T.to_string()返回不带引号的纯字符串,原代码的trim(BOTH '"' from v_value)会破坏卡号值,导致掩码逻辑失效。
  3. 数组处理优化:直接操作数组元素的引用,修改后自动同步到原数组,无需额外写回操作。

修复后输出示例

{
  "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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 11:59:54