Become a member!

Suporte a JSON no Delphi: guia completo com exemplos (2026)

🌐
Este artigo também está disponível em outros idiomas:
🇬🇧 English  •  🇮🇹 Italiano  •  🇪🇸 Español  •  🇩🇪 Deutsch  •  🇫🇷 Français

JSON (JavaScript Object Notation) é o padrão de fato para troca de dados nas aplicações modernas. Seja construindo APIs REST, lendo arquivos de configuração ou conversando com web services, saber trabalhar com JSON no Delphi é essencial.

Este guia cobre tudo o que você precisa saber sobre o suporte a JSON no Delphi, com exemplos completos e compiláveis que você pode usar nos seus projetos.

Todos os exemplos de código deste artigo foram testados e verificados com o Delphi 13 Florence.
📝
Este artigo trata do parser JSON no estilo DOM (TJSONObject, TJSONArray). Para parsing em streaming, no estilo SAX, veja as units System.JSON.Readers e System.JSON.Writers.

Compatibilidade entre versões do Delphi

O suporte a JSON evoluiu bastante ao longo das versões do Delphi:

VersãoUnitPrincipais recursos
Delphi 2009DBXJSONPrimeiro suporte a JSON, com as classes básicas
Delphi XE6System.JSONUnit renomeada, API melhorada
Delphi 10.1 BerlinSystem.JSONAPI fluente TJSONObjectBuilder, melhorias em TryGetValue<T>
Delphi 10.3 RioSystem.JSONMétodo Format(), EJSONParseException com detalhes, melhorias de desempenho
Delphi 11-12System.JSONMais otimizações e refinamentos
Delphi 13 FlorenceSystem.JSONMelhorias mais recentes e suporte contínuo
💡
Todos os exemplos deste artigo são compatíveis com o Delphi XE7 e posteriores, salvo indicação em contrário. Os recursos que dependem de uma versão específica estão marcados.

O que é JSON?

JSON é um formato de troca de dados leve, baseado em texto. É fácil de ler e escrever para pessoas, e fácil de interpretar e gerar para máquinas. Um documento JSON pode conter:

  • Objetos: pares chave-valor entre chaves {}
  • Arrays: listas ordenadas de valores entre colchetes []
  • Valores: strings, números, booleanos (true/false), null, objetos ou arrays

Exemplo de estrutura JSON:

{
  "name": "Daniele Teti",
  "age": 45,
  "active": true,
  "skills": ["Delphi", "Python", "SQL"],
  "address": {
    "city": "Rome",
    "country": "Italy"
  }
}

Visão geral das classes JSON do Delphi

O Delphi oferece suporte nativo a JSON pela unit System.JSON. As classes principais são:

ClasseDescrição
TJSONValueClasse base de todos os tipos de valor JSON
TJSONObjectRepresenta um objeto JSON (pares chave-valor)
TJSONArrayRepresenta um array JSON (lista ordenada)
TJSONStringRepresenta um valor string JSON
TJSONNumberRepresenta um valor numérico JSON
TJSONBoolRepresenta um valor booleano JSON
TJSONNullRepresenta um valor null JSON
TJSONPairRepresenta um par chave-valor dentro de um objeto

Criando objetos JSON

Vamos começar pelo básico: criar objetos JSON e adicionar propriedades.

Criação básica de um objeto JSON

A operação mais elementar é criar um TJSONObject e adicionar pares chave-valor. O Delphi oferece overloads de AddPair que aceitam diretamente strings, inteiros, booleanos e doubles, então você não precisa encapsular valores primitivos em classes específicas de JSON. O exemplo a seguir monta um objeto JSON simples com dados pessoais de vários tipos:

program JSONCreateBasic;
{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.JSON;

var
  LJSONObject: TJSONObject;
begin
  LJSONObject := TJSONObject.Create;
  try
    // Adiciona propriedade string
    LJSONObject.AddPair('firstName', 'Daniele');
    LJSONObject.AddPair('lastName', 'Teti');

    // Adiciona propriedade numérica (há overloads para Integer, Int64, Double)
    LJSONObject.AddPair('age', 45);

    // Adiciona propriedade booleana
    LJSONObject.AddPair('active', True);

    // Adiciona propriedade null (não há overload, é preciso usar TJSONNull)
    LJSONObject.AddPair('middleName', TJSONNull.Create);

    // Exibe o JSON
    // Obs.: Format() disponível desde o 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.

Saída:

{
    "firstName": "Daniele",
    "lastName": "Teti",
    "age": 45,
    "active": true,
    "middleName": null
}

Criando arrays JSON

Arrays JSON são coleções ordenadas que podem conter qualquer combinação de valores: strings, números, booleanos e até outros arrays e objetos. Quando você adiciona um TJSONArray a um TJSONObject com AddPair, o objeto pai passa a ser o dono do array, então basta liberar o objeto raiz. Este exemplo cria um array homogêneo de strings e um array de tipos misturados:

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');

    // Cria array de strings
    LSkills := TJSONArray.Create;
    LJSONObject.AddPair('skills', LSkills);
    LSkills.Add('Delphi');
    LSkills.Add('Python');
    LSkills.Add('SQL');

    // Cria array com tipos misturados
    LContacts := TJSONArray.Create;
    LJSONObject.AddPair('contacts', LContacts);
    LContacts.Add('daniele@example.com');  // string
    LContacts.Add(123456);                 // número
    LContacts.Add(True);                   // booleano

    {$IF CompilerVersion >= 33.0}
    WriteLn(LJSONObject.Format());
    {$ELSE}
    WriteLn(LJSONObject.ToString);
    {$ENDIF}
  finally
    LJSONObject.Free; // Libera também LContacts e LSkills
  end;

  ReadLn;
end.

Saída:

{
    "name": "Daniele Teti",
    "skills": [
        "Delphi",
        "Python",
        "SQL"
    ],
    "contacts": [
        "daniele@example.com",
        123456,
        true
    ]
}

Criando um array de objetos

Um dos padrões mais comuns no JSON do mundo real é um array com vários objetos: uma lista de usuários, de produtos, qualquer coleção de registros. Cada objeto do array pode ter seu próprio conjunto de propriedades. Para montar essa estrutura, você cria cada objeto separadamente e o adiciona ao array com o método Add. O array passa a ser dono de todos os objetos que você adicionar:

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);

    // Primeiro usuário
    LUser := TJSONObject.Create;
    LUsers.Add(LUser);
    LUser.AddPair('id', 1);
    LUser.AddPair('name', 'Alice');
    LUser.AddPair('email', 'alice@example.com');

    // Segundo usuário
    LUser := TJSONObject.Create;
    LUsers.Add(LUser);
    LUser.AddPair('id', 2);
    LUser.AddPair('name', 'Bob');
    LUser.AddPair('email', 'bob@example.com');

    // Terceiro usuário
    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.

Saída:

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

Dados complexos muitas vezes pedem uma organização hierárquica: uma pessoa tem um endereço, um endereço tem cidade e país, e assim por diante. No Delphi, você cria estruturas aninhadas adicionando instâncias de TJSONObject como valores dentro de outros objetos. Como acontece com os arrays, o objeto pai assume a posse dos filhos, o que simplifica o gerenciamento de memória. Este exemplo cria uma pessoa com os objetos aninhados de endereço e de 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');

    // Cria o objeto aninhado de endereço
    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');

    // Cria outro objeto aninhado
    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.

Saída:

{
    "name": "Daniele Teti",
    "address": {
        "street": "Via Roma 123",
        "city": "Rome",
        "country": "Italy",
        "zipCode": "00100"
    },
    "company": {
        "name": "bit Time Professionals",
        "website": "https://www.bittime.it"
    }
}

Usando TJSONObjectBuilder (Delphi 10.1 Berlin+)

Se você prefere uma sintaxe mais declarativa e encadeável para montar JSON, o Delphi 10.1 Berlin trouxe o TJSONObjectBuilder. Essa API fluente permite construir estruturas JSON complexas em uma única expressão, encadeando chamadas a BeginObject, BeginArray, Add e EndObject/EndArray. O builder escreve em um TJsonTextWriter, que por sua vez escreve em um TStringBuilder. A preparação é mais verbosa, mas para estruturas complexas o código fica mais limpo e legível:

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
          // Monta o JSON com a API fluente
          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.

Saída:

{
    "firstName": "Daniele",
    "lastName": "Teti",
    "age": 45,
    "active": true,
    "address": {
        "city": "Rome",
        "country": "Italy"
    },
    "skills": [
        "Delphi",
        "Python",
        "SQL"
    ]
}

Fazendo parsing de strings JSON

Quando você recebe dados JSON de um web service, de um arquivo ou de qualquer outra fonte, precisa convertê-los em objetos Delphi com os quais consiga trabalhar. O método de classe TJSONObject.ParseJSONValue faz essa conversão. Ele retorna um TJSONValue (a classe base), então você precisa verificar se é o tipo esperado, normalmente TJSONObject ou TJSONArray. Se o JSON estiver malformado, o método retorna nil, então verifique isso sempre antes de seguir em frente:

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 retorna TJSONValue, faça o cast para o tipo adequado
  LJSONValue := TJSONObject.ParseJSONValue(JSON_STRING);

  if LJSONValue = nil then
  begin
    WriteLn('ERROR: Invalid JSON!');
    ReadLn;
    Exit;
  end;

  try
    // Verifica se é um objeto (na raiz poderia ser um array)
    if not (LJSONValue is TJSONObject) then
    begin
      WriteLn('ERROR: Expected JSON object at root level');
      Exit;
    end;

    LJSONObject := TJSONObject(LJSONValue); // Hard cast, seguro depois do teste com "is"
    WriteLn('Parsed successfully!');
    WriteLn('Number of pairs: ', LJSONObject.Count);

    {$IF CompilerVersion >= 33.0}
    WriteLn(LJSONObject.Format());
    {$ELSE}
    WriteLn(LJSONObject.ToString);
    {$ENDIF}
  finally
    LJSONValue.Free;
  end;

  ReadLn;
end.
⚠️
Sempre verifique se ParseJSONValue retornou nil, o que indica um JSON inválido.

Tratando erros de parsing (Delphi 10.3+)

Quando o parsing falha, saber por que falhou ajuda muito na depuração. A partir do Delphi 10.3 Rio, você pode passar True como segundo parâmetro de ParseJSONValue para que ele lance uma EJSONParseException em vez de retornar nil. A exceção traz a mensagem de erro, o caminho onde o parsing falhou e o offset do caractere: informação preciosa quando você lida com JSON complexo ou que vem de fora:

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
    // Usa a opção RaiseExc para receber uma exceção com detalhes
    LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON, True);
    try
      WriteLn('Parsed: ', LJSONValue.ToString);
    finally
      LJSONValue.Free;
    end;
  except
    on E: EJSONParseException do
    begin
      WriteLn('Parse error!');
      WriteLn('  Message: ', E.Message);
      WriteLn('  Path: ', E.Path);
      WriteLn('  Offset: ', E.Offset);
    end;
  end;
  {$ELSE}
  // Antes do 10.3: basta verificar nil
  LJSONValue := TJSONObject.ParseJSONValue(INVALID_JSON);
  if LJSONValue = nil then
    WriteLn('Invalid JSON - no details available')
  else
    LJSONValue.Free;
  {$ENDIF}

  ReadLn;
end.

Lendo valores JSON

O Delphi oferece várias formas de ler valores de objetos JSON, cada uma com um equilíbrio diferente entre praticidade e segurança. Saber quando usar cada uma ajuda a escrever código mais robusto, que lida bem com dados ausentes ou inesperados.

Método 1: GetValue com tipo genérico (Delphi XE7+)

A forma mais simples de ler um valor é o método genérico GetValue<T>. Você informa o tipo esperado como parâmetro de tipo e o Delphi faz a conversão sozinho. Só que esse método lança uma exceção se a chave não existir, então use-o apenas quando tiver certeza de que a chave está lá:

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>: lança exceção se a chave não existir
    WriteLn('Name: ', LJSONObject.GetValue<string>('name'));
    WriteLn('Age: ', LJSONObject.GetValue<Integer>('age'));
    WriteLn('Active: ', LJSONObject.GetValue<Boolean>('active'));
  finally
    LJSONObject.Free;
  end;

  ReadLn;
end.

Método 2: TryGetValue, leitura segura (recomendado)

Para código de produção, TryGetValue<T> é a abordagem recomendada. Ele retorna False se a chave não existir ou se o valor não puder ser convertido para o tipo pedido, e assim você trata dados ausentes sem recorrer a exceções. Isso é especialmente útil quando o JSON vem de fontes externas, onde você não tem como garantir que todos os campos estejam 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 retorna False se a chave não existir (sem exceção)
    if LJSONObject.TryGetValue<string>('name', LName) then
      WriteLn('Name: ', LName)
    else
      WriteLn('Name not found');

    if LJSONObject.TryGetValue<Integer>('age', LAge) then
      WriteLn('Age: ', LAge)
    else
      WriteLn('Age not found');

    // Esta chave não existe, e nenhuma exceção é lançada
    if LJSONObject.TryGetValue<string>('middleName', LMiddleName) then
      WriteLn('Middle Name: ', LMiddleName)
    else
      WriteLn('Middle Name: (not specified)');
  finally
    LJSONObject.Free;
  end;

  ReadLn;
end.

Método 3: FindValue, retorna nil se não encontrar

Quando você precisa do objeto TJSONValue bruto em vez de um valor já convertido, use FindValue. Esse método retorna nil se a chave não existir, nunca lança exceção e dá acesso completo às propriedades e métodos do valor JSON. É útil quando você precisa verificar o tipo real de um valor ou quando trabalha com estruturas aninhadas complexas:

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 retorna nil se não encontrar (nunca lança exceção)
    LValue := LJSONObject.FindValue('name');
    if LValue <> nil then
      WriteLn('Name: ', LValue.Value);

    LValue := LJSONObject.FindValue('nonexistent');
    if LValue = nil then
      WriteLn('Key "nonexistent" not found');
  finally
    LJSONObject.Free;
  end;

  ReadLn;
end.

Método 4: notação de caminho para valores aninhados

Um dos recursos mais práticos do Delphi é a notação de caminho: você acessa valores profundamente aninhados com caminhos separados por ponto, como 'person.address.city', em vez de navegar por vários objetos intermediários. Funciona com TryGetValue, GetValue e FindValue, e facilita muito extrair valores específicos de estruturas JSON complexas sem escrever código de navegação 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
    // Usa a notação com ponto para acessar valores aninhados
    if LJSONObject.TryGetValue<string>('person.name', LValue) then
      WriteLn('Person Name: ', LValue);

    if LJSONObject.TryGetValue<string>('person.address.city', LValue) then
      WriteLn('City: ', LValue);

    if LJSONObject.TryGetValue<string>('person.address.country', LValue) then
      WriteLn('Country: ', LValue);
  finally
    LJSONObject.Free;
  end;

  ReadLn;
end.

Lendo arrays: abordagem clássica e moderna

Quando o seu JSON contém arrays, você precisa percorrer os elementos para processar cada valor. O Delphi suporta tanto o loop tradicional por índice com Items[I] quanto a sintaxe for-in, mais moderna, que funciona com qualquer TJSONArray. O for-in fica mais limpo quando você não precisa do índice; o loop clássico dá acesso à posição. Em arrays numéricos, faça o cast de cada item para TJSONNumber para usar métodos como AsInt ou 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
    // Lê array de strings: loop for clássico
    if LJSONObject.TryGetValue<TJSONArray>('skills', LSkills) then
    begin
      WriteLn('Skills (classic loop):');
      for I := 0 to LSkills.Count - 1 do
        WriteLn('  ', I + 1, '. ', LSkills.Items[I].Value);
    end;

    WriteLn;

    // Lê array de strings: loop for-in moderno (Delphi XE+)
    if LJSONObject.TryGetValue<TJSONArray>('skills', LSkills) then
    begin
      WriteLn('Skills (for-in loop):');
      for LItem in LSkills do
        WriteLn('  - ', LItem.Value);
    end;

    WriteLn;

    // Lê array numérico
    if LJSONObject.TryGetValue<TJSONArray>('scores', LScores) then
    begin
      WriteLn('Scores:');
      for LItem in LScores do
        WriteLn('  Score: ', (LItem as TJSONNumber).AsInt);
    end;
  finally
    LJSONObject.Free;
  end;

  ReadLn;
end.

Percorrendo os pares de um objeto JSON

Às vezes você precisa processar todas as propriedades de um objeto JSON sem conhecer os nomes das chaves de antemão, por exemplo ao construir um visualizador de JSON genérico ou quando a estrutura é dinâmica. O loop for-in funciona em TJSONObject do mesmo jeito que nos arrays, retornando instâncias de TJSONPair. Cada par dá acesso à chave (via JsonString.Value) e ao valor (via JsonValue), e você ainda pode inspecionar o tipo em tempo de execução com 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('All pairs in object:');
    WriteLn;

    // Percorre todos os pares com for-in
    for LPair in LJSONObject do
    begin
      WriteLn('Key: ', LPair.JsonString.Value);
      WriteLn('Value: ', LPair.JsonValue.ToString);
      WriteLn('Type: ', LPair.JsonValue.ClassName);
      WriteLn;
    end;
  finally
    LJSONObject.Free;
  end;

  ReadLn;
end.

Modificando objetos JSON

Os objetos JSON no Delphi são totalmente mutáveis: você pode adicionar, remover e atualizar propriedades depois da criação. Entender as regras de gerenciamento de memória é fundamental: quando você chama RemovePair, a posse daquele par volta para você, e cabe a você liberá-lo. No Delphi, o método Free pode ser chamado com segurança em nil, então o padrão RemovePair('key').Free funciona mesmo que a chave não exista.

Adicionando e removendo pares

Este exemplo mostra o ciclo completo de modificação de um objeto JSON: criar as propriedades iniciais, remover uma e atualizar outra. Repare que, para atualizar um valor, você precisa primeiro remover o par antigo (liberando-o) e depois adicionar um novo com a mesma chave:

program JSONModify;
{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.JSON;

var
  LJSONObject: TJSONObject;
  LRemovedPair: TJSONPair;
begin
  LJSONObject := TJSONObject.Create;
  try
    // Adiciona os pares iniciais
    LJSONObject.AddPair('name', 'Daniele');
    LJSONObject.AddPair('city', 'Rome');
    LJSONObject.AddPair('temp', 'to be removed');

    WriteLn('Initial:');
    WriteLn(LJSONObject.ToString);
    WriteLn;

    // Remove um par: RemovePair retorna o par removido (agora ele é seu!)
    LRemovedPair := LJSONObject.RemovePair('temp');
    LRemovedPair.Free; // Seguro mesmo se nil: Free verifica Self <> nil

    WriteLn('After removing "temp":');
    WriteLn(LJSONObject.ToString);
    WriteLn;

    // Para atualizar um valor: remove e depois adiciona
    LRemovedPair := LJSONObject.RemovePair('city');
    LRemovedPair.Free;
    LJSONObject.AddPair('city', 'Milan');

    WriteLn('After updating "city":');
    WriteLn(LJSONObject.ToString);
  finally
    LJSONObject.Free;
  end;

  ReadLn;
end.

Saída:

Initial:
{"name":"Daniele","city":"Rome","temp":"to be removed"}

After removing "temp":
{"name":"Daniele","city":"Rome"}

After updating "city":
{"name":"Daniele","city":"Milan"}

Clonando objetos JSON

Quando você precisa criar uma versão modificada de um objeto JSON sem mexer no original, use o método Clone. Ele cria uma cópia profunda, uma árvore de objetos totalmente independente: mudanças no clone não afetam o original, e vice-versa. Isso é essencial quando você recebe dados JSON que precisa transformar antes de mandar para outro lugar, preservando o 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 cria uma cópia independente
    LClone := LOriginal.Clone as TJSONObject;
    try
      // Modifica o clone: o original não é afetado
      LPair := LClone.RemovePair('city');
      LPair.Free;
      LClone.AddPair('city', 'Milan');

      WriteLn('Original: ', LOriginal.ToString);
      WriteLn('Clone: ', LClone.ToString);
    finally
      LClone.Free;
    end;
  finally
    LOriginal.Free;
  end;

  ReadLn;
end.

Saída:

Original: {"name":"Daniele","city":"Rome"}
Clone: {"name":"Daniele","city":"Milan"}

Trabalhando com arquivos JSON

Persistir JSON em disco é uma necessidade comum para arquivos de configuração, cache e exportação de dados. A unit System.IOUtils do Delphi oferece a classe TFile, com métodos simples para ler e gravar arquivos de texto, que se encaixam bem com a representação em string do JSON.

Salvando JSON em arquivo

Para salvar um objeto JSON em arquivo, converta-o em string com Format() (saída legível) ou ToString() (saída compacta) e grave essa string no disco. Usar TPath.GetDocumentsPath garante que o arquivo vá para um local com permissão de escrita, que funciona em diferentes configurações do 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);

    // Salva em arquivo
    {$IF CompilerVersion >= 33.0}
    TFile.WriteAllText(LFileName, LJSONObject.Format());
    {$ELSE}
    TFile.WriteAllText(LFileName, LJSONObject.ToString);
    {$ENDIF}

    WriteLn('Saved to: ', LFileName);
  finally
    LJSONObject.Free;
  end;

  ReadLn;
end.

Carregando JSON de um arquivo

Ler JSON de um arquivo é igualmente simples: leia o conteúdo do arquivo para uma string e faça o parsing com ParseJSONValue. Verifique sempre antes se o arquivo existe, para evitar exceções, e confirme que o parsing deu certo antes de acessar os dados. A notação de caminho funciona no JSON lido de arquivo tão bem quanto nos objetos montados à mão:

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('File not found: ', LFileName);
    ReadLn;
    Exit;
  end;

  LContent := TFile.ReadAllText(LFileName);
  LJSONValue := TJSONObject.ParseJSONValue(LContent);

  if LJSONValue = nil then
  begin
    WriteLn('Invalid JSON in file!');
    ReadLn;
    Exit;
  end;

  try
    LJSONObject := LJSONValue as TJSONObject;

    if LJSONObject.TryGetValue<string>('appName', LAppName) then
      WriteLn('App Name: ', LAppName);

    if LJSONObject.TryGetValue<Integer>('database.port', LPort) then
      WriteLn('Database Port: ', LPort);
  finally
    LJSONValue.Free;
  end;

  ReadLn;
end.

Transformando objetos em JSON com REST.Json

Até aqui, todo o código monta o JSON à mão, par por par. Esse é o caminho certo quando o formato do documento é o que importa. É o caminho errado quando você já tem uma classe e só quer mandá-la pela rede.

Para isso, o Delphi traz o REST.Json. Ele vem de fábrica desde o XE5, não precisa de código de terceiros, e quase todo o trabalho cabe em uma chamada.

De objeto para JSON

uses
  REST.Json, REST.Json.Types;

type
  TAddress = class
  private
    FCity: string;
    FZipCode: string;
  public
    property City: string read FCity write FCity;
    property ZipCode: string read FZipCode write FZipCode;
  end;

  TCustomer = class
  private
    FId: Integer;
    FName: string;
    FActive: Boolean;
    FAddress: TAddress;
    [JSONMarshalled(False)]
    FInternalNote: string;
    [JSONName('vat_number')]
    FVatNumber: string;
  public
    constructor Create;
    destructor Destroy; override;
    property Id: Integer read FId write FId;
    property Name: string read FName write FName;
    property Active: Boolean read FActive write FActive;
    property Address: TAddress read FAddress write FAddress;
    property InternalNote: string read FInternalNote write FInternalNote;
    property VatNumber: string read FVatNumber write FVatNumber;
  end;

// ...

LCustomer.Id := 42;
LCustomer.Name := 'Daniele Teti';
LCustomer.Active := True;
LCustomer.VatNumber := 'IT01234567890';
LCustomer.InternalNote := 'do not send this to the client';
LCustomer.Address.City := 'Roma';
LCustomer.Address.ZipCode := '00100';

Writeln(TJson.ObjectToJsonString(LCustomer));

Saída:

{"id":42,"name":"Daniele Teti","active":true,"address":{"city":"Roma","zipCode":"00100"},"vat_number":"IT01234567890"}

Aconteceram três coisas aí.

O TAddress aninhado também foi serializado, sem que você pedisse. O REST.Json percorre o grafo de objetos.

InternalNote não está na saída. [JSONMarshalled(False)] é como você deixa um campo fora do documento, e é o atributo que você quer em tudo aquilo que o cliente não tem nada que ver.

As chaves saíram como id, name, zipCode. O REST.Json lê os campos privados, não as propriedades, tira o prefixo F e passa a primeira letra para minúscula. Então FZipCode vira zipCode, camelCase, goste você ou não. Quando o outro lado exige outra grafia, [JSONName('vat_number')] sobrescreve o nome, um campo por vez.

De JSON de volta para objeto

const
  JSON_TEXT =
    '{"Id":7,"Name":"Anna Bianchi","Active":false,' +
    '"vat_number":"IT09876543210",' +
    '"Address":{"City":"Milano","ZipCode":"20100"}}';
var
  LCustomer: TCustomer;
begin
  LCustomer := TJson.JsonToObject<TCustomer>(JSON_TEXT);
  try
    Writeln(Format('Id=%d Name=%s Active=%s Vat=%s City=%s',
      [LCustomer.Id, LCustomer.Name, BoolToStr(LCustomer.Active, True),
       LCustomer.VatNumber, LCustomer.Address.City]));
  finally
    LCustomer.Free;
  end;
end;
Id=7 Name=Anna Bianchi Active=False Vat=IT09876543210 City=Milano

Repare que a entrada usava Id e Name com inicial maiúscula e mesmo assim funcionou: a leitura não diferencia maiúsculas de minúsculas, a escrita sim. O objeto volta montado por inteiro, endereço aninhado incluído, e liberá-lo é problema seu.

Os campos que o JSON não menciona ficam com o que o construtor deixou neles:

LCustomer := TJson.JsonToObject<TCustomer>('{"Name":"Only a name"}');
Id=0 Name=Only a name Active=False

Nenhuma exceção. Se a falta de um Id significa que algo deu errado lá atrás, verificar isso é com você.

O erro que vai te custar uma tarde

Declare essas classes no .dpr e TJson.JsonToObject falha:

EConversionError: Internal: Cannot instantiate type restjson.TCustomer

O serializador precisa da RTTI linkada da classe, e um tipo declarado no arquivo do programa não a recebe. Mova as declarações para uma unit e o mesmo código funciona. A serialização a partir do objeto nunca reclama, então você só esbarra nisso no caminho de volta, geralmente depois de ter se convencido de que o JSON está malformado.

Mais uma, típica das versões recentes: TJson.Format foi marcado como deprecated no Delphi 13 Florence. O próprio compilador diz o que usar no lugar:

W1000 Symbol 'Format' is deprecated: 'Use TJSONAncestor.Format instead'

Então formatar um objeto de forma legível agora fica assim:

LJson := TJson.ObjectToJsonObject(LCustomer);
try
  Writeln(LJson.Format);   // TJSONAncestor.Format
finally
  LJson.Free;
end;
{
    "id": 1,
    "name": "Pretty",
    "active": false,
    "address": {
        "city": "Napoli",
        "zipCode": ""
    },
    "vat_number": ""
}

Onde o REST.Json para

Serialize uma lista e você encontra o limite:

LArray := TJSONArray.Create;
try
  for LCustomer in LList do
    LArray.AddElement(TJson.ObjectToJsonObject(LCustomer));
  Writeln(LArray.ToJSON);
finally
  LArray.Free;
end;

Funciona, e já é um loop que você escreveu à mão. No sentido contrário, de um array JSON de volta para uma TObjectList<TCustomer>, o REST.Json não tem nada a oferecer: você faz o parsing do array por conta própria e chama JsonToObject para cada elemento.

O REST.Json é muito bom com um objeto de cada vez, com nomes que ele escolhe por você. Para qualquer coisa além disso, você quer um serializador feito para esse trabalho.

Quando você precisa de mais: os serializadores do DelphiMVCFramework

O DelphiMVCFramework traz um serializador que você pode usar sozinho, sem servidor e sem controller à vista. É uma unit e uma interface.

Uma lista, em uma chamada, nos dois sentidos:

uses
  MVCFramework.Serializer.Intf,
  MVCFramework.Serializer.Commons,
  MVCFramework.Serializer.JsonDataObjects;

var
  LSer: IMVCSerializer;
begin
  LSer := TMVCJsonDataObjectsSerializer.Create;
  Writeln(LSer.SerializeCollection(LOrders));
[{"id":1,"description":"Order 1","placedat":"2026-09-01T10:30:00.000+02:00"},{"id":2,"description":"Order 2","placedat":"2026-09-02T10:30:00.000+02:00"},{"id":3,"description":"Order 3","placedat":"2026-09-03T10:30:00.000+02:00"}]

E de volta:

LOrders := TObjectList<TOrder>.Create(True);
try
  LSer.DeserializeCollection(JSON_TEXT, LOrders, TOrder);
objects rebuilt: 2
  id=10 desc=From JSON placed=01/09/2026 10:30:00
  id=11 desc=Second one placed=02/09/2026 10:30:00

Duas chamadas onde o REST.Json te deu dois loops. Repare também que o TDateTime saiu e voltou como uma data de verdade, em ISO 8601 com o offset, que é o assunto da próxima seção.

Aqui o formato das chaves é uma decisão sua, não uma regra imposta. Coloque o atributo na classe e a classe inteira segue:

[MVCNameCase(ncSnakeCase)]
TSnakeOrder = class
  // ...
end;

[MVCNameCase(ncPascalCase)]
TPascalOrder = class
  // ...
end;
ncSnakeCase : {"order_id":42,"customer_name":"Daniele Teti"}
ncPascalCase: {"OrderId":42,"CustomerName":"Daniele Teti"}

É isso que decide a questão para a maioria das pessoas. Se a API com que você precisa conversar quer order_id, o REST.Json te dá um [JSONName] por campo, para sempre; o serializador do DMVCFramework te dá um atributo por classe.

Mas tem uma armadilha, e ela é silenciosa. O formato dos nomes vale também na leitura. O serializador usa ncLowerCase por padrão, então ele emite placedat e espera placedat. Mande para ele o mesmo payload com placedAt e:

key written as "placedAt", serializer expects "placedat":
  id=10 desc=From JSON placed=30/12/1899
  no exception, the date is simply gone

30/12/1899 é o zero do TDateTime. Nenhum erro, nenhum aviso, só um campo que discretamente não chegou. Quando você consome uma API que não controla, configure o formato dos nomes para bater com o dela e teste um payload de ponta a ponta antes de acreditar em qualquer coisa.

A mesma regra da RTTI vale aqui, aliás. Declare TOrder no .dpr e você recebe:

Exception: Cannot find RTTI for dmvcser.TOrder. Hint: Is the specified classtype linked in the module?

Serializador diferente, mensagem diferente, mesma causa: tipos vão em units.

Datas em JSON, e a hora que você vai perder

JSON não tem tipo data. Faça o que fizer, um TDateTime sai do seu processo como string, e todo mundo já concordou sobre qual string: ISO 8601. O Delphi te dá a conversão em System.DateUtils, e te dá um padrão que está errado para a maior parte do código que você escreve.

O padrão é UTC

uses
  System.DateUtils;

const
  FIXED: TDateTime = 45000.5;   // 2023-03-15 12:00:00

Writeln('local value        : ', DateTimeToStr(FIXED));
Writeln('DateToISO8601(v)   : ', DateToISO8601(FIXED));
Writeln('DateToISO8601(v,F) : ', DateToISO8601(FIXED, False));
local value        : 15/03/2023 12:00:00
DateToISO8601(v)   : 2023-03-15T12:00:00.000Z
DateToISO8601(v,F) : 2023-03-15T12:00:00.000+01:00

O segundo parâmetro é AInputIsUTC e o padrão dele é True. Então DateToISO8601(SomeDate) diz ao mundo que o valor que você passou já está em UTC. Se ele veio de Now, de um TDateTimePicker ou de uma coluna de banco gravada por uma aplicação local, ele não está em UTC, e você acabou de carimbar um Z numa hora local.

Nada lança exceção e o documento é válido. A hora simplesmente está errada.

Escreva e leia com o mesmo flag

LText := DateToISO8601(LOriginal, False);
LBack := ISO8601ToDate(LText, False);
Writeln('written  : ', LText);
Writeln('read back: ', DateTimeToStr(LBack));
Writeln('identical: ', BoolToStr(SameDateTime(LOriginal, LBack), True));
written  : 2023-03-15T12:00:00.000+01:00
read back: 15/03/2023 12:00:00
identical: True

Misture os flags e o valor se desloca pelo seu offset em relação ao UTC, em silêncio:

LText := DateToISO8601(LOriginal, False);   // local
LBack := ISO8601ToDate(LText);              // padrão, trata como UTC
written with False, read with the default:
  15/03/2023 12:00:00 -> 15/03/2023 11:00:00
  drift in minutes: 60

Uma hora, numa máquina na Itália em março. Em agosto são duas. Numa máquina em UTC é zero, e é exatamente por isso que o problema passa pelos testes e aparece no cliente.

Dentro de um documento

LJson := TJSONObject.Create;
try
  LJson.AddPair('event', 'invoice.created');
  LJson.AddPair('created_at', DateToISO8601(FIXED, False));
  Writeln(LJson.ToJSON);
  LWhen := ISO8601ToDate(LJson.GetValue<string>('created_at'), False);
  Writeln('parsed back: ', DateTimeToStr(LWhen));
finally
  LJson.Free;
end;
{"event":"invoice.created","created_at":"2023-03-15T12:00:00.000+01:00"}
parsed back: 15/03/2023 12:00:00

Entrada que não foi você que escreveu

ISO8601ToDate lança exceção com qualquer coisa que não consiga ler. Para um payload que chegou pela rede, use a versão Try:

if TryISO8601ToDate('2026-13-45T99:00:00', LWhen, False) then
  Writeln('parsed: ', DateTimeToStr(LWhen))
else
  Writeln('TryISO8601ToDate returned False, no exception raised');
TryISO8601ToDate returned False, no exception raised

Mesmo padrão do TryGetValue visto antes neste artigo, e o mesmo motivo para preferi-lo.

Escolha UTC ou hora local uma vez, para a aplicação inteira, e passe o flag explicitamente todas as vezes. O padrão não vai ser o que você queria.

Exemplo prático: cliente de API REST

Agora vamos juntar tudo num cenário real: chamar uma API REST e processar a resposta JSON. Este exemplo se conecta ao JSONPlaceholder (uma API gratuita para testes), busca uma lista de usuários e converte cada um num record Delphi. Repare que usamos TryGetValue o tempo todo para lidar com campos que podem faltar: obrigatório quando se trata de APIs externas, que podem mudar.

📝
THTTPClient exige o Delphi XE8 ou posterior.
program JSONRestApiClient;
{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.JSON,
  System.Net.HttpClient;  // Exige Delphi XE8+

type
  TUser = record
    ID: Integer;
    Name: string;
    Email: string;
    Username: string;
  end;

function ParseUser(AJSONObject: TJSONObject): TUser;
begin
  // Usa TryGetValue por segurança
  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('Fetching users from JSONPlaceholder API...');
  WriteLn;

  LClient := THTTPClient.Create;
  try
    LResponse := LClient.Get('https://jsonplaceholder.typicode.com/users');

    if LResponse.StatusCode <> 200 then
    begin
      WriteLn('HTTP Error: ', LResponse.StatusCode);
      ReadLn;
      Exit;
    end;

    // Faz o parsing da resposta, um array JSON
    LJSONValue := TJSONObject.ParseJSONValue(LResponse.ContentAsString);
    if LJSONValue = nil then
    begin
      WriteLn('Invalid JSON response');
      ReadLn;
      Exit;
    end;

    try
      if not (LJSONValue is TJSONArray) then
      begin
        WriteLn('Expected JSON array');
        Exit;
      end;

      LJSONArray := LJSONValue as TJSONArray;
      WriteLn('Found ', LJSONArray.Count, ' users:');
      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('Name: ', LUser.Name);
        WriteLn('Email: ', LUser.Email);
        WriteLn('Username: ', LUser.Username);
        WriteLn(StringOfChar('-', 50));
      end;

    finally
      LJSONValue.Free;
    end;

  finally
    LClient.Free;
  end;

  ReadLn;
end.

Exemplo prático: gerenciador de arquivo de configuração

Este último exemplo mostra uma classe completa e reutilizável para gerenciar a configuração de uma aplicação. O TConfigManager esconde toda a complexidade de carregar, salvar e acessar as configurações, oferecendo uma API limpa e com tipagem segura. Ele mostra lazy loading (o arquivo só é lido quando necessário pela primeira vez), valores padrão para chaves ausentes e criação automática do arquivo. Você pode usar esse padrão como ponto de partida para os seus próprios sistemas de configuração:

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;

// Exemplo de uso
var
  Config: TConfigManager;
  LConfigFile: string;
begin
  LConfigFile := TPath.Combine(TPath.GetDocumentsPath, 'appsettings.json');
  WriteLn('Config file: ', LConfigFile);
  WriteLn;

  Config := TConfigManager.Create(LConfigFile);
  try
    // Define alguns valores (chaves planas, não objetos aninhados)
    Config.SetValue('databaseHost', 'localhost');
    Config.SetValue('databasePort', 5432);
    Config.SetValue('databaseName', 'myapp');
    Config.SetValue('loggingEnabled', True);
    Config.SetValue('loggingMaxFiles', 10);
    Config.Save;

    WriteLn('Configuration saved!');
    WriteLn;

    // Lê os valores de volta
    WriteLn('Database Host: ', Config.GetString('databaseHost'));
    WriteLn('Database Port: ', Config.GetInteger('databasePort'));
    WriteLn('Logging Enabled: ', Config.GetBoolean('loggingEnabled'));

    // Leitura com valor padrão
    WriteLn('Timeout (default 30): ', Config.GetInteger('timeout', 30));
  finally
    Config.Free;
  end;

  ReadLn;
end.

Saída:

Config file: C:\Users\yourname\Documents\appsettings.json

Configuration saved!

Database Host: localhost
Database Port: 5432
Logging Enabled: TRUE
Timeout (default 30): 30

Bibliotecas JSON de terceiros

O parser JSON nativo do Delphi atende muito bem à maioria dos casos, mas alguns cenários podem se beneficiar de bibliotecas de terceiros:

BibliotecaIdeal paraURL
JsonDataObjectsAlto desempenho, usada pelo DelphiMVCFrameworkGitHub
Grijjy FoundationCompleta, inclui suporte a BSONGitHub
mORMot2Framework full-stack (ORM, SOA, REST) que traz sua própria camada JSONGitHub

Quando usar bibliotecas de terceiros

  • Arquivos JSON grandes (>10MB): considere parsers de streaming ou o JsonDataObjects
  • Parsing em alta frequência: JsonDataObjects, medido contra o System.JSON mais adiante neste artigo
  • Precisa de suporte a BSON: Grijjy Foundation
  • Serialização de objetos: REST.Json para os casos simples, os serializadores do DelphiMVCFramework para listas, datasets e controle do formato dos nomes

Para a maioria das aplicações, o System.JSON nativo basta, e tem a vantagem de não trazer dependências externas.

Quão rápido é o System.JSON, de verdade

“Use uma biblioteca de terceiros se precisar de desempenho” é fácil de escrever e difícil de pôr em prática. Aqui estão os números.

O teste monta um array de 50.000 registros, cada um com um inteiro, duas strings, um booleano, um número e um timestamp ISO 8601: 14 MB de texto UTF-16, o formato de uma exportação real. Depois mede dois trabalhos. Fazer o parsing e ler um inteiro de cada registro, que é o que um cliente faz. E fazer o parsing e serializar tudo de volta, que é o que um proxy faz.

Cada rodada é precedida por um aquecimento descartado, e o que se reporta é a melhor de sete, então os números são o piso, não uma média de tudo o mais que a máquina estava fazendo.

payload: 50000 records, 14657 KB of UTF-16 text
best of 7 runs, one warm-up discarded

System.JSON  parse + read ids         158 ms      90,6 MB/s
JsonDataObjects  parse + read ids      42 ms     340,8 MB/s

System.JSON  parse + serialize        170 ms      84,2 MB/s
JsonDataObjects  parse + serialize     63 ms     227,2 MB/s

Delphi 13 Florence, Win32, otimização ligada, range check e overflow check desligados, num Core i9-13980HX com Windows 11.

Então: o JsonDataObjects faz o parsing cerca de quatro vezes mais rápido, e a ida e volta cerca de duas vezes e meia mais rápido. Essa diferença é real e estável entre as rodadas.

O System.JSON ainda assim mastigou 14 MB em cerca de um sexto de segundo. Se o seu JSON tem algumas centenas de kilobytes, o que cobre a maioria das respostas REST e quase todo arquivo de configuração, você está escolhendo entre dois milissegundos e meio milissegundo. Isso não é uma decisão, é erro de arredondamento, e o System.JSON já está instalado.

Recorra ao JsonDataObjects quando o payload se mede em megabytes, quando você faz parsing num loop que roda milhares de vezes, ou quando está num dispositivo em que a CPU não sai de graça. Fora isso, a dependência custa mais do que entrega.

Uma observação prática, se você for adotá-lo: o JsonDataObjects declara seus próprios TJSONObject e TJSONArray. Numa unit que usa os dois, vence o que vier por último na cláusula uses, e você recebe erros que não fazem sentido até perceber isso:

E2003 Undeclared identifier: 'ParseJSONValue'
E2010 Incompatible types: 'System.JSON.TJSONValue' and 'JsonDataObjects.TJsonArray'

Qualifique os nomes dos tipos, System.JSON.TJSONObject e JsonDataObjects.TJsonArray, e a ambiguidade desaparece.

O mORMot2 não está nesta comparação. É um framework completo, não uma biblioteca JSON, e medi-lo de forma justa significa trazer e configurar o conjunto inteiro, o que é assunto para outro artigo.

Construindo APIs REST com JSON

Se você está construindo APIs REST em Delphi, o DelphiMVCFramework oferece um excelente suporte a JSON, com serialização 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); // Serialização JSON automática
end;

Veja os exemplos do DelphiMVCFramework para casos completos, e o guia oficial se você prefere ter os serializadores explicados em vez de adivinhados.

Perguntas frequentes

Como faço o parsing de uma string JSON no Delphi?

Use TJSONObject.ParseJSONValue() da unit 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')); // Saída: John
    finally
      LJSONObject.Free;
    end;
  end;
end;

Como lidar com valores null em JSON?

Use TryGetValue para tratar com segurança valores ausentes ou null:

var
  LValue: string;
begin
  if LJSONObject.TryGetValue<string>('optionalField', LValue) then
    WriteLn('Value: ', LValue)
  else
    WriteLn('Field is missing or null');
end;

Como percorrer um array JSON?

Use a sintaxe moderna do loop 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;

Qual a diferença entre Format() e ToString()?

  • Format(): retorna JSON indentado e legível (somente Delphi 10.3+)
  • ToString(): retorna JSON compacto, sem espaços em branco (melhor para trafegar pela rede, funciona em todas as versões)

Como modificar um objeto JSON existente?

Use RemovePair e depois AddPair. RemovePair retorna o par removido (ou nil se não encontrar): ele é seu e você precisa liberá-lo:

begin
  // Remove retorna o par: você precisa liberá-lo!
  // Free pode ser chamado em nil (verifica Self <> nil internamente)
  LJSONObject.RemovePair('name').Free;
  // Adiciona o novo valor
  LJSONObject.AddPair('name', 'New Value');
end;

Qual versão do Delphi introduziu o suporte a JSON?

  • Delphi 2009: primeiro suporte a JSON, na unit DBXJSON
  • Delphi XE6: renomeada para System.JSON, com melhorias na API
  • Delphi 10.1 Berlin: API fluente TJSONObjectBuilder
  • Delphi 10.3 Rio: chegam o método Format() e a EJSONParseException com informações detalhadas do erro

Qual a diferença entre GetValue, FindValue e TryGetValue?

MétodoDevolveSe a chave não existir
GetValue<T>('key')Valor do tipo TLevanta exceção
FindValue('key')TJSONValue ou nilDevolve nil
TryGetValue<T>('key', outVar)BooleanDevolve False

Recomendação: use TryGetValue em código de produção, é a abordagem mais segura.

Como criar uma cópia profunda de um objeto JSON?

Use o método Clone:

var
  LOriginal, LCopy: TJSONObject;
begin
  LOriginal := TJSONObject.ParseJSONValue('{"name":"test"}') as TJSONObject;
  try
    LCopy := LOriginal.Clone as TJSONObject;
    try
      // LCopy é independente: as modificações não afetam LOriginal
    finally
      LCopy.Free;
    end;
  finally
    LOriginal.Free;
  end;
end;

Como verificar se um valor JSON é null?

var
  LValue: TJSONValue;
begin
  LValue := LJSONObject.FindValue('myField');
  if LValue = nil then
    WriteLn('Field does not exist')
  else if LValue is TJSONNull then
    WriteLn('Field exists but is null')
  else
    WriteLn('Field has a value: ', LValue.Value);
end;

Dá para usar notação de caminho para acessar elementos de array?

Sim, use colchetes com o índice:

var
  LFirstSkill: string;
begin
  // Acessa o primeiro elemento do array skills
  if LJSONObject.TryGetValue<string>('skills[0]', LFirstSkill) then
    WriteLn('First skill: ', LFirstSkill);
end;

Como converter um objeto Delphi em JSON?

Com TJson.ObjectToJsonString do REST.Json, que vem com o Delphi. Ele percorre o grafo de objetos, lê os campos privados e te entrega chaves em camelCase; [JSONName] renomeia um campo e [JSONMarshalled(False)] deixa um de fora. Para listas, controle do formato dos nomes e datasets, use os serializadores do DelphiMVCFramework. Veja Transformando objetos em JSON com REST.Json mais acima.

O System.JSON é rápido o bastante?

Para quase tudo, sim. Com 14 MB de JSON, 50.000 registros, o System.JSON faz o parsing e a leitura em cerca de 158 ms; o JsonDataObjects faz o mesmo trabalho em 42 ms, umas quatro vezes mais rápido. Com um payload de algumas centenas de kilobytes, o que cobre a maioria das respostas REST e todo arquivo de configuração, a diferença é uma fração de milissegundo. Troque de biblioteca quando os documentos se medirem em megabytes ou quando você fizer parsing num loop apertado, não por padrão. Os números e o método estão em Quão rápido é o System.JSON, de verdade.

Como serializar uma TObjectList para JSON?

O REST.Json não tem suporte a listas: você faz um loop, chama TJson.ObjectToJsonObject para cada item e adiciona cada um a um TJSONArray. No sentido contrário ele não tem nada, então você faz o parsing do array e chama JsonToObject para cada elemento. O serializador do DelphiMVCFramework faz as duas coisas numa chamada só, SerializeCollection e DeserializeCollection.

Por que TJson.JsonToObject lança “Cannot instantiate type”?

Porque a classe está declarada no arquivo de programa .dpr, que não recebe RTTI linkada. Mova a declaração do tipo para uma unit e o mesmo código funciona. Serializar a partir do objeto nunca reclama, então o erro só aparece no caminho de volta. O serializador do DelphiMVCFramework falha pela mesma causa com uma mensagem diferente, Cannot find RTTI for ....

O System.JSON é thread-safe?

Não, TJSONObject e as classes relacionadas não são thread-safe. Se várias threads precisam acessar o mesmo objeto JSON, você tem que implementar sua própria sincronização (critical sections, locks etc.). Para acesso somente leitura depois do parsing inicial, dá para compartilhar o objeto entre threads com segurança, desde que ninguém o modifique.

Como serializar um TDateTime para JSON?

TJSONObject não tem overload de AddPair para TDateTime. Converta antes para uma string ISO 8601 e passe AInputIsUTC explicitamente, porque o padrão é True e ele vai rotular uma hora local como UTC. Veja Datas em JSON, e a hora que você vai perder:

LJSONObject.AddPair('createdAt', FormatDateTime('yyyy-mm-dd"T"hh:nn:ss', Now));

Qual o tamanho máximo de JSON que o Delphi consegue processar?

Não há um limite rígido, mas o System.JSON carrega o documento inteiro na memória. Para arquivos muito grandes (>100MB), considere parsers de streaming como o TJsonTextReader de System.JSON.Readers, ou bibliotecas de terceiros otimizadas para documentos grandes.

Qual a diferença entre System.JSON e DBXJSON?

São a mesma biblioteca, só renomeada. DBXJSON era o nome original da unit do Delphi 2009 ao XE5. A partir do Delphi XE6, ela foi renomeada para System.JSON para seguir as novas convenções de nomes. A API é praticamente a mesma, então migrar código antigo é simples.

Como formatar JSON de forma legível (pretty print) no Delphi?

Use o método Format() (Delphi 10.3+), que retorna JSON indentado e legível:

WriteLn(LJSONObject.Format());  // Formatado, com indentação
WriteLn(LJSONObject.ToString);  // Compacto, uma linha só

Em versões mais antigas do Delphi, use bibliotecas de terceiros ou implemente sua própria formatação.

Como lidar com caracteres especiais e Unicode em JSON?

O System.JSON lida automaticamente com Unicode e faz o escape dos caracteres especiais ao gerar JSON. No parsing, sequências de escape como \n, \t e \uXXXX são convertidas corretamente. Não é preciso tratar nada à mão:

LJSONObject.AddPair('message', 'Line 1'#13#10'Line 2');  // Quebras de linha com escape automático
LJSONObject.AddPair('emoji', '🚀');  // Unicode funciona direto

Como mesclar dois objetos JSON?

Não existe uma função de merge nativa. Percorra um objeto e adicione os pares dele ao outro:

for LPair in LSource do
  LTarget.AddPair(LPair.JsonString.Value, LPair.JsonValue.Clone as TJSONValue);

Observação: você precisa clonar os valores, porque cada um só pode pertencer a um objeto pai.

Como validar JSON antes do parsing?

ParseJSONValue retorna nil para JSON inválido, o que serve como validação básica. Para validação de schema (verificar estrutura, campos obrigatórios, tipos), você vai precisar de bibliotecas de terceiros, porque o Delphi não inclui suporte nativo a JSON Schema.

Como acessar arrays profundamente aninhados?

Combine a notação de caminho com o índice do array:

// Acesso: {"data": {"users": [{"name": "Alice"}, {"name": "Bob"}]}}
if LJSONObject.TryGetValue<string>('data.users[1].name', LValue) then
  WriteLn(LValue);  // Saída: Bob

Dá para usar JSON com datasets do FireDAC?

Sim, mas não há integração direta. Você pode percorrer um dataset manualmente e montar o JSON, ou usar bibliotecas de serialização. O DelphiMVCFramework e o mORMot2 oferecem, de fábrica, serialização de dataset para JSON.

Como lidar com JSON com chaves duplicadas?

Tecnicamente o JSON permite chaves duplicadas, embora isso seja desaconselhado. O TJSONObject guarda todos os pares, mas GetValue/TryGetValue retornam só a primeira ocorrência. Para acessar todos os valores com a mesma chave, percorra o objeto com o loop for-in.

Resumo

O Delphi oferece um suporte a JSON robusto e nativo pela unit System.JSON. Os pontos principais:

  1. Use TJSONObject e TJSONArray para criar JSON e fazer parsing
  2. Sempre verifique se o resultado é nil ao fazer parsing de strings JSON
  3. Use TryGetValue para ler valores com segurança quando há campos opcionais
  4. Use a notação de caminho ('parent.child') para valores aninhados
  5. Lembre-se do gerenciamento de memória: os objetos pai são donos dos filhos; RemovePair devolve a posse para você
  6. Considere bibliotecas de terceiros só para necessidades específicas de desempenho
  7. Use Format() para saída legível (Delphi 10.3+) e ToString() para saída compacta
  8. Use loops for-in para percorrer arrays e pares de objetos de forma mais limpa

Para construir APIs REST modernas em Delphi, dê uma olhada no DelphiMVCFramework: ele inclui serialização JSON avançada e é usado em produção por empresas do mundo todo.

Artigos relacionados:

Comments