PHP JSON
JSON é a língua que os programas usam para trocar dados. Quando um aplicativo de celular pede a lista de produtos a um site, quando o site consulta o CEP num serviço dos Correios, quando você salva as configurações de um programa: quase sempre os dados viajam em JSON. O PHP transforma qualquer array em JSON com uma função, e qualquer JSON de volta em dados com outra. Nesta aula você vai usar as duas, com as bandeiras que deixam o resultado legível, e vai aprender a não se enganar com um JSON quebrado.
- O que é JSON e como ele se parece com os arrays do PHP.
json_encode: array → texto JSON, comJSON_PRETTY_PRINTeJSON_UNESCAPED_UNICODE(sem ela,çviraç).json_decode: texto JSON → dados, como objeto ou como array (comtrue).- Descobrir que um JSON está errado:
json_last_error_msg,JSON_THROW_ON_ERRORejson_validate(PHP 8.3+). - Guardar e ler dados num arquivo
.json.
Para esta aula, você precisa conhecer arrays associativos e ler e escrever arquivos.
A ideia: uma ficha que qualquer programa entende
JSON (JavaScript Object Notation, "notação de objetos do JavaScript", pronuncia-se "jêison") nasceu no JavaScript, mas hoje toda linguagem lê e escreve JSON. É texto puro, com poucas regras. Veja a ficha de um produto da lojinha da Ana, em JSON:
{
"nome": "Pão de mel",
"preco": 4.5,
"estoque": 30,
"vegano": false,
"sabores": ["chocolate", "doce de leite"],
"foto": null
}
E a mesma ficha como array do PHP:
$produto = [
'nome' => 'Pão de mel',
'preco' => 4.5,
'estoque' => 30,
'vegano' => false,
'sabores' => ['chocolate', 'doce de leite'],
'foto' => null,
];
| JSON | PHP | Regra do JSON |
|---|---|---|
{ "chave": valor, … } (objeto) | array associativo ['chave' => valor] | Chaves sempre entre aspas duplas. |
[ valor, valor ] (lista) | array indexado [valor, valor] | Sem vírgula depois do último item. |
"texto" | string | Só aspas duplas; aspas simples não valem. |
4.5, 30 | float, int | Decimal com ponto. |
true, false | bool | Minúsculas. |
null | null | Minúsculas. |
Transformar dados em texto para guardar ou enviar tem um nome: serializar (ou "codificar", encode). O caminho de volta é desserializar ("decodificar", decode). Daí os nomes das funções.
json_encode: do PHP para o JSON
<?php
$produto = [
'nome' => 'Pão de mel',
'preco' => 4.5,
'estoque' => 30,
'vegano' => false,
'sabores' => ['chocolate', 'doce de leite'],
'foto' => null,
];
echo json_encode($produto), "\n";
$ cd json
$ php enc1.php
{"nome":"Pão de mel","preco":4.5,"estoque":30,"vegano":false,"sabores":["chocolate","doce de leite"],"foto":null}
Funcionou: é JSON válido, numa linha só, sem espaços (o formato mais econômico para enviar pela rede). Mas repare no nome: Pão. Por padrão, o json_encode troca toda letra fora do inglês básico por um código de escape \uXXXX (ã é o "ã"). Qualquer programa que ler esse JSON vai entender "Pão" direitinho, mas para gente é ilegível:
$ php enc3.php « três json_encode sem e com bandeiras
{"cidade":"São José","site":"https:\/\/loja.example\/bolos"}
{"cidade":"São José","site":"https://loja.example/bolos"}
["açúcar","maçã"]
Na última linha, açúcar e maçã viraram açúcar e maçã: o ç é ç. E as barras de endereço viraram \/. A segunda linha foi gerada com duas bandeiras (flags) que desligam esses escapes.
As bandeiras mais úteis
O segundo argumento do json_encode aceita bandeiras, combinadas com | (como as do file()):
<?php // … o mesmo $produto … echo json_encode($produto, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), "\n";
$ php enc2.php
{
"nome": "Pão de mel",
"preco": 4.5,
"estoque": 30,
"vegano": false,
"sabores": [
"chocolate",
"doce de leite"
],
"foto": null
}
| Bandeira | Tradução | Efeito |
|---|---|---|
JSON_PRETTY_PRINT | "impressão bonita" | Quebra linhas e recua com 4 espaços. Ótimo para arquivos que pessoas vão ler. |
JSON_UNESCAPED_UNICODE | "Unicode sem escape" | Mantém ã, ç, é como são (em UTF-8), em vez de ã. |
JSON_UNESCAPED_SLASHES | "barras sem escape" | Mantém / em vez de \/. |
JSON_THROW_ON_ERROR | "lançar exceção no erro" | Se der errado, lança uma exceção em vez de devolver false (veja adiante). |
JSON_FORCE_OBJECT | "forçar objeto" | Gera {} mesmo para listas e arrays vazios. |
JSON_PRESERVE_ZERO_FRACTION | "manter o .0" | Grava 10.0 como 10.0, e não 10. |
Missão: rode e veja os ç e companhia. Depois acrescente as bandeiras para o JSON sair em várias linhas e com os acentos legíveis ("nome": "Açúcar mascavo").
<?php
$item = [
'nome' => 'Açúcar mascavo',
'quantidade' => 2,
'unidade' => 'kg',
'fornecedor' => ['cidade' => 'São José', 'contato' => null],
];
echo json_encode($item), "\n";
<?php
$item = [
'nome' => 'Açúcar mascavo',
'quantidade' => 2,
'unidade' => 'kg',
'fornecedor' => ['cidade' => 'São José', 'contato' => null],
];
echo json_encode($item, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), "\n";
Lista ou objeto? E outros detalhes
O PHP tem um tipo só (o array) para duas coisas que no JSON são diferentes (lista [] e objeto {}). Ele decide assim: se as chaves forem 0, 1, 2…, em ordem e sem buracos, vira lista; senão, vira objeto.
<?php
echo json_encode([10, 10.0, 0.1 + 0.2, '42', true, null]), "\n";
echo json_encode([]), "\n";
echo json_encode(['a', 'b', 'c']), "\n";
echo json_encode([1 => 'a', 2 => 'b']), "\n";
$lista = ['a', 'b', 'c'];
unset($lista[1]);
echo json_encode($lista), "\n";
echo json_encode(array_values($lista)), "\n";
echo json_encode([], JSON_FORCE_OBJECT), "\n";
echo json_encode('Ela disse "oi"'), "\n";
$ php tipos.php
[10,10,0.30000000000000004,"42",true,null] « 10.0 virou 10; '42' continua texto
[]
["a","b","c"] « chaves 0,1,2: lista
{"1":"a","2":"b"} « começa em 1: objeto
{"0":"a","2":"c"} « o unset abriu um buraco: objeto!
["a","c"] « array_values renumera: lista de novo
{}
"Ela disse \"oi\"" « aspas dentro do texto são escapadas
A quinta linha é uma armadilha comum: depois de remover um item com unset (ou filtrar com array_filter), as chaves ficam 0, 2, e quem esperava uma lista recebe um objeto. Passe por array_values antes de codificar.
json_encode também aceita objetos: ele grava as propriedades públicas e ignora as privadas.
$ php objeto.php « classe Produto com $nome e $preco públicos e $custo privado
{"nome":"Brigadeiro","preco":2.5}
Para escolher o que vai no JSON, a classe pode implementar a interface JsonSerializable. Você vai ver classes e interfaces no Nível 3 (Classes e Objetos, Interfaces).
json_decode: do JSON para o PHP
O caminho de volta. json_decode recebe o texto JSON e devolve os dados. Mas atenção ao formato que ele escolhe por padrão:
<?php
$json = '{"nome":"Pão de mel","preco":4.5,"sabores":["chocolate","doce de leite"]}';
$obj = json_decode($json);
var_dump($obj);
echo $obj->nome, "\n";
echo $obj->sabores[0], "\n";
$ php dec1.php
object(stdClass)#1 (3) { « um OBJETO, não um array
["nome"]=>
string(11) "Pão de mel"
["preco"]=>
float(4.5)
["sabores"]=>
array(2) { « as listas viram arrays
[0]=>
string(9) "chocolate"
[1]=>
string(13) "doce de leite"
}
}
Pão de mel
chocolate
Sem o segundo argumento, cada objeto JSON {…} vira um objeto da classe genérica stdClass ("classe padrão"), e você lê os campos com a seta ->: $obj->nome. Passando true como segundo argumento ("associativo"), tudo vira array, e você usa colchetes como sempre:
<?php
$json = '{"nome":"Pão de mel","preco":4.5,"sabores":["chocolate","doce de leite"]}';
$arr = json_decode($json, true);
var_dump($arr);
echo $arr['nome'], "\n";
echo $arr['sabores'][1], "\n";
$ php dec2.php
array(3) { « agora um array associativo
["nome"]=>
string(11) "Pão de mel"
["preco"]=>
float(4.5)
["sabores"]=>
array(2) {
[0]=>
string(9) "chocolate"
[1]=>
string(13) "doce de leite"
}
}
Pão de mel
doce de leite
json_decode($json) | json_decode($json, true) | |
|---|---|---|
| Objeto JSON vira | objeto stdClass | array associativo |
| Ler um campo | $d->nome | $d['nome'] |
Funções de array (count, foreach com chave, array_map…) | só nas listas | em tudo |
| Quando usar | Gosto pessoal; alguns frameworks preferem | Recomendado para começar: é o que você já conhece |
Neste guia, vamos usar true quase sempre. Misturar os dois estilos é o erro mais comum com JSON:
$ php dec-erro-obj.php « $obj = json_decode('{"nome":"Pão de mel"}'); echo $obj['nome'];
Fatal error: Uncaught Error: Cannot use object of type stdClass as array in /home/ana/json/dec-erro-obj.php:3
Stack trace:
#0 {main}
thrown in /home/ana/json/dec-erro-obj.php on line 3
$ php dec-erro-arr.php « $arr = json_decode('{"nome":"Pão de mel"}', true); echo $arr->nome;
Warning: Attempt to read property "nome" on array in /home/ana/json/dec-erro-arr.php on line 3
Missão: um pedido chegou do site em JSON. Decodifique-o como array e mostre: Cliente: Bia, uma linha por item no formato 2 x Bolo de cenoura, e o total (quantidade × preco somados) como Total: R$ 82,00.
<?php
$json = '{
"cliente": "Bia",
"itens": [
{"produto": "Bolo de cenoura", "quantidade": 2, "preco": 35},
{"produto": "Brigadeiro", "quantidade": 4, "preco": 3}
]
}';
$pedido = json_decode($json);
var_dump($pedido);
<?php
$json = '{
"cliente": "Bia",
"itens": [
{"produto": "Bolo de cenoura", "quantidade": 2, "preco": 35},
{"produto": "Brigadeiro", "quantidade": 4, "preco": 3}
]
}';
$pedido = json_decode($json, true);
echo "Cliente: {$pedido['cliente']}\n";
$total = 0;
foreach ($pedido['itens'] as $item) {
echo "{$item['quantidade']} x {$item['produto']}\n";
$total += $item['quantidade'] * $item['preco'];
}
echo "Total: R$ " . number_format($total, 2, ',', '.') . "\n";
Quando o JSON está errado
JSON é rigoroso: uma vírgula a mais ou uma aspa simples e o texto inteiro deixa de ser JSON. E o pior: json_decode não mostra aviso nenhum. Ele só devolve null, em silêncio. Para saber o que houve, pergunte com json_last_error() (um número) e json_last_error_msg() (a mensagem):
<?php
$textos = [
"{'nome': 'Ana'}",
'{"nome": "Ana",}',
'{"nome": "Ana"',
'',
'null',
];
foreach ($textos as $t) {
$r = json_decode($t, true);
echo str_pad($t, 18), " => ";
var_dump($r);
echo " erro: ", json_last_error(), " - ", json_last_error_msg(), "\n";
}
$ php invalido.php
{'nome': 'Ana'} => NULL « aspas simples: proibidas no JSON
erro: 4 - Syntax error
{"nome": "Ana",} => NULL « vírgula depois do último item
erro: 4 - Syntax error
{"nome": "Ana" => NULL « faltou fechar a chave
erro: 4 - Syntax error
=> NULL « texto vazio também não é JSON
erro: 4 - Syntax error
null => NULL « este é JSON válido: o valor null!
erro: 0 - No error
A última linha mostra o problema de confiar no null: o texto null é um JSON perfeito, cujo valor é null. Só o json_last_error() (0 = "nenhum erro") separa os dois casos. Syntax error = "erro de sintaxe": o texto não segue as regras.
Melhor: JSON_THROW_ON_ERROR
Ficar perguntando json_last_error() depois de cada chamada é fácil de esquecer. Com a bandeira JSON_THROW_ON_ERROR, um JSON inválido vira uma exceção (JsonException), que você captura com try/catch (aula Exceções):
<?php
$json = '{"nome": "Ana",}';
try {
$dados = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo "JSON inválido: ", $e->getMessage(), "\n";
}
- json_decode($json, true, flags: JSON_THROW_ON_ERROR)
- As bandeiras são o quarto argumento (o terceiro é a profundidade máxima, 512 por padrão). Com o argumento nomeado
flags:você pula o terceiro. A forma sem nome éjson_decode($json, true, 512, JSON_THROW_ON_ERROR). - try { … }
- "Tente": se algo lá dentro lançar uma exceção, o PHP pula para o
catch. - catch (JsonException $e)
- "Capture" exceções do tipo
JsonException;$e->getMessage()traz a mensagem.
$ php throw.php JSON inválido: Syntax error
E sem o try, a exceção para o programa, com uma mensagem clara (o que é bem melhor do que seguir com null e quebrar dez linhas depois):
$ php throw2.php « json_decode('{"nome": "Ana",}', true, 512, JSON_THROW_ON_ERROR);
Fatal error: Uncaught JsonException: Syntax error in /home/ana/json/throw2.php:2
Stack trace:
#0 /home/ana/json/throw2.php(2): json_decode('{"nome": "Ana",...', true, 512, 4194304)
#1 {main}
thrown in /home/ana/json/throw2.php on line 2
(O 4194304 no rastro é o valor numérico da bandeira JSON_THROW_ON_ERROR.) A mesma bandeira funciona no json_encode.
Só conferir: json_validate PHP 8.3+
Quando você só quer saber se o texto é JSON válido, sem usar os dados, json_validate responde true ou false, gastando menos memória que o json_decode:
<?php
var_dump(json_validate('{"nome": "Ana"}'));
var_dump(json_validate('{"nome": "Ana",}'));
echo json_last_error_msg(), "\n";
$ php validate.php bool(true) bool(false) Syntax error
Em versões antigas (antes do 8.3), use json_decode e confira json_last_error() === JSON_ERROR_NONE.
Erro no json_encode: texto que não é UTF-8
O JSON exige UTF-8. Se uma string veio de um arquivo antigo, gravado em outra codificação (Latin-1, comum em sistemas do Windows), o json_encode falha:
<?php $ruim = "P\xE3o"; // "Pão" em Latin-1: o ã é o byte E3 var_dump(json_encode(['nome' => $ruim])); echo json_last_error_msg(), "\n"; var_dump(json_encode(['nome' => $ruim], JSON_INVALID_UTF8_SUBSTITUTE));
$ php utf8.php
bool(false)
Malformed UTF-8 characters, possibly incorrectly encoded
string(19) "{"nome":"P�o"}" « o byte ruim virou o caractere de substituição
"Caracteres UTF-8 malformados, possivelmente codificados de forma incorreta". O conserto de verdade é converter o texto para UTF-8 antes (mb_convert_encoding($ruim, 'UTF-8', 'ISO-8859-1'), veja mbstring).
Missão: o programa lê dois pedidos, e o segundo está quebrado. Hoje ele mostra um Warning confuso. Use JSON_THROW_ON_ERROR com try/catch (JsonException $e) para que o segundo mostre JSON inválido: Syntax error e nenhum aviso apareça.
<?php
$pedidos = [
'{"cliente": "Caio", "itens": 3}',
"{'cliente': 'Duda', 'itens': 1}",
];
foreach ($pedidos as $json) {
$p = json_decode($json, true);
echo "Pedido de {$p['cliente']} com {$p['itens']} itens\n";
}
<?php
$pedidos = [
'{"cliente": "Caio", "itens": 3}',
"{'cliente': 'Duda', 'itens': 1}",
];
foreach ($pedidos as $json) {
try {
$p = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
echo "Pedido de {$p['cliente']} com {$p['itens']} itens\n";
} catch (JsonException $e) {
echo "JSON inválido: ", $e->getMessage(), "\n";
}
}
JSON num arquivo
Juntando com a aula de arquivos: um arquivo .json é um ótimo "banquinho de dados" para coisas pequenas (configurações, uma lista de produtos, um cache). O ciclo é sempre o mesmo: ler o arquivo e decodificar, mudar o array, codificar e gravar.
<?php
$arquivo = __DIR__ . '/produtos.json';
$produtos = [
['nome' => 'Bolo de cenoura', 'preco' => 35.0],
['nome' => 'Torta de limão', 'preco' => 48.5],
];
file_put_contents($arquivo, json_encode($produtos, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$lidos = json_decode(file_get_contents($arquivo), true);
$lidos[] = ['nome' => 'Brigadeiro', 'preco' => 2.5];
file_put_contents($arquivo, json_encode($lidos, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
foreach (json_decode(file_get_contents($arquivo), true) as $p) {
echo $p['nome'], ': R$ ', number_format($p['preco'], 2, ',', '.'), "\n";
}
$ php arquivo.php
Bolo de cenoura: R$ 35,00
Torta de limão: R$ 48,50
Brigadeiro: R$ 2,50
$ cat produtos.json
[
{
"nome": "Bolo de cenoura",
"preco": 35 « o 35.0 foi gravado como 35
},
{
"nome": "Torta de limão",
"preco": 48.5
},
{
"nome": "Brigadeiro",
"preco": 2.5
}
]$ « sem quebra de linha no fim do arquivo
Dois detalhes reais: o 35.0 virou 35 (ao ler de volta, ele chega como int; use JSON_PRESERVE_ZERO_FRACTION se isso importar), e o json_encode não põe quebra de linha no final, por isso o prompt do terminal grudou no ]. Se quiser, grave json_encode(…) . "\n".
Para poucos dados, lidos e gravados por uma pessoa por vez, um arquivo JSON resolve. Quando muita gente grava ao mesmo tempo, ou os dados crescem, use um banco de dados (veja Banco de Dados: o Básico). E para enviar JSON como resposta de um site (uma API), você vai precisar do cabeçalho Content-Type: application/json; isso fica para a aula Criar uma API JSON.
Quando dá errado
json_decode devolveu NULL
O que significa: o texto não é JSON válido (aspas simples, vírgula sobrando, chave sem aspas, texto vazio porque o arquivo não foi lido…), ou o JSON era literalmente null.
Como consertar: mostre json_last_error_msg() logo depois, ou use JSON_THROW_ON_ERROR. Para achar o erro no texto, cole o JSON num validador ou confira com json_validate.
Fatal error: Uncaught Error: Cannot use object of type stdClass as array in /home/ana/json/dec-erro-obj.php:3
O que significa: você decodificou sem true (objeto) e tentou ler com colchetes (array).
Como consertar: json_decode($json, true), ou leia com $obj->campo. O contrário (-> num array) dá Warning: Attempt to read property "nome" on array.
O que significa: o json_encode devolveu false porque alguma string não está em UTF-8.
Como consertar: converta a origem para UTF-8 com mb_convert_encoding. Como paliativo, JSON_INVALID_UTF8_SUBSTITUTE troca os bytes ruins por � (o losango com interrogação).
[…] e veio um objeto {"0":…,"2":…}
O que significa: as chaves do array não são 0, 1, 2… seguidas (depois de unset ou array_filter).
Como consertar: json_encode(array_values($lista)).
Referência
| Função / bandeira | O que faz |
|---|---|
json_encode($dados, $flags) | Dados → texto JSON (ou false se falhar). |
json_decode($json, true) | Texto JSON → arrays (sem true: objetos stdClass); null se inválido. |
json_decode($json, true, 512, $flags) | Com profundidade máxima e bandeiras. |
json_last_error() / json_last_error_msg() | Código (0 = sem erro) e mensagem do último erro. |
json_validate($json) 8.3+ | true/false: é JSON válido? |
JSON_PRETTY_PRINT | Várias linhas, recuado. |
JSON_UNESCAPED_UNICODE | Acentos legíveis (sem ç). |
JSON_UNESCAPED_SLASHES | / sem \/. |
JSON_THROW_ON_ERROR | Erro vira JsonException. |
JSON_PRESERVE_ZERO_FRACTION | 10.0 continua 10.0. |
JSON_FORCE_OBJECT | Sempre {}. |
JSON_INVALID_UTF8_SUBSTITUTE | Troca bytes inválidos por �. |
Exercícios
1. Acentos legíveis
Complete para que maçã não vire maçã:
2. Array em vez de objeto
Complete para receber arrays associativos:
3. A mensagem do erro
Complete para mostrar por que o último json_decode falhou:
4. Qual destes textos é JSON válido?
5. $l = ['a', 'b', 'c']; unset($l[0]); O que json_encode($l) mostra?
6. O que json_decode('{"x": 1,}') faz?
Missão final: o estoque.json (segunda aba) guarda os produtos. Leia o arquivo como array, diminua em 1 o estoque do Bolo de cenoura, mostre Vendido: Bolo de cenoura, grave o arquivo de volta (bonito e com acentos legíveis) e mostre o conteúdo novo. O bolo deve ficar com "estoque": 4.
<?php $arquivo = __DIR__ . '/estoque.json';
[
{"nome": "Bolo de cenoura", "estoque": 5},
{"nome": "Torta de limão", "estoque": 2}
]
<?php
$arquivo = __DIR__ . '/estoque.json';
$produtos = json_decode(file_get_contents($arquivo), true, flags: JSON_THROW_ON_ERROR);
foreach ($produtos as $i => $p) {
if ($p['nome'] === 'Bolo de cenoura') {
$produtos[$i]['estoque']--;
echo "Vendido: {$p['nome']}\n";
}
}
file_put_contents($arquivo, json_encode($produtos, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n");
echo file_get_contents($arquivo);
Resumo
- JSON é texto com objetos
{}, listas[], textos em aspas duplas, números com ponto,true/false/null. json_encodetransforma arrays em JSON; useJSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODEpara gente ler (sem a segunda,çviraç).json_decode($json, true)devolve arrays; semtrue, objetosstdClass(lidos com->).- JSON inválido não avisa:
json_decodedevolvenull. UseJSON_THROW_ON_ERROR(oujson_last_error_msg()); para só conferir,json_validate(8.3+). - Arquivo JSON:
file_get_contents+json_decodepara ler,json_encode+file_put_contentspara gravar. - Listas com buracos viram objetos: use
array_values.
Próxima aula: Datas e Horas.