Soporte JSON en Delphi: Guía Completa con Ejemplos (2025)
JSON (JavaScript Object Notation) es el estándar de facto para el intercambio de datos en aplicaciones modernas. Ya sea que estés construyendo APIs REST, leyendo archivos de configuración o comunicándote con servicios web, entender cómo trabajar con JSON en Delphi es esencial.
Esta guía completa cubre todo lo que necesitas saber sobre el soporte de JSON en Delphi, con ejemplos completos y compilables que puedes usar en tus proyectos.
TJSONObject, TJSONArray). Para análisis en modo streaming/SAX, consulta las unidades System.JSON.Readers y System.JSON.Writers.
Compatibilidad de Versiones de Delphi
El soporte JSON ha evolucionado significativamente a través de las versiones de Delphi:
| Versión | Unidad | Características Clave |
|---|---|---|
| Delphi 2009 | DBXJSON |
Soporte JSON inicial con clases básicas |
| Delphi XE6 | System.JSON |
Unidad renombrada, API mejorada |
| Delphi 10.1 Berlin | System.JSON |
API fluida con TJSONObjectBuilder, mejoras en TryGetValue<T> |
| Delphi 10.3 Rio | System.JSON |
Método Format(), EJSONParseException con detalles, mejoras de rendimiento |
| Delphi 11-12 | System.JSON |
Optimizaciones adicionales y refinamientos |
| Delphi 13 Florence | System.JSON |
Últimas mejoras y soporte continuo |
¿Qué es JSON?
JSON es un formato ligero de intercambio de datos basado en texto. Es fácil de leer y escribir para los humanos, y fácil de analizar y generar para las máquinas. Un documento JSON puede contener:
- Objetos: Pares clave-valor encerrados entre llaves
{} - Arrays: Listas ordenadas de valores encerradas entre corchetes
[] - Valores: Cadenas, números, booleanos (
true/false),null, objetos o arrays
Ejemplo de estructura JSON:
{
"name": "Daniele Teti",
"age": 45,
"active": true,
"skills": ["Delphi", "Python", "SQL"],
"address": {
"city": "Rome",
"country": "Italy"
}
}
Descripción General de las Clases JSON de Delphi
Delphi proporciona soporte JSON integrado a través de la unidad System.JSON. Las clases principales son:
| Clase | Descripción |
|---|---|
TJSONValue |
Clase base para todos los tipos de valores JSON |
TJSONObject |
Representa un objeto JSON (pares clave-valor) |
TJSONArray |
Representa un array JSON (lista ordenada) |
TJSONString |
Representa un valor de cadena JSON |
TJSONNumber |
Representa un valor numérico JSON |
TJSONBool |
Representa un valor booleano JSON |
TJSONNull |
Representa un valor null JSON |
TJSONPair |
Representa un par clave-valor en un objeto |
Creación de Objetos JSON
Comencemos con lo básico: crear objetos JSON y agregar propiedades.
Creación Básica de Objetos JSON
La operación más fundamental es crear un TJSONObject y agregarle pares clave-valor. Delphi proporciona sobrecargas convenientes de AddPair que aceptan cadenas, enteros, booleanos y doubles directamente - no es necesario envolver valores primitivos en clases específicas de JSON. El siguiente ejemplo demuestra cómo construir un objeto JSON simple que contiene información personal con varios tipos de datos:
program JSONCreateBasic;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
begin
LJSONObject := TJSONObject.Create;
try
// Agregar propiedad de cadena
LJSONObject.AddPair('firstName', 'Daniele');
LJSONObject.AddPair('lastName', 'Teti');
// Agregar propiedad numérica (sobrecargas disponibles para Integer, Int64, Double)
LJSONObject.AddPair('age', 45);
// Agregar propiedad booleana
LJSONObject.AddPair('active', True);
// Agregar propiedad null (sin sobrecarga - debe usar TJSONNull)
LJSONObject.AddPair('middleName', TJSONNull.Create);
// Mostrar el JSON
// Nota: Format() disponible desde Delphi 10.3 Rio
{$IF CompilerVersion >= 33.0} // Delphi 10.3 Rio
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free;
end;
ReadLn;
end.
Salida:
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"middleName": null
}
Creación de Arrays JSON
Los arrays JSON son colecciones ordenadas que pueden contener cualquier combinación de valores - cadenas, números, booleanos o incluso otros arrays y objetos. Cuando agregas un TJSONArray a un TJSONObject usando AddPair, el objeto padre toma posesión del array, por lo que solo necesitas liberar el objeto raíz. Este ejemplo muestra cómo crear tanto un array homogéneo de cadenas como un array de tipo mixto:
program JSONCreateArray;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LContacts: TJSONArray;
LSkills: TJSONArray;
begin
LJSONObject := TJSONObject.Create;
try
LJSONObject.AddPair('name', 'Daniele Teti');
// Crear array de cadenas
LSkills := TJSONArray.Create;
LJSONObject.AddPair('skills', LSkills);
LSkills.Add('Delphi');
LSkills.Add('Python');
LSkills.Add('SQL');
// Crear array con tipos mixtos
LContacts := TJSONArray.Create;
LJSONObject.AddPair('contacts', LContacts);
LContacts.Add('daniele@example.com'); // cadena
LContacts.Add(123456); // número
LContacts.Add(True); // booleano
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free; // También libera LContacts y LSkills
end;
ReadLn;
end.
Salida:
{
"name": "Daniele Teti",
"skills": [
"Delphi",
"Python",
"SQL"
],
"contacts": [
"daniele@example.com",
123456,
true
]
}
Creación de un Array de Objetos
Uno de los patrones más comunes en JSON del mundo real es un array que contiene múltiples objetos - piensa en una lista de usuarios, productos o cualquier colección de registros. Cada objeto en el array puede tener su propio conjunto de propiedades. Al construir esta estructura, creas cada objeto por separado y lo agregas al array usando el método Add. El array entonces posee todos los objetos que le agregues:
program JSONArrayOfObjects;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LRoot: TJSONObject;
LUsers: TJSONArray;
LUser: TJSONObject;
begin
LRoot := TJSONObject.Create;
try
LUsers := TJSONArray.Create;
LRoot.AddPair('users', LUsers);
// Primer usuario
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 1);
LUser.AddPair('name', 'Alice');
LUser.AddPair('email', 'alice@example.com');
// Segundo usuario
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 2);
LUser.AddPair('name', 'Bob');
LUser.AddPair('email', 'bob@example.com');
// Tercer usuario
LUser := TJSONObject.Create;
LUsers.Add(LUser);
LUser.AddPair('id', 3);
LUser.AddPair('name', 'Charlie');
LUser.AddPair('email', 'charlie@example.com');
{$IF CompilerVersion >= 33.0}
WriteLn(LRoot.Format());
{$ELSE}
WriteLn(LRoot.ToString);
{$ENDIF}
finally
LRoot.Free;
end;
ReadLn;
end.
Salida:
{
"users": [
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
},
{
"id": 2,
"name": "Bob",
"email": "bob@example.com"
},
{
"id": 3,
"name": "Charlie",
"email": "charlie@example.com"
}
]
}
Objetos JSON Anidados
Los datos complejos a menudo requieren organización jerárquica - una persona tiene una dirección, una dirección tiene ciudad y país, y así sucesivamente. En Delphi, creas estructuras anidadas agregando instancias de TJSONObject como valores dentro de otros objetos. Al igual que con los arrays, el objeto padre toma posesión de sus hijos, simplificando la gestión de memoria. Este ejemplo crea una persona con objetos anidados de dirección y empresa:
program JSONNested;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LAddress: TJSONObject;
LCompany: TJSONObject;
begin
LJSONObject := TJSONObject.Create;
try
LJSONObject.AddPair('name', 'Daniele Teti');
// Crear objeto de dirección anidado
LAddress := TJSONObject.Create;
LJSONObject.AddPair('address', LAddress);
LAddress.AddPair('street', 'Via Roma 123');
LAddress.AddPair('city', 'Rome');
LAddress.AddPair('country', 'Italy');
LAddress.AddPair('zipCode', '00100');
// Crear otro objeto anidado
LCompany := TJSONObject.Create;
LJSONObject.AddPair('company', LCompany);
LCompany.AddPair('name', 'bit Time Professionals');
LCompany.AddPair('website', 'https://www.bittime.it');
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONObject.Free;
end;
ReadLn;
end.
Salida:
{
"name": "Daniele Teti",
"address": {
"street": "Via Roma 123",
"city": "Rome",
"country": "Italy",
"zipCode": "00100"
},
"company": {
"name": "bit Time Professionals",
"website": "https://www.bittime.it"
}
}
Uso de TJSONObjectBuilder (Delphi 10.1 Berlin+)
Si prefieres una sintaxis más declarativa y encadenable para construir JSON, Delphi 10.1 Berlin introdujo TJSONObjectBuilder. Esta API fluida te permite construir estructuras JSON complejas en una sola expresión usando encadenamiento de métodos con llamadas a BeginObject, BeginArray, Add y EndObject/EndArray. El constructor escribe en un TJsonTextWriter, que genera salida a un TStringBuilder. Aunque más verbosa en la configuración, este enfoque produce código más limpio y legible para estructuras complejas:
program JSONBuilderExample;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.Classes,
System.JSON.Types,
System.JSON.Writers,
System.JSON.Builders;
var
LBuilder: TJSONObjectBuilder;
LWriter: TJsonTextWriter;
LStringWriter: TStringWriter;
LStringBuilder: TStringBuilder;
begin
LStringBuilder := TStringBuilder.Create;
try
LStringWriter := TStringWriter.Create(LStringBuilder);
try
LWriter := TJsonTextWriter.Create(LStringWriter);
try
LWriter.Formatting := TJsonFormatting.Indented;
LBuilder := TJSONObjectBuilder.Create(LWriter);
try
// Construir JSON usando API fluida
LBuilder
.BeginObject
.Add('firstName', 'Daniele')
.Add('lastName', 'Teti')
.Add('age', 45)
.Add('active', True)
.BeginObject('address')
.Add('city', 'Rome')
.Add('country', 'Italy')
.EndObject
.BeginArray('skills')
.Add('Delphi')
.Add('Python')
.Add('SQL')
.EndArray
.EndObject;
WriteLn(LStringBuilder.ToString);
finally
LBuilder.Free;
end;
finally
LWriter.Free;
end;
finally
LStringWriter.Free;
end;
finally
LStringBuilder.Free;
end;
ReadLn;
end.
Salida:
{
"firstName": "Daniele",
"lastName": "Teti",
"age": 45,
"active": true,
"address": {
"city": "Rome",
"country": "Italy"
},
"skills": [
"Delphi",
"Python",
"SQL"
]
}
Análisis de Cadenas JSON
Cuando recibes datos JSON de un servicio web, archivo o cualquier otra fuente, necesitas analizarlos en objetos Delphi con los que puedas trabajar. El método de clase TJSONObject.ParseJSONValue maneja esta conversión. Devuelve un TJSONValue (la clase base), por lo que necesitarás verificar si es del tipo esperado - típicamente TJSONObject o TJSONArray. Si el JSON está mal formado, el método devuelve nil, así que siempre verifica esto antes de continuar:
program JSONParsing;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_STRING =
'{"name":"Daniele","age":45,"skills":["Delphi","Python"]}';
var
LJSONValue: TJSONValue;
LJSONObject: TJSONObject;
begin
// ParseJSONValue devuelve TJSONValue, convertir al tipo apropiado
LJSONValue := TJSONObject.ParseJSONValue(JSON_STRING);
if LJSONValue = nil then
begin
WriteLn('ERROR: ¡JSON inválido!');
ReadLn;
Exit;
end;
try
// Verificar si es un objeto (podría ser un array en el nivel raíz)
if not (LJSONValue is TJSONObject) then
begin
WriteLn('ERROR: Se esperaba un objeto JSON en el nivel raíz');
Exit;
end;
LJSONObject := TJSONObject(LJSONValue); // Conversión directa - segura después de verificar "is"
WriteLn('¡Analizado exitosamente!');
WriteLn('Número de pares: ', LJSONObject.Count);
{$IF CompilerVersion >= 33.0}
WriteLn(LJSONObject.Format());
{$ELSE}
WriteLn(LJSONObject.ToString);
{$ENDIF}
finally
LJSONValue.Free;
end;
ReadLn;
end.
ParseJSONValue devuelve nil, lo cual indica JSON inválido.
Manejo de Errores de Análisis (Delphi 10.3+)
Cuando el análisis falla, saber por qué falló ayuda tremendamente a la depuración. Comenzando desde Delphi 10.3 Rio, puedes pasar True como segundo parámetro a ParseJSONValue para hacer que lance una EJSONParseException en lugar de devolver nil. Esta excepción incluye el mensaje de error, la ruta donde falló el análisis y el desplazamiento de caracteres - información invaluable cuando se trabaja con JSON complejo o proporcionado externamente:
program JSONParseErrors;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
INVALID_JSON = '{"name": "Test", "value": }'; // ¡Inválido!
var
LJSONValue: TJSONValue;
begin
{$IF CompilerVersion >= 33.0} // Delphi 10.3 Rio
try
// Usar opción RaiseExc para obtener excepción con detalles
LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON, True);
try
WriteLn('Analizado: ', LJSONValue.ToString);
finally
LJSONValue.Free;
end;
except
on E: EJSONParseException do
begin
WriteLn('¡Error de análisis!');
WriteLn(' Mensaje: ', E.Message);
WriteLn(' Ruta: ', E.Path);
WriteLn(' Desplazamiento: ', E.Offset);
end;
end;
{$ELSE}
// Pre-10.3: solo verificar nil
LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON);
if LJSONValue = nil then
WriteLn('JSON inválido - no hay detalles disponibles')
else
LJSONValue.Free;
{$ENDIF}
ReadLn;
end.
Lectura de Valores JSON
Delphi proporciona múltiples formas de leer valores de objetos JSON, cada una con diferentes compromisos entre conveniencia y seguridad. Entender cuándo usar cada enfoque te ayudará a escribir código más robusto que maneje datos faltantes o inesperados con elegancia.
Método 1: GetValue con Tipo Genérico (Delphi XE7+)
La forma más simple de leer un valor es usando el método genérico GetValue<T>. Especificas el tipo esperado como parámetro de tipo, y Delphi maneja la conversión automáticamente. Sin embargo, este método lanza una excepción si la clave no existe, así que úsalo solo cuando estés seguro de que la clave está presente:
program JSONReadGetValue;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"name":"Daniele","age":45,"active":true}';
var
LJSONObject: TJSONObject;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// GetValue<T> - lanza excepción si la clave no se encuentra
WriteLn('Nombre: ', LJSONObject.GetValue<string>('name'));
WriteLn('Edad: ', LJSONObject.GetValue<Integer>('age'));
WriteLn('Activo: ', LJSONObject.GetValue<Boolean>('active'));
finally
LJSONObject.Free;
end;
ReadLn;
end.
Método 2: TryGetValue - Lectura Segura (Recomendado)
Para código de producción, TryGetValue<T> es el enfoque recomendado. Devuelve False si la clave falta o el valor no puede ser convertido al tipo solicitado, permitiéndote manejar datos faltantes sin manejo de excepciones. Esto es particularmente útil cuando se analiza JSON de fuentes externas donde no puedes garantizar que todos los campos estén presentes:
program JSONReadTryGetValue;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"name":"Daniele","age":45}';
var
LJSONObject: TJSONObject;
LName: string;
LAge: Integer;
LMiddleName: string;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// TryGetValue devuelve False si la clave no se encuentra (sin excepción)
if LJSONObject.TryGetValue<string>('name', LName) then
WriteLn('Nombre: ', LName)
else
WriteLn('Nombre no encontrado');
if LJSONObject.TryGetValue<Integer>('age', LAge) then
WriteLn('Edad: ', LAge)
else
WriteLn('Edad no encontrada');
// Esta clave no existe - no se lanza excepción
if LJSONObject.TryGetValue<string>('middleName', LMiddleName) then
WriteLn('Segundo Nombre: ', LMiddleName)
else
WriteLn('Segundo Nombre: (no especificado)');
finally
LJSONObject.Free;
end;
ReadLn;
end.
Método 3: FindValue - Devuelve nil si No se Encuentra
Cuando necesitas acceso al objeto TJSONValue sin procesar en lugar de un valor convertido, usa FindValue. Este método devuelve nil si la clave no existe, nunca lanza una excepción y te da acceso completo a las propiedades y métodos del valor JSON. Es útil cuando necesitas verificar el tipo real de un valor o cuando trabajas con estructuras anidadas complejas:
program JSONReadFindValue;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"name":"Daniele","age":45}';
var
LJSONObject: TJSONObject;
LValue: TJSONValue;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// FindValue devuelve nil si no se encuentra (nunca lanza excepción)
LValue := LJSONObject.FindValue('name');
if LValue <> nil then
WriteLn('Nombre: ', LValue.Value);
LValue := LJSONObject.FindValue('nonexistent');
if LValue = nil then
WriteLn('Clave "nonexistent" no encontrada');
finally
LJSONObject.Free;
end;
ReadLn;
end.
Método 4: Notación de Ruta para Valores Anidados
Una de las características más convenientes de Delphi es la notación de ruta - puedes acceder a valores profundamente anidados usando rutas separadas por puntos como 'person.address.city' en lugar de navegar a través de múltiples objetos intermedios. Esto funciona con TryGetValue, GetValue y FindValue, haciendo mucho más fácil extraer valores específicos de estructuras JSON complejas sin escribir código de navegación verboso:
program JSONReadPath;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{' +
'"person": {' +
' "name": "Daniele",' +
' "address": {' +
' "city": "Rome",' +
' "country": "Italy"' +
' }' +
'}' +
'}';
var
LJSONObject: TJSONObject;
LValue: string;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// Usar notación de puntos para acceder a valores anidados
if LJSONObject.TryGetValue<string>('person.name', LValue) then
WriteLn('Nombre de Persona: ', LValue);
if LJSONObject.TryGetValue<string>('person.address.city', LValue) then
WriteLn('Ciudad: ', LValue);
if LJSONObject.TryGetValue<string>('person.address.country', LValue) then
WriteLn('País: ', LValue);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Lectura de Arrays - Enfoques Clásico y Moderno
Cuando tu JSON contiene arrays, necesitarás iterar sobre sus elementos para procesar cada valor. Delphi soporta tanto bucles tradicionales basados en índices usando Items[I] como la sintaxis más moderna for-in que funciona con cualquier TJSONArray. El enfoque for-in es más limpio cuando no necesitas el índice, mientras que el bucle clásico te da acceso a la posición. Para arrays numéricos, convierte cada elemento a TJSONNumber para acceder a métodos como AsInt o AsDouble:
program JSONReadArrays;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"skills":["Delphi","Python","SQL"],"scores":[95,87,92]}';
var
LJSONObject: TJSONObject;
LSkills: TJSONArray;
LScores: TJSONArray;
LItem: TJSONValue;
I: Integer;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
// Leer array de cadenas - bucle for clásico
if LJSONObject.TryGetValue<TJSONArray>('skills', LSkills) then
begin
WriteLn('Habilidades (bucle clásico):');
for I := 0 to LSkills.Count - 1 do
WriteLn(' ', I + 1, '. ', LSkills.Items[I].Value);
end;
WriteLn;
// Leer array de cadenas - bucle for-in moderno (Delphi XE+)
if LJSONObject.TryGetValue<TJSONArray>('skills', LSkills) then
begin
WriteLn('Habilidades (bucle for-in):');
for LItem in LSkills do
WriteLn(' - ', LItem.Value);
end;
WriteLn;
// Leer array numérico
if LJSONObject.TryGetValue<TJSONArray>('scores', LScores) then
begin
WriteLn('Puntuaciones:');
for LItem in LScores do
WriteLn(' Puntuación: ', (LItem as TJSONNumber).AsInt);
end;
finally
LJSONObject.Free;
end;
ReadLn;
end.
Iteración Sobre Pares de Objetos JSON
A veces necesitas procesar todas las propiedades en un objeto JSON sin conocer los nombres de las claves de antemano - por ejemplo, cuando construyes un visor JSON genérico o cuando la estructura es dinámica. El bucle for-in funciona en TJSONObject al igual que en arrays, produciendo instancias de TJSONPair. Cada par te da acceso tanto a la clave (a través de JsonString.Value) como al valor (a través de JsonValue), además puedes inspeccionar el tipo en tiempo de ejecución usando ClassName:
program JSONIteratePairs;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
const
JSON_DATA = '{"name":"Daniele","age":45,"city":"Rome","active":true}';
var
LJSONObject: TJSONObject;
LPair: TJSONPair;
begin
LJSONObject := TJSONObject.ParseJSONValue(JSON_DATA) as TJSONObject;
try
WriteLn('Todos los pares en el objeto:');
WriteLn;
// Iterar sobre todos los pares usando for-in
for LPair in LJSONObject do
begin
WriteLn('Clave: ', LPair.JsonString.Value);
WriteLn('Valor: ', LPair.JsonValue.ToString);
WriteLn('Tipo: ', LPair.JsonValue.ClassName);
WriteLn;
end;
finally
LJSONObject.Free;
end;
ReadLn;
end.
Modificación de Objetos JSON
Los objetos JSON en Delphi son completamente mutables - puedes agregar, eliminar y actualizar propiedades después de la creación. Entender las reglas de gestión de memoria es crucial: cuando llamas a RemovePair, la posesión de ese par se transfiere de vuelta a ti, por lo que debes liberarlo. El método Free en Delphi es seguro de llamar en nil, por lo que el patrón RemovePair('key').Free funciona incluso si la clave no existe.
Agregar y Eliminar Pares
Este ejemplo demuestra el ciclo de vida completo de modificar un objeto JSON: crear propiedades iniciales, eliminar una y actualizar otra. Observa que para actualizar un valor, primero debes eliminar el par antiguo (liberándolo) y luego agregar uno nuevo con la misma clave:
program JSONModify;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LRemovedPair: TJSONPair;
begin
LJSONObject := TJSONObject.Create;
try
// Agregar pares iniciales
LJSONObject.AddPair('name', 'Daniele');
LJSONObject.AddPair('city', 'Rome');
LJSONObject.AddPair('temp', 'a ser eliminado');
WriteLn('Inicial:');
WriteLn(LJSONObject.ToString);
WriteLn;
// Eliminar un par - RemovePair devuelve el par eliminado (¡tú lo posees!)
LRemovedPair := LJSONObject.RemovePair('temp');
LRemovedPair.Free; // Seguro incluso si es nil - Free verifica Self <> nil
WriteLn('Después de eliminar "temp":');
WriteLn(LJSONObject.ToString);
WriteLn;
// Para actualizar un valor: eliminar y luego agregar
LRemovedPair := LJSONObject.RemovePair('city');
LRemovedPair.Free;
LJSONObject.AddPair('city', 'Milan');
WriteLn('Después de actualizar "city":');
WriteLn(LJSONObject.ToString);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Salida:
Inicial:
{"name":"Daniele","city":"Rome","temp":"a ser eliminado"}
Después de eliminar "temp":
{"name":"Daniele","city":"Rome"}
Después de actualizar "city":
{"name":"Daniele","city":"Milan"}
Clonación de Objetos JSON
Cuando necesitas crear una versión modificada de un objeto JSON sin afectar el original, usa el método Clone. Esto crea una copia profunda - un árbol de objetos completamente independiente donde los cambios en el clon no afectan al original y viceversa. Esto es esencial cuando recibes datos JSON que necesitas transformar antes de enviarlos a otro lugar mientras preservas el original:
program JSONClone;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON;
var
LOriginal: TJSONObject;
LClone: TJSONObject;
LPair: TJSONPair;
begin
LOriginal := TJSONObject.Create;
try
LOriginal.AddPair('name', 'Daniele');
LOriginal.AddPair('city', 'Rome');
// Clone crea una copia independiente
LClone := LOriginal.Clone as TJSONObject;
try
// Modificar el clon - el original no se ve afectado
LPair := LClone.RemovePair('city');
LPair.Free;
LClone.AddPair('city', 'Milan');
WriteLn('Original: ', LOriginal.ToString);
WriteLn('Clon: ', LClone.ToString);
finally
LClone.Free;
end;
finally
LOriginal.Free;
end;
ReadLn;
end.
Salida:
Original: {"name":"Daniele","city":"Rome"}
Clon: {"name":"Daniele","city":"Milan"}
Trabajo con Archivos JSON
Persistir JSON en disco es un requisito común para archivos de configuración, almacenamiento en caché y exportación de datos. La unidad System.IOUtils de Delphi proporciona la clase TFile con métodos simples para leer y escribir archivos de texto, que se combina perfectamente con la representación de cadena de JSON.
Guardar JSON en Archivo
Para guardar un objeto JSON en un archivo, conviértelo a una cadena usando Format() (para salida legible) o ToString() (para salida compacta), luego escribe esa cadena en disco. Usar TPath.GetDocumentsPath asegura que tu archivo vaya a una ubicación escribible que funciona en diferentes configuraciones de Windows:
program JSONSaveToFile;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.IOUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LDatabase: TJSONObject;
LFileName: string;
begin
LFileName := TPath.Combine(TPath.GetDocumentsPath, 'config.json');
LJSONObject := TJSONObject.Create;
try
LJSONObject.AddPair('appName', 'MyApplication');
LJSONObject.AddPair('version', '1.0.0');
LJSONObject.AddPair('debug', False);
LDatabase := TJSONObject.Create;
LJSONObject.AddPair('database', LDatabase);
LDatabase.AddPair('host', 'localhost');
LDatabase.AddPair('port', 5432);
// Guardar en archivo
{$IF CompilerVersion >= 33.0}
TFile.WriteAllText(LFileName, LJSONObject.Format());
{$ELSE}
TFile.WriteAllText(LFileName, LJSONObject.ToString);
{$ENDIF}
WriteLn('Guardado en: ', LFileName);
finally
LJSONObject.Free;
end;
ReadLn;
end.
Cargar JSON desde Archivo
Leer JSON desde un archivo es igualmente sencillo: lee el contenido del archivo en una cadena, luego analízalo con ParseJSONValue. Siempre verifica primero si el archivo existe para evitar excepciones, y verifica que el análisis tuvo éxito antes de acceder a los datos. La notación de ruta funciona igual de bien en JSON analizado que en objetos construidos manualmente:
program JSONLoadFromFile;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.IOUtils,
System.JSON;
var
LJSONObject: TJSONObject;
LJSONValue: TJSONValue;
LContent: string;
LFileName: string;
LAppName: string;
LPort: Integer;
begin
LFileName := TPath.Combine(TPath.GetDocumentsPath, 'config.json');
if not TFile.Exists(LFileName) then
begin
WriteLn('Archivo no encontrado: ', LFileName);
ReadLn;
Exit;
end;
LContent := TFile.ReadAllText(LFileName);
LJSONValue := TJSONObject.ParseJSONValue(LContent);
if LJSONValue = nil then
begin
WriteLn('¡JSON inválido en el archivo!');
ReadLn;
Exit;
end;
try
LJSONObject := LJSONValue as TJSONObject;
if LJSONObject.TryGetValue<string>('appName', LAppName) then
WriteLn('Nombre de Aplicación: ', LAppName);
if LJSONObject.TryGetValue<Integer>('database.port', LPort) then
WriteLn('Puerto de Base de Datos: ', LPort);
finally
LJSONValue.Free;
end;
ReadLn;
end.
Ejemplo Práctico: Cliente de API REST
Ahora pongamos todo junto en un escenario del mundo real: llamar a una API REST y procesar la respuesta JSON. Este ejemplo se conecta a JSONPlaceholder (una API de prueba gratuita), obtiene una lista de usuarios y analiza cada uno en un registro Delphi. Observa cómo usamos TryGetValue en todo momento para manejar con elegancia campos potencialmente faltantes - un requisito cuando se trabaja con APIs externas que pueden cambiar.
THTTPClient requiere Delphi XE8 o posterior.
program JSONRestApiClient;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.JSON,
System.Net.HttpClient; // Requiere Delphi XE8+
type
TUser = record
ID: Integer;
Name: string;
Email: string;
Username: string;
end;
function ParseUser(AJSONObject: TJSONObject): TUser;
begin
// Usando TryGetValue por seguridad
if not AJSONObject.TryGetValue<Integer>('id', Result.ID) then
Result.ID := 0;
if not AJSONObject.TryGetValue<string>('name', Result.Name) then
Result.Name := '';
if not AJSONObject.TryGetValue<string>('email', Result.Email) then
Result.Email := '';
if not AJSONObject.TryGetValue<string>('username', Result.Username) then
Result.Username := '';
end;
var
LClient: THTTPClient;
LResponse: IHTTPResponse;
LJSONValue: TJSONValue;
LJSONArray: TJSONArray;
LUserJSON: TJSONObject;
LUser: TUser;
I: Integer;
begin
WriteLn('Obteniendo usuarios de la API JSONPlaceholder...');
WriteLn;
LClient := THTTPClient.Create;
try
LResponse := LClient.Get('https://jsonplaceholder.typicode.com/users');
if LResponse.StatusCode <> 200 then
begin
WriteLn('Error HTTP: ', LResponse.StatusCode);
ReadLn;
Exit;
end;
// Analizar respuesta de array JSON
LJSONValue := TJSONObject.ParseJSONValue(LResponse.ContentAsString);
if LJSONValue = nil then
begin
WriteLn('Respuesta JSON inválida');
ReadLn;
Exit;
end;
try
if not (LJSONValue is TJSONArray) then
begin
WriteLn('Se esperaba un array JSON');
Exit;
end;
LJSONArray := LJSONValue as TJSONArray;
WriteLn('Se encontraron ', LJSONArray.Count, ' usuarios:');
WriteLn(StringOfChar('-', 50));
for I := 0 to LJSONArray.Count - 1 do
begin
LUserJSON := LJSONArray.Items[I] as TJSONObject;
LUser := ParseUser(LUserJSON);
WriteLn('ID: ', LUser.ID);
WriteLn('Nombre: ', LUser.Name);
WriteLn('Email: ', LUser.Email);
WriteLn('Usuario: ', LUser.Username);
WriteLn(StringOfChar('-', 50));
end;
finally
LJSONValue.Free;
end;
finally
LClient.Free;
end;
ReadLn;
end.
Ejemplo Práctico: Gestor de Archivos de Configuración
Este ejemplo final muestra una clase completa y reutilizable para gestionar la configuración de aplicaciones. El TConfigManager encapsula toda la complejidad de cargar, guardar y acceder a configuraciones mientras proporciona una API limpia y con tipos seguros. Demuestra carga perezosa (solo lee el archivo cuando se necesita por primera vez), valores predeterminados para claves faltantes y creación automática de archivos. Puedes usar este patrón como punto de partida para tus propios sistemas de configuración:
program JSONConfigManager;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.IOUtils,
System.JSON;
type
TConfigManager = class
private
FFileName: string;
FJSONObject: TJSONObject;
FModified: Boolean;
procedure EnsureLoaded;
public
constructor Create(const AFileName: string);
destructor Destroy; override;
procedure Load;
procedure Save;
function GetString(const AKey: string; const ADefault: string = ''): string;
function GetInteger(const AKey: string; const ADefault: Integer = 0): Integer;
function GetBoolean(const AKey: string; const ADefault: Boolean = False): Boolean;
procedure SetValue(const AKey: string; const AValue: string); overload;
procedure SetValue(const AKey: string; const AValue: Integer); overload;
procedure SetValue(const AKey: string; const AValue: Boolean); overload;
property FileName: string read FFileName;
property Modified: Boolean read FModified;
end;
constructor TConfigManager.Create(const AFileName: string);
begin
inherited Create;
FFileName := AFileName;
FJSONObject := nil;
FModified := False;
end;
destructor TConfigManager.Destroy;
begin
FJSONObject.Free;
inherited;
end;
procedure TConfigManager.EnsureLoaded;
begin
if FJSONObject = nil then
Load;
end;
procedure TConfigManager.Load;
var
LContent: string;
LJSONValue: TJSONValue;
begin
FreeAndNil(FJSONObject);
FModified := False;
if TFile.Exists(FFileName) then
begin
LContent := TFile.ReadAllText(FFileName);
LJSONValue := TJSONObject.ParseJSONValue(LContent);
if (LJSONValue <> nil) and (LJSONValue is TJSONObject) then
FJSONObject := TJSONObject(LJSONValue)
else if LJSONValue <> nil then
LJSONValue.Free;
end;
if FJSONObject = nil then
FJSONObject := TJSONObject.Create;
end;
procedure TConfigManager.Save;
begin
EnsureLoaded;
{$IF CompilerVersion >= 33.0}
TFile.WriteAllText(FFileName, FJSONObject.Format());
{$ELSE}
TFile.WriteAllText(FFileName, FJSONObject.ToString);
{$ENDIF}
FModified := False;
end;
function TConfigManager.GetString(const AKey, ADefault: string): string;
begin
EnsureLoaded;
if not FJSONObject.TryGetValue<string>(AKey, Result) then
Result := ADefault;
end;
function TConfigManager.GetInteger(const AKey: string; const ADefault: Integer): Integer;
begin
EnsureLoaded;
if not FJSONObject.TryGetValue<Integer>(AKey, Result) then
Result := ADefault;
end;
function TConfigManager.GetBoolean(const AKey: string; const ADefault: Boolean): Boolean;
begin
EnsureLoaded;
if not FJSONObject.TryGetValue<Boolean>(AKey, Result) then
Result := ADefault;
end;
procedure TConfigManager.SetValue(const AKey: string; const AValue: string);
begin
EnsureLoaded;
FJSONObject.RemovePair(AKey).Free;
FJSONObject.AddPair(AKey, AValue);
FModified := True;
end;
procedure TConfigManager.SetValue(const AKey: string; const AValue: Integer);
begin
EnsureLoaded;
FJSONObject.RemovePair(AKey).Free;
FJSONObject.AddPair(AKey, AValue);
FModified := True;
end;
procedure TConfigManager.SetValue(const AKey: string; const AValue: Boolean);
begin
EnsureLoaded;
FJSONObject.RemovePair(AKey).Free;
FJSONObject.AddPair(AKey, AValue);
FModified := True;
end;
// Uso de demostración
var
Config: TConfigManager;
LConfigFile: string;
begin
LConfigFile := TPath.Combine(TPath.GetDocumentsPath, 'appsettings.json');
WriteLn('Archivo de configuración: ', LConfigFile);
WriteLn;
Config := TConfigManager.Create(LConfigFile);
try
// Establecer algunos valores (las claves son planas - no objetos anidados)
Config.SetValue('databaseHost', 'localhost');
Config.SetValue('databasePort', 5432);
Config.SetValue('databaseName', 'myapp');
Config.SetValue('loggingEnabled', True);
Config.SetValue('loggingMaxFiles', 10);
Config.Save;
WriteLn('¡Configuración guardada!');
WriteLn;
// Leer valores de vuelta
WriteLn('Host de Base de Datos: ', Config.GetString('databaseHost'));
WriteLn('Puerto de Base de Datos: ', Config.GetInteger('databasePort'));
WriteLn('Logging Habilitado: ', Config.GetBoolean('loggingEnabled'));
// Leer con valor predeterminado
WriteLn('Tiempo de espera (predeterminado 30): ', Config.GetInteger('timeout', 30));
finally
Config.Free;
end;
ReadLn;
end.
Salida:
Archivo de configuración: C:\Users\yourname\Documents\appsettings.json
¡Configuración guardada!
Host de Base de Datos: localhost
Puerto de Base de Datos: 5432
Logging Habilitado: TRUE
Tiempo de espera (predeterminado 30): 30
Bibliotecas JSON de Terceros
Aunque el analizador JSON integrado de Delphi es excelente para la mayoría de los casos de uso, algunos escenarios pueden beneficiarse de bibliotecas de terceros:
| Biblioteca | Mejor Para | URL |
|---|---|---|
| JsonDataObjects | Alto rendimiento, usado por DelphiMVCFramework | GitHub |
| Grijjy Foundation | Con todas las características, incluye soporte BSON | GitHub |
| mORMot2 | Grado empresarial, muy rápido | GitHub |
Cuándo Usar Bibliotecas de Terceros
- Archivos JSON grandes (>10MB): Considera analizadores de streaming o JsonDataObjects
- Análisis de alta frecuencia: JsonDataObjects o mORMot2 ofrecen mejor rendimiento
- Soporte BSON necesario: Grijjy Foundation
- Serialización de objetos: Serializadores de DelphiMVCFramework o mORMot2
Para la mayoría de las aplicaciones, el System.JSON integrado es suficiente y tiene la ventaja de no tener dependencias externas.
Construcción de APIs REST con JSON
Si estás construyendo APIs REST en Delphi, DelphiMVCFramework proporciona excelente soporte JSON con serialización automática:
[MVCPath('/api/customers')]
TCustomersController = class(TMVCController)
public
[MVCPath]
[MVCHTTPMethod([httpGET])]
procedure GetCustomers;
[MVCPath('/($id)')]
[MVCHTTPMethod([httpGET])]
procedure GetCustomer(id: Integer);
end;
procedure TCustomersController.GetCustomers;
var
LCustomers: TObjectList<TCustomer>;
begin
LCustomers := TCustomerService.GetAll;
Render(LCustomers); // Serialización JSON automática
end;
Consulta los ejemplos de DelphiMVCFramework para ejemplos completos.
Preguntas Frecuentes
¿Cómo analizo una cadena JSON en Delphi?
Usa TJSONObject.ParseJSONValue() de la unidad System.JSON:
uses System.JSON;
var
LJSONObject: TJSONObject;
LValue: TJSONValue;
begin
LValue := TJSONObject.ParseJSONValue('{"name":"John"}');
if (LValue <> nil) and (LValue is TJSONObject) then
begin
LJSONObject := TJSONObject(LValue);
try
WriteLn(LJSONObject.GetValue<string>('name')); // Salida: John
finally
LJSONObject.Free;
end;
end;
end;
¿Cómo manejo valores null en JSON?
Usa TryGetValue para manejar de forma segura valores faltantes o null:
var
LValue: string;
begin
if LJSONObject.TryGetValue<string>('optionalField', LValue) then
WriteLn('Valor: ', LValue)
else
WriteLn('El campo falta o es null');
end;
¿Cómo itero sobre un array JSON?
Usa la sintaxis moderna de bucle for-in:
var
LArray: TJSONArray;
LItem: TJSONValue;
begin
if LJSONObject.TryGetValue<TJSONArray>('items', LArray) then
begin
for LItem in LArray do
WriteLn(LItem.Value);
end;
end;
¿Cuál es la diferencia entre Format() y ToString()?
Format(): Devuelve JSON indentado y legible para humanos (solo Delphi 10.3+)ToString(): Devuelve JSON compacto sin espacios en blanco (mejor para transferencia de red, funciona en todas las versiones)
¿Cómo modifico un objeto JSON existente?
Usa RemovePair y luego AddPair. RemovePair devuelve el par eliminado (o nil si no se encuentra) - tú lo posees y debes liberarlo:
begin
// Remove devuelve el par - ¡debes liberarlo!
// Free es seguro de llamar en nil (verifica Self <> nil internamente)
LJSONObject.RemovePair('name').Free;
// Agregar nuevo valor
LJSONObject.AddPair('name', 'Nuevo Valor');
end;
¿Qué versión de Delphi introdujo el soporte JSON?
- Delphi 2009: Soporte JSON inicial en la unidad
DBXJSON - Delphi XE6: Renombrado a
System.JSONcon mejoras en la API - Delphi 10.1 Berlin: API fluida con
TJSONObjectBuilder - Delphi 10.3 Rio: Se agregó el método
Format(),EJSONParseExceptioncon información de error detallada
¿Cuál es la diferencia entre GetValue, FindValue y TryGetValue?
| Método | Devuelve | Si la Clave No se Encuentra |
|---|---|---|
GetValue<T>('key') |
Valor de tipo T | Lanza excepción |
FindValue('key') |
TJSONValue o nil | Devuelve nil |
TryGetValue<T>('key', outVar) |
Boolean | Devuelve False |
Recomendación: Usa TryGetValue para código de producción ya que es el enfoque más seguro.
¿Cómo creo una copia profunda de un objeto JSON?
Usa el método Clone:
var
LOriginal, LCopy: TJSONObject;
begin
LOriginal := TJSONObject.ParseJSONValue('{"name":"test"}') as TJSONObject;
try
LCopy := LOriginal.Clone as TJSONObject;
try
// LCopy es independiente - las modificaciones no afectan a LOriginal
finally
LCopy.Free;
end;
finally
LOriginal.Free;
end;
end;
¿Cómo verifico si un valor JSON es null?
var
LValue: TJSONValue;
begin
LValue := LJSONObject.FindValue('myField');
if LValue = nil then
WriteLn('El campo no existe')
else if LValue is TJSONNull then
WriteLn('El campo existe pero es null')
else
WriteLn('El campo tiene un valor: ', LValue.Value);
end;
¿Puedo usar notación de ruta para acceder a elementos de array?
Sí, usa notación de corchetes con el índice:
var
LFirstSkill: string;
begin
// Acceder al primer elemento del array de habilidades
if LJSONObject.TryGetValue<string>('skills[0]', LFirstSkill) then
WriteLn('Primera habilidad: ', LFirstSkill);
end;
¿Cómo convierto un objeto Delphi a JSON?
Para casos simples, construye el JSON manualmente con TJSONObject. Para serialización automática de objetos y registros, usa los serializadores de DelphiMVCFramework o bibliotecas de terceros como mORMot2. Estos pueden convertir cualquier objeto Delphi a JSON con una sola llamada a método.
¿Es System.JSON seguro para hilos?
No, TJSONObject y las clases relacionadas no son seguras para hilos. Si múltiples hilos necesitan acceder al mismo objeto JSON, debes implementar tu propia sincronización (secciones críticas, bloqueos, etc.). Para acceso de solo lectura después del análisis inicial, puedes compartir de forma segura el objeto entre hilos siempre que no ocurran modificaciones.
¿Cómo serializo un TDateTime a JSON?
TJSONObject no tiene una sobrecarga AddPair integrada para TDateTime. Conviértelo primero a una cadena usando un formato estándar como ISO 8601:
LJSONObject.AddPair('createdAt', FormatDateTime('yyyy-mm-dd"T"hh:nn:ss', Now));
¿Cuál es el tamaño máximo de JSON que Delphi puede analizar?
No hay un límite estricto, pero System.JSON carga todo el documento en memoria. Para archivos muy grandes (>100MB), considera analizadores de streaming como TJsonTextReader de System.JSON.Readers, o bibliotecas de terceros optimizadas para documentos grandes.
¿Cuál es la diferencia entre System.JSON y DBXJSON?
Son la misma biblioteca - solo renombrada. DBXJSON fue el nombre original de la unidad en Delphi 2009-XE5. A partir de Delphi XE6, se renombró a System.JSON para seguir las nuevas convenciones de nomenclatura. La API es esencialmente la misma, por lo que migrar código antiguo es sencillo.
¿Cómo imprimo JSON de forma elegante en Delphi?
Usa el método Format() (Delphi 10.3+) que devuelve JSON indentado y legible para humanos:
WriteLn(LJSONObject.Format()); // Impreso de forma elegante con indentación
WriteLn(LJSONObject.ToString); // Compacto, una sola línea
Para versiones antiguas de Delphi, usa bibliotecas de terceros o implementa formato personalizado.
¿Cómo manejo caracteres especiales y Unicode en JSON?
System.JSON maneja automáticamente Unicode y escapa caracteres especiales al generar JSON. Al analizar, las secuencias escapadas como \n, \t y \uXXXX se convierten correctamente. No se necesita manejo manual:
LJSONObject.AddPair('message', 'Línea 1'#13#10'Línea 2'); // Saltos de línea auto-escapados
LJSONObject.AddPair('emoji', '🚀'); // Unicode funciona directamente
¿Cómo fusiono dos objetos JSON?
No hay función de fusión integrada. Itera sobre un objeto y agrega sus pares al otro:
for LPair in LSource do
LTarget.AddPair(LPair.JsonString.Value, LPair.JsonValue.Clone as TJSONValue);
Nota: Debes clonar los valores ya que solo pueden pertenecer a un objeto padre.
¿Cómo valido JSON antes de analizarlo?
ParseJSONValue devuelve nil para JSON inválido, lo que sirve como validación básica. Para validación de esquema (verificar estructura, campos requeridos, tipos), necesitarás bibliotecas de terceros ya que Delphi no incluye soporte integrado para JSON Schema.
¿Cómo accedo a arrays profundamente anidados?
Combina notación de ruta con indexación de array:
// Acceder: {"data": {"users": [{"name": "Alice"}, {"name": "Bob"}]}}
if LJSONObject.TryGetValue<string>('data.users[1].name', LValue) then
WriteLn(LValue); // Salida: Bob
¿Puedo usar JSON con conjuntos de datos FireDAC?
Sí, pero no hay integración directa. Puedes iterar manualmente sobre un conjunto de datos y construir JSON, o usar bibliotecas de serialización. Tanto DelphiMVCFramework como mORMot2 proporcionan serialización de conjunto de datos a JSON lista para usar.
¿Cómo manejo JSON con claves duplicadas?
JSON técnicamente permite claves duplicadas, aunque está desaconsejado. TJSONObject almacena todos los pares, pero GetValue/TryGetValue solo devuelven la primera coincidencia. Para acceder a todos los valores con la misma clave, itera usando el bucle for-in.
Resumen
Delphi proporciona soporte JSON robusto e integrado a través de la unidad System.JSON. Puntos clave:
- Usa
TJSONObjectyTJSONArraypara crear y analizar JSON - Siempre verifica nil al analizar cadenas JSON
- Usa
TryGetValuepara lectura segura de valores con campos opcionales - Usa notación de ruta (
'parent.child') para valores anidados - Recuerda la gestión de memoria: los objetos padre poseen a sus hijos;
RemovePairdevuelve la posesión a ti - Considera bibliotecas de terceros solo para necesidades específicas de rendimiento
- Usa
Format()para salida legible (Delphi 10.3+),ToString()para salida compacta - Usa bucles for-in para iteración más limpia sobre arrays y pares de objetos
Para construir APIs REST modernas en Delphi, consulta DelphiMVCFramework - incluye serialización JSON avanzada y es usado en producción por empresas en todo el mundo.
Comments
comments powered by Disqus