Suporte a JSON no Delphi: guia completo com exemplos (2026)
🇬🇧 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.
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ão | Unit | Principais recursos |
|---|---|---|
| Delphi 2009 | DBXJSON | Primeiro suporte a JSON, com as classes básicas |
| Delphi XE6 | System.JSON | Unit renomeada, API melhorada |
| Delphi 10.1 Berlin | System.JSON | API fluente TJSONObjectBuilder, melhorias em TryGetValue<T> |
| Delphi 10.3 Rio | System.JSON | Método Format(), EJSONParseException com detalhes, melhorias de desempenho |
| Delphi 11-12 | System.JSON | Mais otimizações e refinamentos |
| Delphi 13 Florence | System.JSON | Melhorias mais recentes e suporte contínuo |
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:
| Classe | Descrição |
|---|---|
TJSONValue | Classe base de todos os tipos de valor JSON |
TJSONObject | Representa um objeto JSON (pares chave-valor) |
TJSONArray | Representa um array JSON (lista ordenada) |
TJSONString | Representa um valor string JSON |
TJSONNumber | Representa um valor numérico JSON |
TJSONBool | Representa um valor booleano JSON |
TJSONNull | Representa um valor null JSON |
TJSONPair | Representa 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.
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:
| Biblioteca | Ideal para | URL |
|---|---|---|
| JsonDataObjects | Alto desempenho, usada pelo DelphiMVCFramework | GitHub |
| Grijjy Foundation | Completa, inclui suporte a BSON | GitHub |
| mORMot2 | Framework full-stack (ORM, SOA, REST) que traz sua própria camada JSON | GitHub |
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.JSONmais adiante neste artigo - Precisa de suporte a BSON: Grijjy Foundation
- Serialização de objetos:
REST.Jsonpara 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 aEJSONParseExceptioncom informações detalhadas do erro
Qual a diferença entre GetValue, FindValue e TryGetValue?
| Método | Devolve | Se a chave não existir |
|---|---|---|
GetValue<T>('key') | Valor do tipo T | Levanta exceção |
FindValue('key') | TJSONValue ou nil | Devolve nil |
TryGetValue<T>('key', outVar) | Boolean | Devolve 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:
- Use
TJSONObjecteTJSONArraypara criar JSON e fazer parsing - Sempre verifique se o resultado é nil ao fazer parsing de strings JSON
- Use
TryGetValuepara ler valores com segurança quando há campos opcionais - Use a notação de caminho (
'parent.child') para valores aninhados - Lembre-se do gerenciamento de memória: os objetos pai são donos dos filhos;
RemovePairdevolve a posse para você - Considere bibliotecas de terceiros só para necessidades específicas de desempenho
- Use
Format()para saída legível (Delphi 10.3+) eToString()para saída compacta - 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:
- Construindo aplicações web em Delphi com DMVCFramework e TemplatePro - Guia completo de desenvolvimento web
- Como serializar uma TList de objetos com Delphi - Técnicas avançadas de serialização
- Marshalling/Unmarshalling personalizado no Delphi - Aprofundamento no tratamento personalizado de JSON
Comments