Conversor JSON para Dart
Gere classes Dart a partir de JSON com fromJson e toJson
Entrada JSON
Saída Dart
O que é a conversão de JSON para Dart?
A conversão de JSON para Dart recebe um objeto JSON bruto e produz definições de classes Dart com campos tipados, um construtor nomeado, uma factory fromJson e um método toJson. O Dart não possui reflexão em tempo de execução no Flutter (dart:mirrors está desabilitado), portanto não é possível desserializar JSON em objetos tipados sem escrever código de mapeamento explícito. Toda resposta de REST API, documento Firebase ou payload de configuração precisa de uma classe de modelo Dart correspondente antes que você possa acessar seus campos com segurança de tipos.
Uma classe de modelo Dart típica para JSON declara campos final para cada chave, um construtor com parâmetros nomeados (usando a palavra-chave required para campos não anuláveis), um construtor factory chamado fromJson que lê de um Map de String para dynamic, e um método toJson que retorna um Map de String para dynamic. Objetos JSON aninhados se tornam classes separadas. Arrays se tornam campos List tipados. Valores JSON anuláveis usam a sintaxe de null safety do Dart com o sufixo ? no tipo.
Escrever essas classes de modelo à mão significa ler cada chave JSON, decidir o tipo Dart, criar o cast fromJson para cada campo (incluindo mapeamento de listas com .map().toList()), construir o literal de mapa toJson e repetir o processo para cada objeto aninhado. Para um objeto JSON com 12 campos e 2 objetos aninhados, isso significa 3 classes, 6 linhas de factory e dezenas de expressões de cast. Um conversor produz tudo isso em milissegundos a partir de um único cole.
Por que usar um conversor JSON para Dart?
Escrever classes de modelo Dart manualmente a partir de JSON envolve ler nomes de campos, deduzir tipos a partir de valores de exemplo, escrever casts fromJson com tratamento correto de nulos e repetir o processo para objetos aninhados. Quando o formato da API muda, cada atualização de campo toca o construtor, o fromJson e o toJson. Um conversor elimina esse trabalho repetitivo.
Casos de uso de JSON para Dart
Mapeamento de tipos JSON para Dart
Cada valor JSON é mapeado para um tipo Dart específico. A tabela abaixo mostra como o conversor traduz cada tipo JSON. A coluna Alternativa mostra os tipos usados em cenários de mapeamento menos comuns ou manual.
| Tipo JSON | Exemplo | Tipo Dart | Alternativa |
|---|---|---|---|
| string | "hello" | String | String |
| number (integer) | 42 | int | int |
| number (float) | 3.14 | double | double |
| boolean | true | bool | bool |
| null | null | dynamic | Null / dynamic |
| object | {"k": "v"} | NestedClass | Map<String, dynamic> |
| array of strings | ["a", "b"] | List<String> | List<String> |
| array of objects | [{"id": 1}] | List<Item> | List<Item> |
| mixed array | [1, "a"] | List<dynamic> | List<dynamic> |
Abordagens de serialização JSON em Dart
Dart e Flutter oferecem várias formas de lidar com serialização JSON. O fromJson/toJson manual é a abordagem mais simples e não requer geração de código. Para projetos maiores, soluções baseadas em build_runner como json_serializable e freezed geram o código repetitivo em tempo de compilação, reduzindo erros e mantendo os modelos consistentes com o contrato JSON.
| Abordagem | Descrição | Fonte |
|---|---|---|
| json_serializable | Code-generation-based JSON serialization. Generates .g.dart files at build time via build_runner. Type-safe and compile-time verified. | Official (Google) |
| freezed | Generates immutable data classes with copyWith, fromJson/toJson, equality, and pattern matching support. Built on top of json_serializable. | Community (rrousselGit) |
| built_value | Generates immutable value types with serialization. Enforces immutability patterns. Used in larger codebases with strict data modeling. | |
| dart_mappable | Annotation-based mapper that generates fromJson, toJson, copyWith, and equality. Simpler setup than freezed with similar features. | Community |
| Manual fromJson/toJson | Hand-written factory constructors and toJson methods. No code generation needed. Full control over the mapping logic. | Built-in Dart |
Manual vs json_serializable vs freezed
Dart tem três abordagens comuns para classes de modelo JSON. O fromJson/toJson manual tem zero dependências. json_serializable automatiza o código de mapeamento. freezed adiciona imutabilidade, copyWith e correspondência de padrões sobre o json_serializable.
Exemplos de código
Estes exemplos mostram como usar classes Dart geradas para desserialização de JSON, como configurar json_serializable com build_runner e como gerar classes Dart programaticamente a partir de JavaScript e Python.
import 'dart:convert';
class User {
final int id;
final String name;
final String email;
final bool active;
final Address address;
final List<String> tags;
User({
required this.id,
required this.name,
required this.email,
required this.active,
required this.address,
required this.tags,
});
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
active: json['active'] as bool,
address: Address.fromJson(json['address'] as Map<String, dynamic>),
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
'active': active,
'address': address.toJson(),
'tags': tags,
};
}
class Address {
final String street;
final String city;
final String zip;
Address({required this.street, required this.city, required this.zip});
factory Address.fromJson(Map<String, dynamic> json) => Address(
street: json['street'] as String,
city: json['city'] as String,
zip: json['zip'] as String,
);
Map<String, dynamic> toJson() => {'street': street, 'city': city, 'zip': zip};
}
void main() {
final jsonStr = '{"id":1,"name":"Alice","email":"alice@example.com","active":true,"address":{"street":"123 Main","city":"Springfield","zip":"12345"},"tags":["admin","user"]}';
final user = User.fromJson(jsonDecode(jsonStr));
print(user.name); // -> Alice
print(jsonEncode(user.toJson())); // -> round-trip back to JSON
}// pubspec.yaml dependencies:
// json_annotation: ^4.8.0
// dev_dependencies:
// build_runner: ^2.4.0
// json_serializable: ^6.7.0
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart'; // generated by: dart run build_runner build
@JsonSerializable()
class User {
final int id;
final String name;
final String email;
@JsonKey(name: 'is_active')
final bool isActive;
final List<String> tags;
User({
required this.id,
required this.name,
required this.email,
required this.isActive,
required this.tags,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
// Run code generation:
// dart run build_runner build --delete-conflicting-outputsfunction jsonToDart(obj, name = "Root") {
const classes = [];
function infer(val, fieldName) {
if (val === null) return "dynamic";
if (typeof val === "string") return "String";
if (typeof val === "number") return Number.isInteger(val) ? "int" : "double";
if (typeof val === "boolean") return "bool";
if (Array.isArray(val)) {
const first = val.find(v => v !== null);
if (!first) return "List<dynamic>";
return `List<${infer(first, fieldName)}>`;
}
if (typeof val === "object") {
const cls = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
build(val, cls);
return cls;
}
return "dynamic";
}
function build(obj, cls) {
const fields = Object.entries(obj).map(([k, v]) =>
` final ${infer(v, k)} ${k};`
);
classes.push(`class ${cls} {\n${fields.join("\n")}\n}`);
}
build(obj, name);
return classes.join("\n\n");
}
console.log(jsonToDart({ id: 1, name: "Alice", scores: [98, 85] }, "User"));
// class User {
// final int id;
// final String name;
// final List<int> scores;
// }import json
def json_to_dart(obj: dict, class_name: str = "Root") -> str:
classes = []
def infer(val, name):
if val is None:
return "dynamic"
if isinstance(val, bool):
return "bool"
if isinstance(val, int):
return "int"
if isinstance(val, float):
return "double"
if isinstance(val, str):
return "String"
if isinstance(val, list):
if not val:
return "List<dynamic>"
return f"List<{infer(val[0], name)}>"
if isinstance(val, dict):
cls = name[0].upper() + name[1:]
build(val, cls)
return cls
return "dynamic"
def build(obj, cls):
fields = [f" final {infer(v, k)} {k};" for k, v in obj.items()]
classes.append(f"class {cls} {{\n" + "\n".join(fields) + "\n}")
build(obj, class_name)
return "\n\n".join(classes)
data = json.loads('{"id": 1, "name": "Alice", "active": true}')
print(json_to_dart(data, "User"))
# class User {
# final int id;
# final String name;
# final bool active;
# }