Become a member!

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.

Todos los ejemplos de código en este artículo han sido probados y verificados con Delphi 13 Florence.
📝
Este artículo se centra en el analizador JSON estilo DOM (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
💡
Todos los ejemplos en este artículo son compatibles con Delphi XE7 y versiones posteriores a menos que se indique lo contrario. Las características específicas de versión están claramente marcadas.

¿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.
⚠️
Siempre verifica si 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.JSON con mejoras en la API
  • Delphi 10.1 Berlin: API fluida con TJSONObjectBuilder
  • Delphi 10.3 Rio: Se agregó el método Format(), EJSONParseException con 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:

  1. Usa TJSONObject y TJSONArray para crear y analizar JSON
  2. Siempre verifica nil al analizar cadenas JSON
  3. Usa TryGetValue para lectura segura de valores con campos opcionales
  4. Usa notación de ruta ('parent.child') para valores anidados
  5. Recuerda la gestión de memoria: los objetos padre poseen a sus hijos; RemovePair devuelve la posesión a ti
  6. Considera bibliotecas de terceros solo para necesidades específicas de rendimiento
  7. Usa Format() para salida legible (Delphi 10.3+), ToString() para salida compacta
  8. 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