Office Address

  • 123/A, Miranda City Prikano
  • +0989 7876 9865 9
  • info@example.com

Social List

Domina APEX_JSON en Oracle APEX 24: Genera JSON como un Profesional

Lectura: 10 minutos

PARTE 1: INTRODUCCIÓN Y CONTEXTO

El Problema clásico de concatenación

Antes de APEX_JSON, los desarrolladores concatenaban strings JSON manualmente:

-- ❌ El viejo camino (PROBLEMA: caracteres especiales no escapados)
declare
    v_name varchar2(100) := 'John O''Reilly';  -- Apostrofe
    v_salary number := 50000;
    v_json varchar2(4000);
begin
    v_json := '{"employee":"' || v_name || '","salary":' || v_salary || '}';
    -- Resultado INVÁLIDO: {"employee":"John O'Reilly","salary":50000}
    -- El apostrofe rompe el JSON
end;

Este JSON es inválido. El apóstrofe no está escapado. Multiplica esto por 1000 líneas de código y tienes un desastre de mantenimiento.

La solución: APEX_JSON

-- ✅ APEX_JSON escapa automáticamente
declare
    v_name varchar2(100) := 'John O''Reilly';
    v_salary number := 50000;
begin
    apex_json.initialize_clob_output;
    
    apex_json.open_object;
    apex_json.write('employee', v_name);        -- Escapa automáticamente
    apex_json.write('salary', v_salary);        -- Convierte tipos automáticamente
    apex_json.close_object;
    
    -- Resultado VÁLIDO: {"employee":"John O'Reilly","salary":50000}
    dbms_output.put_line(apex_json.get_clob_output);
    apex_json.free_output;
end;

Con APEX_JSON obtienes:

✅ Escapado automático de caracteres especiales

✅ Manejo automático de tipos (number, date, boolean)

✅ Validación de estructura JSON

✅ APIs fluidas y legibles

✅ Mejor rendimiento


PARTE 2: LAS FUNCIONES NATIVAS EXPLICADAS

GRUPO 1: INICIALIZACIÓN

apex_json.initialize_clob_output()

¿Qué hace exactamente? Inicializa un buffer CLOB (Character Large Object) interno en la sesión PL/SQL actual. Este buffer acumula todo lo que escribas con apex_json.write() hasta que llames a get_clob_output().

Parámetros: Ninguno

Retorna: Void (modifica estado interno)

¿Cuándo usar?

  • Cuando necesitas construir un JSON y devolverlo completo (no streaming)
  • Cuando trabajas con JSON que cabe en memoria (< 500MB)
  • Cuando necesitas acceso al JSON final con get_clob_output()

Ejemplo técnico:

declare
    v_json clob;
begin
    -- Paso 1: Inicializar buffer CLOB
    apex_json.initialize_clob_output;
    
    -- Paso 2: Escribir datos (se acumulan en el buffer interno)
    apex_json.open_object;
    apex_json.write('timestamp', systimestamp);
    apex_json.write('status', 'active');
    apex_json.close_object;
    
    -- Paso 3: Recuperar el buffer completo
    v_json := apex_json.get_clob_output;
    
    -- Paso 4: CRÍTICO - Liberar memoria
    apex_json.free_output;
    
    -- Paso 5: Ahora puedes usar v_json sin limitaciones
    insert into json_log (payload) values (v_json);
    commit;
end;

Detalles técnicos importantes:

  • El buffer CLOB reside en la sesión PL/SQL, no en SGA
  • Si no llamas a free_output(), el buffer persiste hasta fin de sesión (memory leak)
  • Tamaño máximo del CLOB depende de memory_target del servidor
  • Las operaciones de write no son instantáneas—se acumulan internamente

apex_json.initialize_output()

¿Qué hace exactamente? Inicializa un buffer HTP (HyperText Protocol) que escribe directamente a la respuesta HTTP. Esto es diferente a CLOB—los datos se envían inmediatamente al cliente mientras se generan.

Parámetros: Ninguno

Retorna: Void (prepara el buffer HTTP)

¿Cuándo usar?

  • En REST endpoints que responden JSON directamente
  • Cuando necesitas devolver millones de filas sin cargar todo en memoria
  • Cuando quieres que el cliente comience a recibir datos inmediatamente
  • En procedures que se ejecutan desde APEX como AJAX callbacks

Ejemplo avanzado (Streaming 1 millón de filas):

create or replace procedure stream_transactions_1m as
    nm owa.vc_arr;
    vl owa.vc_arr;
    l_rows integer := 0;
begin
    -- PASO 1: Inicializar entorno CGI mínimo
    -- Esto es REQUERIDO para usar initialize_output
    nm(1) := 'dummy_param';
    vl(1) := 'dummy_value';
    owa.init_cgi_env(1, nm, vl);

    -- PASO 2: Inicializar buffer HTTP (no CLOB)
    apex_json.initialize_output;

    -- PASO 3: Comenzar respuesta JSON
    apex_json.open_object;
    apex_json.write('status', 'streaming');
    apex_json.write('started_at', systimestamp);
    apex_json.open_array('data');
    
    -- PASO 4: Iterar sobre MILLONES de filas
    -- Cada write() se envía al cliente inmediatamente
    for c in (
        select id, amount, created_at, status 
        from large_transactions_table
        where created_at >= trunc(sysdate)
    ) loop
        apex_json.open_object;
        apex_json.write('id', c.id);
        apex_json.write('amount', c.amount);
        apex_json.write('ts', to_char(c.created_at, 'YYYY-MM-DD"T"HH24:MI:SS"Z"'));
        apex_json.write('status', c.status);
        apex_json.close_object;
        
        l_rows := l_rows + 1;
        
        -- Log cada 10K filas
        if mod(l_rows, 10000) = 0 then
            dbms_output.put_line('Streamed ' || l_rows || ' rows');
        end if;
    end loop;
    
    -- PASO 5: Cerrar estructura
    apex_json.close_array;
    apex_json.open_object('summary');
    apex_json.write('total_rows', l_rows);
    apex_json.write('completed_at', systimestamp);
    apex_json.close_object;
    apex_json.close_object;
    
    -- NO necesita free_output() - HTP se limpia automáticamente

exception 
    when others then
        -- Error durante streaming es crítico—cliente ya recibió datos
        dbms_output.put_line('CRITICAL: Error at row ' || l_rows);
        dbms_output.put_line('Error: ' || sqlerrm);
        -- Intenta cerrar JSON gracefully
        apex_json.close_object;
end stream_transactions_1m;

Detalles técnicos CRÍTICOS:

  • Requiere owa.init_cgi_env() para establecer contexto HTTP
  • Los datos se escriben directamente a htp.print() internamente
  • Si hay excepción a mitad del streaming, el cliente recibe JSON incompleto/inválido
  • No puedes llamar a get_clob_output() después de usar initialize_output()
  • No necesita free_output() porque el buffer HTP se limpia automáticamente tras la respuesta

GRUPO 2: CONSTRUCCIÓN DE OBJETOS Y ARRAYS

apex_json.open_object() / apex_json.close_object()

¿Qué hace? Crea un objeto JSON ({...}). Cada open_object() debe balancearse con un close_object().

Parámetros:

apex_json.open_object(
    p_name varchar2  -- Opcional: clave para el objeto si está dentro de otro
)

Retorna: Void

¿Cuándo usar? Para estructuras que contienen propiedades con pares clave-valor.

Ejemplo básico:

declare
begin
    apex_json.initialize_clob_output;
    
    -- Objeto raíz (sin nombre)
    apex_json.open_object;
    apex_json.write('name', 'Juan');
    apex_json.write('age', 30);
    apex_json.close_object;
    
    -- JSON generado: {"name":"Juan","age":30}
end;

Ejemplo con anidamiento y nombres:

declare
begin
    apex_json.initialize_clob_output;
    
    apex_json.open_object;  -- Raíz (sin nombre)
    apex_json.write('employee_id', 12345);
    
    -- Objeto anidado con clave 'personal'
    apex_json.open_object('personal');
    apex_json.write('first_name', 'Juan');
    apex_json.write('last_name', 'García');
    apex_json.write('email', 'juan@company.com');
    apex_json.close_object;  -- Cierra personal
    
    -- Otro objeto anidado
    apex_json.open_object('employment');
    apex_json.write('title', 'Senior Developer');
    apex_json.write('salary', 95000);
    apex_json.write('start_date', to_date('2020-01-15', 'YYYY-MM-DD'));
    apex_json.close_object;  -- Cierra employment
    
    apex_json.close_object;  -- Cierra raíz
    
    -- JSON generado:
    -- {
    --   "employee_id": 12345,
    --   "personal": {
    --     "first_name": "Juan",
    --     "last_name": "García",
    --     "email": "juan@company.com"
    --   },
    --   "employment": {
    --     "title": "Senior Developer",
    --     "salary": 95000,
    --     "start_date": "2020-01-15"
    --   }
    -- }
end;

Errores comunes:

-- ❌ INCORRECTO: Olvida close_object()
apex_json.open_object;
apex_json.write('name', 'Juan');
-- Falta: apex_json.close_object;
-- Resultado: {"name":"Juan"  ← JSON ABIERTO, INVÁLIDO

-- ❌ INCORRECTO: Cierra dos veces
apex_json.open_object;
apex_json.write('name', 'Juan');
apex_json.close_object;
apex_json.close_object;  -- ← Error, nada que cerrar
-- Resultado: Excepción

-- ✅ CORRECTO: Balanceado
apex_json.open_object;
apex_json.write('name', 'Juan');
apex_json.close_object;  -- Una única pareja

apex_json.open_array() / apex_json.close_array()

¿Qué hace? Crea un array JSON ([...]). Los elementos se agregan entre open_array() y close_array().

Parámetros:

apex_json.open_array(
    p_name varchar2  -- Opcional: clave para el array si está dentro de un objeto
)

Retorna: Void

¿Cuándo usar? Para listas de datos homogéneos (empleados, transacciones, ubicaciones, etc.).

Ejemplo: Array de objetos (más común):

declare
    v_json clob;
begin
    apex_json.initialize_clob_output;
    
    apex_json.open_object;
    apex_json.write('department', 'Engineering');
    
    -- Array de empleados bajo clave 'team_members'
    apex_json.open_array('team_members');
    
    for emp in (select empno, ename, sal from emp where deptno = 20) loop
        apex_json.open_object;  -- Cada elemento es un objeto
        apex_json.write('id', emp.empno);
        apex_json.write('name', emp.ename);
        apex_json.write('salary', emp.sal);
        apex_json.close_object;
    end loop;
    
    apex_json.close_array;  -- Cierra team_members array
    apex_json.close_object; -- Cierra objeto raíz
    
    -- JSON:
    -- {
    --   "department": "Engineering",
    --   "team_members": [
    --     {"id": 7369, "name": "SMITH", "salary": 800},
    --     {"id": 7876, "name": "ADAMS", "salary": 1100},
    --     ...
    --   ]
    -- }
    
    v_json := apex_json.get_clob_output;
    apex_json.free_output;
end;

Ejemplo: Array de valores simples:

declare
begin
    apex_json.initialize_clob_output;
    
    apex_json.open_object;
    apex_json.write('id', 1);
    
    -- Array de números
    apex_json.open_array('scores');
    apex_json.write(95);
    apex_json.write(87);
    apex_json.write(92);
    apex_json.close_array;
    
    apex_json.close_object;
    
    -- JSON: {"id":1,"scores":[95,87,92]}
end;

Limitaciones y consideraciones:

-- ✅ Arrays pueden anidar
apex_json.open_array;
  apex_json.open_array;    -- Array dentro de array
    apex_json.write(1);
    apex_json.write(2);
  apex_json.close_array;
apex_json.close_array;
-- Resultado: [[1,2]]

-- ⚠️ Para arrays > 100K elementos, usa streaming
-- En CLOB mode (initialize_clob_output), > 100K elementos
-- puede causar memory issues en algunos servidores

GRUPO 3: ESCRITURA DE VALORES

apex_json.write(...) (LA FUNCIÓN MÁS IMPORTANTE)

¿Qué hace? Escribe un valor (de cualquier tipo soportado) al JSON. Es la función que más usarás.

Parámetros:

apex_json.write(
    p_name  varchar2,    -- Clave (requerida si en objeto, ignorada si en array)
    p_value <tipo>       -- Valor: VARCHAR2, NUMBER, DATE, BOOLEAN, CLOB, etc.
)

Tipos soportados y conversión:

-- VARCHAR2 → String JSON
apex_json.write('name', 'Juan García');
-- Resultado: "name":"Juan García"

-- NUMBER → Number JSON (sin comillas)
apex_json.write('salary', 50000);
apex_json.write('percentage', 99.99);
-- Resultado: "salary":50000, "percentage":99.99

-- DATE → String ISO 8601
apex_json.write('hire_date', to_date('2020-01-15', 'YYYY-MM-DD'));
-- Resultado: "hire_date":"2020-01-15T00:00:00Z"

-- TIMESTAMP
apex_json.write('updated_at', systimestamp);
-- Resultado: "updated_at":"2024-02-16T10:30:45.123456Z"

-- BOOLEAN (PL/SQL true/false) → JSON boolean (sin comillas)
apex_json.write('is_active', true);
apex_json.write('is_deleted', false);
-- Resultado: "is_active":true, "is_deleted":false

-- NULL → JSON null (sin comillas)
declare
    v_commission number := null;
begin
    apex_json.write('commission', v_commission);
    -- Resultado: "commission":null
end;

-- CLOB con caracteres especiales
declare
    v_text clob := 'Línea 1' || chr(10) || 'Línea 2';
begin
    apex_json.write('description', v_text);
    -- Resultado: "description":"Línea 1\nLínea 2"
    -- chr(10) se convierte a \n (escapado)
end;

Escapado automático de caracteres especiales:

APEX_JSON escapa automáticamente caracteres que rompen JSON:

-- Comillas dobles
declare
    v_text varchar2(100) := 'Ella dijo "Hola"';
begin
    apex_json.write('quote', v_text);
    -- Resultado: "quote":"Ella dijo \"Hola\""
end;

-- Barra invertida
declare
    v_path varchar2(100) := 'C:\Users\Juan';
begin
    apex_json.write('filepath', v_path);
    -- Resultado: "filepath":"C:\\Users\\Juan"
end;

-- Control characters (newline, tab, etc.)
declare
    v_text varchar2(100) := 'Tab:' || chr(9) || 'Value';
begin
    apex_json.write('data', v_text);
    -- Resultado: "data":"Tab:\tValue"
end;

-- Caracteres Unicode especiales
declare
    v_emoji varchar2(100) := '🎉 Celebration 🎊';
begin
    apex_json.write('message', v_emoji);
    -- Resultado: "message":"🎉 Celebration 🎊"
    -- Unicode se preserva correctamente
end;

Detalles técnicos de write():

-- Si p_name es NULL y estás en un ARRAY, se agrega como elemento
apex_json.open_array;
apex_json.write(100);      -- p_name implícito NULL
apex_json.write(200);
apex_json.close_array;
-- Resultado: [100,200]

-- Si p_name es NULL y estás en un OBJETO, da error
apex_json.open_object;
apex_json.write(null, 'valor');  -- ← Error: p_name requerida en objeto
apex_json.close_object;

-- Los NULL en PL/SQL se convierten a null JSON
declare
    v_var varchar2(100) := null;
begin
    apex_json.write('field', v_var);
    -- Resultado: "field":null  (no "" vacío)
end;

-- Valores muy grandes (> 32KB) se pueden escribir como CLOB
declare
    v_large clob := rpad('x', 50000, 'x');  -- 50KB
begin
    apex_json.write('big_text', v_large);  -- APEX_JSON maneja esto
end;

GRUPO 4: PARSEO Y LECTURA

apex_json.parse(...)

¿Qué hace exactamente? Convierte un JSON string en una estructura interna (apex_json.t_values) que APEX_JSON puede navegar. Esto es el primer paso obligatorio para leer JSON.

Parámetros:

apex_json.parse(
    p_values in out apex_json.t_values,  -- Estructura destino (se llena con el árbol JSON)
    p_source in     clob                  -- JSON a parsear (puede ser VARCHAR2 también)
)

Retorna: Void (llena p_values)

¿Cuándo usar?

  • Cuando recibes JSON de un API externo
  • Cuando deserializas JSON almacenado en base de datos
  • Cuando necesitas procesar JSON dinámico con estructura variable

Ejemplo básico:

declare
    l_json clob := '{"name":"Juan","age":30,"city":"Madrid"}';
    l_values apex_json.t_values;
begin
    -- Parsear: convierte JSON string → estructura interna
    apex_json.parse(
        p_values => l_values,
        p_source => l_json
    );
    
    -- Ahora l_values contiene el árbol JSON parseado
    -- Puedes navegar con get_varchar2, get_number, get_count, etc.
end;

Manejo de errores en parseo:

declare
    l_json clob := '{"broken json...}';  -- JSON malformado
    l_values apex_json.t_values;
begin
    apex_json.parse(p_values => l_values, p_source => l_json);
exception
    when apex_json.invalid_json then
        dbms_output.put_line('ERROR: JSON inválido');
        dbms_output.put_line('Detalles: ' || sqlerrm);
    when others then
        dbms_output.put_line('ERROR inesperado: ' || sqlerrm);
end;

Detalles técnicos:

  • t_values es un tipo de tabla PL/SQL que APEX_JSON llena internamente
  • El JSON parseado persiste en l_values hasta fin del bloque
  • Si el JSON es inválido, lanza apex_json.invalid_json (excepción)
  • Después de parsear, accedes a valores usando rutas (paths)

apex_json.get_count(...)

¿Qué hace? Cuenta elementos en un array u objetos en un objeto. Fundamental para iterar sobre estructuras dinámicas.

Parámetros:

apex_json.get_count(
    p_path      varchar2,           -- Ruta (ej: '.', '[0]', 'employees')
    p_values    apex_json.t_values  -- Estructura parseada
) return number

Retorna: NUMBER (cantidad de elementos)

Ejemplos de rutas:

declare
    l_json clob := '{
        "id": 1,
        "name": "Juan",
        "employees": [
            {"id":101, "name":"Alice"},
            {"id":102, "name":"Bob"}
        ],
        "address": {"street": "Main St", "city": "NYC"}
    }';
    l_values apex_json.t_values;
    l_count number;
begin
    apex_json.parse(p_values => l_values, p_source => l_json);
    
    -- Contar propiedades del objeto raíz
    l_count := apex_json.get_count(p_path => '.', p_values => l_values);
    dbms_output.put_line('Propiedades raíz: ' || l_count);  -- 4 (id, name, employees, address)
    
    -- Contar elementos del array 'employees'
    l_count := apex_json.get_count(p_path => 'employees', p_values => l_values);
    dbms_output.put_line('Empleados: ' || l_count);  -- 2
    
    -- Contar propiedades de un objeto anidado
    l_count := apex_json.get_count(p_path => 'address', p_values => l_values);
    dbms_output.put_line('Propiedades dirección: ' || l_count);  -- 2 (street, city)
end;

Uso para iteración:

declare
    l_json clob := '[{"id":1},{"id":2},{"id":3}]';
    l_values apex_json.t_values;
    l_count number;
begin
    apex_json.parse(p_values => l_values, p_source => l_json);
    l_count := apex_json.get_count(p_path => '.', p_values => l_values);
    
    -- Iterar 3 veces
    for i in 1..l_count loop
        dbms_output.put_line('Item ' || i);
    end loop;
end;

Detalles técnicos:

  • Si la ruta no existe, retorna 0 (no error)
  • Para objetos, cuenta las propiedades
  • Para arrays, cuenta los elementos
  • Para valores simples, retorna 0

apex_json.get_varchar2(...)

¿Qué hace? Extrae un valor STRING del JSON parseado. Convierte automáticamente otros tipos a texto.

Parámetros:

apex_json.get_varchar2(
    p_path      varchar2,           -- Ruta al valor
    p_values    apex_json.t_values, -- Estructura parseada
    p_default   varchar2 := null    -- Fallback si no existe
) return varchar2

Ejemplo básico:

declare
    l_json clob := '{"name":"Juan García","email":"juan@company.com"}';
    l_values apex_json.t_values;
begin
    apex_json.parse(p_values => l_values, p_source => l_json);
    
    -- Extraer valor con ruta simple
    dbms_output.put_line(apex_json.get_varchar2(
        p_path => 'name',
        p_values => l_values
    ));  -- Imprime: Juan García
end;

Rutas dinámicas (arrays):

declare
    l_json clob := '[
        {"id":1, "name":"Juan", "active":true},
        {"id":2, "name":"María", "active":false}
    ]';
    l_values apex_json.t_values;
    l_count number;
begin
    apex_json.parse(p_values => l_values, p_source => l_json);
    l_count := apex_json.get_count(p_path => '.', p_values => l_values);
    
    -- Iterar y extraer nombre de cada elemento
    for i in 1..l_count loop
        dbms_output.put_line(apex_json.get_varchar2(
            p_path => '[%d].name',    -- %d se reemplaza con p0
            p0 => i,
            p_values => l_values
        ));
    end loop;
end;

Manejo de NULL y defaults:

declare
    l_json clob := '{"name":"Juan","nickname":null}';
    l_values apex_json.t_values;
begin
    apex_json.parse(p_values => l_values, p_source => l_json);
    
    -- Sin default: retorna NULL si no existe
    if apex_json.get_varchar2(p_path => 'nickname', p_values => l_values) is null then
        dbms_output.put_line('nickname es NULL');
    end if;
    
    -- Con default: retorna default si no existe o es NULL
    dbms_output.put_line(apex_json.get_varchar2(
        p_path => 'nickname',
        p_values => l_values,
        p_default => 'Sin apodo'
    ));  -- Imprime: Sin apodo
end;

Conversiones automáticas:

declare
    l_json clob := '{"salary":50000, "active":true}';
    l_values apex_json.t_values;
begin
    apex_json.parse(p_values => l_values, p_source => l_json);
    
    -- Número convertido a string
    dbms_output.put_line(apex_json.get_varchar2(p_path => 'salary', p_values => l_values));
    -- Resultado: "50000" (como string)
    
    -- Boolean convertido a string
    dbms_output.put_line(apex_json.get_varchar2(p_path => 'active', p_values => l_values));
    -- Resultado: "true" (como string)
end;

apex_json.get_number(...)

¿Qué hace? Extrae valores NUMÉRICOS del JSON. Convierte strings numéricos a números.

Parámetros:

apex_json.get_number(
    p_path      varchar2,           -- Ruta
    p_values    apex_json.t_values, -- Estructura
    p_default   number := null      -- Fallback
) return number

Ejemplo:

declare
    l_json clob := '[
        {"id":101, "salary":50000, "bonus":5000},
        {"id":102, "salary":60000, "bonus":8000},
        {"id":103, "salary":55000, "bonus":null}
    ]';
    l_values apex_json.t_values;
    l_count number;
    l_total_compensation number;
begin
    apex_json.parse(p_values => l_values, p_source => l_json);
    l_count := apex_json.get_count(p_path => '.', p_values => l_values);
    
    for i in 1..l_count loop
        l_total_compensation := 
            apex_json.get_number(p_path => '[%d].salary', p0 => i, p_values => l_values) +
            nvl(apex_json.get_number(p_path => '[%d].bonus', p0 => i, p_values => l_values), 0);
        
        dbms_output.put_line('Employee ' || i || ' total: ' || l_total_compensation);
    end loop;
end;

PARTE 3: EJEMPLO 1 COMPLETO – GEOJSON AVANZADO

-- Tabla de ubicaciones
create table warehouses (
    id number generated by default as identity primary key,
    warehouse_name varchar2(100) not null,
    latitude number,
    longitude number,
    category varchar2(20),  -- 'vip', 'standard', 'regional'
    capacity number,
    manager_name varchar2(100),
    active_yn char(1) default 'Y'
);

-- Función que genera GeoJSON
create or replace function get_warehouses_geojson 
    return clob 
is
    v_json clob;
    l_color varchar2(10);
    l_icon varchar2(30);
begin
    apex_json.initialize_clob_output;
    
    apex_json.open_object;
    apex_json.write('type', 'FeatureCollection');
    apex_json.write('name', 'Global Warehouse Network');
    apex_json.write('generated_at', to_char(systimestamp, 'YYYY-MM-DD"T"HH24:MI:SS"Z"'));
    
    apex_json.open_array('features');
    
    for w in (
        select id, warehouse_name, latitude, longitude, category, capacity, manager_name
        from warehouses
        where active_yn = 'Y'
    ) loop
        apex_json.open_object;
        apex_json.write('type', 'Feature');
        apex_json.write('id', w.id);
        
        -- Geometría (GeoJSON: [lon, lat])
        apex_json.open_object('geometry');
        apex_json.write('type', 'Point');
        apex_json.open_array('coordinates');
        apex_json.write(w.longitude);
        apex_json.write(w.latitude);
        apex_json.close_array;
        apex_json.close_object;
        
        -- Propiedades
        apex_json.open_object('properties');
        apex_json.write('name', w.warehouse_name);
        apex_json.write('manager', w.manager_name);
        apex_json.write('capacity', w.capacity);
        
        -- Colores dinámicos
        case w.category
            when 'vip' then
                l_color := '#FFD700';
                l_icon := 'star.png';
            when 'regional' then
                l_color := '#87CEEB';
                l_icon := 'circle.png';
            else
                l_color := '#90EE90';
                l_icon := 'marker.png';
        end case;
        
        apex_json.write('icon', l_icon);
        apex_json.write('color', l_color);
        apex_json.write('category', w.category);
        apex_json.close_object;
        
        apex_json.close_object;
    end loop;
    
    apex_json.close_array;
    apex_json.close_object;
    
    v_json := apex_json.get_clob_output;
    apex_json.free_output;
    
    return v_json;
exception
    when others then
        apex_json.free_output;
        raise;
end get_warehouses_geojson;

PARTE 4: EJEMPLO 2 – STREAMING DE 1M DE FILAS

create or replace procedure stream_large_dataset as
    nm owa.vc_arr;
    vl owa.vc_arr;
    l_rows integer := 0;
begin
    nm(1) := 'dummy';
    vl(1) := 'dummy';
    owa.init_cgi_env(1, nm, vl);

    apex_json.initialize_output;

    apex_json.open_object;
    apex_json.write('status', 'streaming');
    apex_json.write('started_at', systimestamp);
    apex_json.open_array('transactions');
    
    for c in (
        select id, amount, created_at, status 
        from large_transactions_table
    ) loop
        apex_json.open_object;
        apex_json.write('id', c.id);
        apex_json.write('amount', c.amount);
        apex_json.write('ts', to_char(c.created_at, 'YYYY-MM-DD"T"HH24:MI:SS"Z"'));
        apex_json.write('status', c.status);
        apex_json.close_object;
        
        l_rows := l_rows + 1;
        
        if mod(l_rows, 100000) = 0 then
            dbms_output.put_line('Streamed ' || l_rows || ' rows');
        end if;
    end loop;
    
    apex_json.close_array;
    apex_json.open_object('summary');
    apex_json.write('total_rows', l_rows);
    apex_json.write('completed_at', systimestamp);
    apex_json.close_object;
    apex_json.close_object;

exception 
    when others then
        dbms_output.put_line('ERROR: ' || sqlerrm);
end stream_large_dataset;

PARTE 5: OPTIMIZACIÓN Y MEJORES PRÁCTICAS

1. Memory Management (CRÍTICO)

-- ❌ MALO: Memory leak
declare
begin
    for i in 1..10000 loop
        apex_json.initialize_clob_output;
        apex_json.open_object;
        apex_json.write('iteration', i);
        apex_json.close_object;
        -- FALTA free_output → 10,000 buffers en memoria
    end loop;
end;

-- ✅ BUENO: Libera cada iteración
declare
    v_json clob;
begin
    for i in 1..10000 loop
        apex_json.initialize_clob_output;
        apex_json.open_object;
        apex_json.write('iteration', i);
        apex_json.close_object;
        
        v_json := apex_json.get_clob_output;
        -- Procesar v_json...
        
        apex_json.free_output;  -- ✅ Liberar
    end loop;
end;

2. Manejo de rutas dinámicas

-- Para arrays, siempre usa rutas parametrizadas
for i in 1..l_count loop
    -- ✅ Eficiente: Ruta parametrizada
    l_value := apex_json.get_varchar2(
        p_path => '[%d].field_name',
        p0 => i,
        p_values => l_values
    );
end loop;

3. Validación de JSON entrante

create or replace function is_valid_json(p_json clob) return boolean is
    l_values apex_json.t_values;
begin
    apex_json.parse(p_values => l_values, p_source => p_json);
    return true;
exception
    when apex_json.invalid_json then
        return false;
end is_valid_json;

CONCLUSIÓN

Con esta guía completa, ahora entiendes:

✅ Exactamente qué hace cada función

✅ Cuándo usar CLOB vs Streaming

✅ Cómo generar GeoJSON válido

✅ Cómo procesar JSON dinámico

✅ Memory management crítico

✅ Patrones de producción

APEX_JSON no es opcional, es fundamental para aplicaciones APEX modernas.

Próximos pasos:

  1. Prueba estos ejemplos exactos en tu entorno
  2. Implementa en tus stored procedures
  3. Monitorea memory en producción

¿Dudas sobre APEX_JSON? Déjame un comentario.

Si quieres aprender más sobre integración de sistemas, te invito a revisar mi curso de ORDS

https://app.aprendiz.academy/ords/r/aprendiz/aprendiz-academy/detalles-del-curso?g_course_selected=62

Post a Comment

Your email address will not be published. Required fields are marked *