Pular para o conteúdo principal

Converter JSON em Java

O JSON para Java gera records ou POJOs a partir de um documento de exemplo no seu navegador. Campos numéricos opcionais viram wrapper — Integer em vez de int — porque um primitivo não pode conter null, então uma chave ausente é deserializada como 0 e fica indistinguível de um zero de verdade.

Só local
Estrutura
JSON de exemplo
0 B1 linhaLin. 1, Col. 1
Erro de sintaxe · Erro na linha 1, coluna 1

O documento termina cedo demais — parece incompleto.


^
Código geradosomente leitura
0 B1 linha
Se possível, cole mais de um registro. Assim, campos opcionais ausentes em algumas linhas podem ser detectados.

Como funciona o Converter JSON em Java?

Os tipos primitivos de Java tornam a questão dos campos opcionais concreta em vez de estilística, e errar nela produz um bug realmente difícil de encontrar.

Um primitivo não pode estar ausente

int count deserializa uma chave count ausente como 0. Sem exceção, sem aviso — e depois disso não há como distinguir um registro em que o count nunca foi enviado de um em que o count realmente era zero. O mesmo vale para boolean, que vira false, e para double, que vira 0.0. Por isso os campos opcionais e anuláveis são embrulhados em wrapper: Integer, Long, Double, Boolean, todos capazes de conter null e de deixar você fazer a pergunta.

Generics não comportam primitivos de jeito nenhum

List<int> não compila. Todo elemento de lista é embrulhado em wrapper, independente de ser opcional, e é por isso que um array de inteiros vira List<Integer>.

record ou POJO

Um record é a melhor escolha padrão no Java 17 e posteriores: imutável, sem boilerplate, equals, hashCode e toString de graça, e o Jackson os suporta desde a 2.12. Um POJO com campos privados e getters e setters é o que uma base de código no Java 8 ou um framework que exige construtor sem argumentos vai preferir, então ambos estão disponíveis.

As anotações do Jackson carregam a chave original

A convenção de Java é camelCase ; as chaves de JSON frequentemente não são. Em vez de depender de um PropertyNamingStrategy configurado em outro lugar, cada componente recebe um @JsonProperty explícito com a chave exata do documento. Assim a classe faz o binding corretamente, não importa como o ObjectMapper esteja configurado.

Um tipo público por arquivo

Java permite exatamente um tipo público de nível superior por arquivo-fonte, então um documento que gera vários tipos não compila se colado de uma vez só. A saída avisa isso em um comentário no cabeçalho: salve cada tipo como <TypeName>.java, ou aninhe-os dentro de uma classe externa. Emitir um arquivo que o javac rejeita sem mencionar isso é o tipo de pequena desonestidade que desperdiça dez minutos.

Exemplo

[
  { "user_id": 1, "retries": 0, "tags": ["a"] },
  { "user_id": 2, "tags": [] }
]

Gerado

public record User(
    @JsonProperty("user_id") int userId,
    @JsonProperty("retries") Integer retries,
    @JsonProperty("tags") List<String> tags
) {}

Quais opções e casos-limite o Converter JSON em Java suporta?

Referência técnica e opções
ParâmetroTipoPadrãoComportamento e casos-limite
Formatorecord / POJOrecordrecord no Java 17+ para um DTO imutável sem código repetitivo. POJO para Java 8, ou para qualquer coisa que precise de um construtor sem argumentos e setters.
Pacoteidentificador(nenhum)Adiciona uma declaração de pacote. Deixe vazio para um arquivo de rascunho; defina-o para corresponder ao diretório onde o arquivo será salvo.
JacksonbooleanoonEmite @JsonProperty com a chave JSON exata, para que o binding não dependa de uma estratégia de nomenclatura do ObjectMapper configurada em outro lugar.
Encapsular optionalsbooleanoonUm int opcional se torna Integer. Desligar isso faz com que uma chave ausente seja desserializada como 0, o que é indistinguível de um zero real.
stringtipo JavaStringFormatos detectados não são mapeados para Instant, LocalDate ou UUID. O Jackson precisa que JavaTimeModule esteja registrado para os dois primeiros, e ele lança um erro em qualquer registro cujo formato seja diferente.
inteirotipo Javaint / longlong quando qualquer amostra excede 2,147,483,647. Um número JSON não tem largura declarada, então a largura é lida a partir dos valores.
arraytipo JavaList<T>Os elementos são sempre boxed, porque genéricos não podem conter tipos primitivos. Um array misto se torna List<Object>.
Objeto aninhadotipotipo próprioPromovido para seu próprio registro ou classe, declarado antes do tipo que o usa. Formas idênticas compartilham um tipo em vez de gerar duplicados numerados.

Perguntas frequentes

Por que um campo é int e outro é Integer?

O Integer aparecia em alguns registros e em outros não. Um int primitivo não pode conter null, então o Jackson deserializa uma chave ausente como 0 — e nada depois disso consegue distinguir isso de um registro que realmente enviou zero. Integer pode ser null, o que torna a ausência visível. Campos presentes em todos os registros continuam primitivos, o que é mais barato e mais simples.

Posso colar a saída inteira em um arquivo só?

Não, se ela gerou mais de um tipo. Java permite exatamente um tipo público de nível superior por arquivo-fonte, então o javac vai rejeitar. Salve cada tipo como <TypeName>.java, ou transforme os aninhados em static nested classes dentro de uma classe externa. A saída diz no topo em qual caso você está.

record ou POJO?

record se você está no Java 17 ou posterior e o objeto é um dado que chegou de algum lugar: ele é imutável, fornece equals, hashCode e toString corretos, e o Jackson suporta records desde a 2.12. POJO se você está no Java 8, se um framework exige construtor sem argumentos e setters (JPA mais antigo, algumas configurações do Spring), ou se o objeto realmente precisa ser mutado depois de construído.

Por que meu timestamp é String em vez de Instant?

Porque o Jackson não consegue convertê-lo em Instant sem o JavaTimeModule registrado, e porque ele lança exceção no primeiro registro cujo formato difere do esperado. APIs reais enviam segundos Unix, milissegundos Unix, ISO 8601 com e sem offsets, e ocasionalmente algo regional. O formato detectado está anotado na referência acima para que você troque o tipo deliberadamente e adicione um conversor, se o formato precisar de um.

Preciso do Lombok?

Não, e o gerador deliberadamente não emite @Data nem @Builder. No Java 17+ um record cobre o que o @Value do Lombok fazia, sem um processador de anotações no seu build. A saída POJO é Java puro com getters e setters de verdade, que compila em qualquer lugar. Se o seu projeto já usa Lombok, você pode trocar os accessors você mesmo, mas nada aqui exige isso.

Meu JSON é enviado?

Não. Parse, inferência de tipos e renderização rodam todos no seu navegador — abra o painel Network enquanto cola e você não verá nada sair. Os exemplos que as pessoas colam em um gerador de classes costumam ser respostas reais de um sistema com o qual estão fazendo integração, que é exatamente o material que não deveria passar por um servidor desconhecido.