Convertir JSON a clases modelo Dart es una de las tareas más comunes en el desarrollo Flutter.
Por qué necesitas clases modelo
Mapeo básico de tipos JSON a Dart
| Tipo JSON | Tipo Dart | Ejemplo |
|---|---|---|
| string | String | "hello" → "hello" |
| number (int) | int | 42 → 42 |
| number (float) | double | 3.14 → 3.14 |
| boolean | bool | true → true |
| null | Null / dynamic | null → null |
| array | List<T> | [1,2,3] → [1,2,3] |
| object | Map / Class | {} → User() |
Clase modelo manual (fromJson / toJson)
El enfoque más directo es escribir las clases a mano:
// JSON input
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"isActive": true,
"score": 95.5
}
// Dart model class
class User {
final int id;
final String name;
final String email;
final bool isActive;
final double score;
User({
required this.id,
required this.name,
required this.email,
required this.isActive,
required this.score,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
isActive: json['isActive'] as bool,
score: (json['score'] as num).toDouble(),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'isActive': isActive,
'score': score,
};
}
}Manejo de objetos anidados
Crea clases Dart separadas para cada nivel:
// JSON with nested object
{
"id": 1,
"name": "John",
"address": {
"street": "123 Main St",
"city": "Springfield",
"zipCode": "62701"
}
}
// Dart classes
class Address {
final String street;
final String city;
final String zipCode;
Address({required this.street, required this.city, required this.zipCode});
factory Address.fromJson(Map<String, dynamic> json) {
return Address(
street: json['street'] as String,
city: json['city'] as String,
zipCode: json['zipCode'] as String,
);
}
Map<String, dynamic> toJson() => {
'street': street, 'city': city, 'zipCode': zipCode,
};
}
class User {
final int id;
final String name;
final Address address;
User({required this.id, required this.name, required this.address});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
address: Address.fromJson(json['address'] as Map<String, dynamic>),
);
}
Map<String, dynamic> toJson() => {
'id': id, 'name': name, 'address': address.toJson(),
};
}Manejo de listas y arrays
Los arrays JSON se mapean a tipos List de Dart:
// JSON with arrays
{
"name": "John",
"hobbies": ["coding", "reading", "gaming"],
"orders": [
{"id": 1, "product": "Laptop", "price": 999.99},
{"id": 2, "product": "Mouse", "price": 29.99}
]
}
// Dart
class User {
final String name;
final List<String> hobbies;
final List<Order> orders;
User({required this.name, required this.hobbies, required this.orders});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
hobbies: List<String>.from(json['hobbies'] as List),
orders: (json['orders'] as List)
.map((e) => Order.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
}Mejores prácticas de Null Safety
Con el null safety de Dart, maneja correctamente los campos nullable:
- Usa `required` para campos obligatorios
- Usa `?` para tipos nullable
- Proporciona valores por defecto con `??`
- Usa `late` solo cuando la inicialización es segura
class User {
final int id;
final String name;
final String? bio; // nullable
final String avatarUrl; // with default
User({
required this.id,
required this.name,
this.bio,
this.avatarUrl = 'https://example.com/default-avatar.png',
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
bio: json['bio'] as String?,
avatarUrl: json['avatarUrl'] as String? ?? 'https://example.com/default-avatar.png',
);
}
}Usando json_serializable
Para proyectos grandes, json_serializable automatiza el código repetitivo:
Paso 1: Agregar dependencias
# pubspec.yaml
dependencies:
json_annotation: ^4.8.1
dev_dependencies:
json_serializable: ^6.7.1
build_runner: ^2.4.6Paso 2: Crear modelo con anotaciones
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final int id;
final String name;
final String email;
@JsonKey(name: 'is_active')
final bool isActive;
@JsonKey(defaultValue: 0.0)
final double score;
User({
required this.id,
required this.name,
required this.email,
required this.isActive,
required this.score,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}Paso 3: Ejecutar Build Runner
dart run build_runner build --delete-conflicting-outputsUsando Freezed para modelos inmutables
El paquete freezed genera clases inmutables con copyWith y serialización JSON:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required int id,
required String name,
required String email,
@Default(false) bool isActive,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}Errores comunes y soluciones
| Error | Solución |
|---|---|
| Usar `json['key']` sin verificar null | Usar `json['key'] as String? ?? ''` |
| Olvidar convertir objetos anidados | Llamar `NestedClass.fromJson()` |
| Usar `int` para todos los números | Usar `num` y convertir |
| No manejar claves faltantes | Usar `containsKey()` o valores por defecto |
| Retornar tipo incorrecto en toJson | Asegurar que objetos anidados llamen `.toJson()` |
FAQ
¿Cuál es la mejor forma de convertir JSON a clases Dart?
Proyectos pequeños: conversión manual. Proyectos grandes: json_serializable o freezed.
¿Cómo manejo claves JSON dinámicas?
Usa Map<String, dynamic>.
¿json_serializable o freezed?
json_serializable para serialización simple, freezed para inmutabilidad.
¿Cómo manejo las fechas?
Usa DateTime.parse() para strings ISO 8601.