Domina APEX_JSON en Oracle APEX 24: Genera JSON como un Profesional
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_targetdel 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 usarinitialize_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_valueses un tipo de tabla PL/SQL que APEX_JSON llena internamente- El JSON parseado persiste en
l_valueshasta 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:
- Prueba estos ejemplos exactos en tu entorno
- Implementa en tus stored procedures
- 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

Social List