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.
O documento termina cedo demais — parece incompleto.
^
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?
| Parâmetro | Tipo | Padrão | Comportamento e casos-limite |
|---|---|---|---|
| Formato | record / POJO | record | record 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. |
| Pacote | identificador | (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. |
| Jackson | booleano | on | Emite @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 optionals | booleano | on | Um 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. |
| string | tipo Java | String | Formatos 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. |
| inteiro | tipo Java | int / long | long 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. |
| array | tipo Java | List<T> | Os elementos são sempre boxed, porque genéricos não podem conter tipos primitivos. Um array misto se torna List<Object>. |
| Objeto aninhado | tipo | tipo próprio | Promovido 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.