JSON y serialización
Cómo usar JSON con Flutter.
Es difícil pensar en una aplicación móvil que no necesite comunicarse con un servidor web o almacenar datos estructurados fácilmente en algún momento. Al crear aplicaciones conectadas a la red, lo más probable es que necesiten consumir JSON, tarde o temprano.
Esta guía analiza las formas de usar JSON con Flutter. Cubre qué solución de JSON usar en diferentes escenarios y por qué.
¿Qué método de serialización JSON es adecuado para mí?
#Este artículo cubre dos estrategias generales para trabajar con JSON:
- Serialización manual
- Serialización automatizada mediante generación de código
Los diferentes proyectos conllevan diferentes complejidades y casos de uso. Para proyectos más pequeños de prueba de concepto o prototipos rápidos, el uso de generadores de código puede ser excesivo. Para aplicaciones con varios modelos JSON de mayor complejidad, la codificación a mano puede volverse rápidamente tediosa, repetitiva y propensa a pequeños errores.
Usa serialización manual para proyectos más pequeños
#La decodificación manual de JSON se refiere al uso del decodificador JSON integrado en
dart:convert. Implica pasar la cadena JSON sin procesar a la función jsonDecode()
y luego buscar los valores que necesitas en el
Map<String, dynamic> resultante.
No tiene dependencias externas ni un proceso de configuración particular,
y es ideal para una prueba de concepto rápida.
La decodificación manual no funciona bien a medida que tu proyecto crece. Escribir la lógica de decodificación a mano puede volverse difícil de gestionar y propenso a errores. Si cometes un error tipográfico al acceder a un campo JSON inexistente, tu código lanzará un error durante el tiempo de ejecución.
Si no tienes muchos modelos JSON en tu proyecto y buscas probar un concepto rápidamente, la serialización manual podría ser la forma en que deseas comenzar. Para ver un ejemplo de codificación manual, consulta Serializar JSON manualmente usando dart:convert.
Usa la generación de código para proyectos medianos y grandes
#La serialización JSON con generación de código significa que una biblioteca externa
genera el código repetitivo de codificación por ti. Después de una configuración inicial,
ejecutas un observador de archivos (file watcher) que genera el código a partir de tus clases de modelo.
Por ejemplo, json_serializable
y built_value son este
tipo de bibliotecas.
Este enfoque escala bien para proyectos más grandes. No se requiere código repetitivo escrito a mano, y los errores tipográficos al acceder a los campos JSON se detectan en tiempo de compilación. La desventaja de la generación de código es que requiere cierta configuración inicial. Además, los archivos fuente generados pueden producir desorden visual en el navegador de tu proyecto.
Es posible que desees utilizar código generado para la serialización JSON cuando tengas un proyecto mediano o grande. Para ver un ejemplo de codificación JSON basada en la generación de código, consulta Serializar JSON usando bibliotecas de generación de código.
¿Existe un equivalente a GSON/Jackson/Moshi en Flutter?
#
La respuesta simple es no.
Dicha biblioteca requeriría el uso de reflexión (reflection) en tiempo de ejecución, la cual está deshabilitada en Flutter. La reflexión en tiempo de ejecución interfiere con el tree shaking (eliminación de código muerto), que Dart admite desde hace bastante tiempo. Con el tree shaking, puedes "sacudir" el código no utilizado de tus compilaciones de producción. Esto optimiza el tamaño de la aplicación significativamente.
Dado que la reflexión hace que todo el código se use implícitamente por defecto, dificulta el tree shaking. Las herramientas no pueden saber qué partes no se utilizan en tiempo de ejecución, por lo que el código redundante es difícil de eliminar. Los tamaños de las aplicaciones no se pueden optimizar fácilmente cuando se utiliza la reflexión.
Aunque no puedes usar la reflexión en tiempo de ejecución con Flutter, algunas bibliotecas te ofrecen API igualmente fáciles de usar, pero basadas en la generación de código. Este enfoque se cubre con más detalle en la sección de bibliotecas de generación de código.
Serializar JSON manualmente usando dart:convert
#La serialización JSON básica en Flutter es muy simple. Flutter tiene una biblioteca integrada
dart:convert que incluye un codificador y decodificador JSON sencillo.
El siguiente JSON de ejemplo implementa un modelo de usuario simple.
{
"name": "John Smith",
"email": "john@example.com"
}
Con dart:convert,
puedes serializar este modelo JSON de dos maneras.
Serializar JSON en línea (inline)
#Al consultar la documentación de dart:convert,
verás que puedes decodificar el JSON llamando a la función
jsonDecode(), pasando la cadena JSON como argumento del método.
final user = jsonDecode(jsonString) as Map<String, dynamic>;
print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
Desafortunadamente, jsonDecode() devuelve un tipo dynamic, lo que significa
que no conoces los tipos de los valores hasta el tiempo de ejecución. Con este enfoque,
pierdes la mayoría de las características del lenguaje de tipado estático: seguridad de tipos,
autocompletado y, lo más importante, excepciones en tiempo de compilación. Tu código se
volverá instantáneamente más propenso a errores.
Por ejemplo, cada vez que accedes a los campos name o email, podrías
introducir rápidamente un error tipográfico. Un error tipográfico del cual el
compilador no tiene conocimiento ya que el JSON reside en una estructura de mapa.
Serializar JSON dentro de clases de modelo
#Combate los problemas mencionados anteriormente introduciendo una clase de modelo simple,
llamada User en este ejemplo. Dentro de la clase User, encontrarás:
- Un constructor
User.fromJson(), para construir una nueva instancia deUsera partir de una estructura de mapa. - Un método
toJson(), que convierte una instancia deUseren un mapa.
Con este enfoque, el código de llamada puede tener seguridad de tipos,
autocompletado para los campos name y email, y excepciones en tiempo de compilación.
Si cometes errores tipográficos o tratas los campos como int en lugar de String,
la aplicación no compilará, en lugar de fallar en tiempo de ejecución.
user.dart
class User {
final String name;
final String email;
User(this.name, this.email);
User.fromJson(Map<String, dynamic> json)
: name = json['name'] as String,
email = json['email'] as String;
Map<String, dynamic> toJson() => {'name': name, 'email': email};
}
La responsabilidad de la lógica de decodificación ahora se traslada al interior del propio modelo. Con este nuevo enfoque, puedes decodificar un usuario fácilmente.
final userMap = jsonDecode(jsonString) as Map<String, dynamic>;
final user = User.fromJson(userMap);
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
Para codificar un usuario, pasa el objeto User a la función jsonEncode().
No necesitas llamar al método toJson(), ya que jsonEncode()
ya lo hace por ti.
String json = jsonEncode(user);
Con este enfoque, el código de llamada no tiene que preocuparse en absoluto por la
serialización JSON. Sin embargo, la clase de modelo definitivamente todavía tiene que hacerlo.
En una aplicación de producción, desearías asegurarte de que la serialización
funcione correctamente. En la práctica, tanto el método User.fromJson() como User.toJson()
necesitan tener pruebas unitarias para verificar el comportamiento correcto.
Sin embargo, los escenarios del mundo real no siempre son tan simples. A veces, las respuestas de la API JSON son más complejas, por ejemplo porque contienen objetos JSON aninados que deben analizarse a través de su propia clase de modelo.
Sería genial si hubiera algo que se encargara de la codificación y decodificación de JSON por ti. ¡Afortunadamente, lo hay!
Serializar JSON usando bibliotecas de generación de código
#Aunque existen otras bibliotecas disponibles, esta guía utiliza
json_serializable, un generador automático de código fuente que
genera el código repetitivo de serialización JSON por ti.
Dado que el código de serialización ya no se escribe ni se mantiene a mano, minimizas el riesgo de tener excepciones de serialización JSON en tiempo de ejecución.
Configuración de json_serializable en un proyecto
#Para incluir json_serializable en tu proyecto, necesitas una dependencia
normal y dos dev dependencies (dependencias de desarrollo). En resumen, las dev dependencies
son dependencias que no se incluyen en el código fuente de nuestra aplicación, sino que
solo se utilizan en el entorno de desarrollo.
Para añadir las dependencias, ejecuta flutter pub add:
flutter pub add json_annotation dev:build_runner dev:json_serializable
Ejecuta flutter pub get dentro de la carpeta raíz de tu proyecto
(o haz clic en Packages get en tu editor)
para que estas nuevas dependencias estén disponibles en tu proyecto.
Crear clases de modelo al estilo de json_serializable
#A continuación se muestra cómo convertir la clase User en una clase
json_serializable. En aras de la simplicidad,
este código utiliza el modelo JSON simplificado
de los ejemplos anteriores.
user.dart
import 'package:json_annotation/json_annotation.dart';
/// This allows the `User` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'user.g.dart';
/// An annotation for the code generator to know that this class needs the
/// JSON serialization logic to be generated.
@JsonSerializable()
class User {
User(this.name, this.email);
String name;
String email;
/// A necessary factory constructor for creating a new User instance
/// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
/// The constructor is named after the source class, in this case, User.
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
/// `toJson` is the convention for a class to declare support for serialization
/// to JSON. The implementation simply calls the private, generated
/// helper method `_$UserToJson`.
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Con esta configuración, el generador de código fuente genera código para codificar
y decodificar los campos name y email desde JSON.
Si es necesario, también es fácil personalizar la estrategia de nomenclatura.
Por ejemplo, si la API devuelve objetos con snake_case,
y deseas utilizar lowerCamelCase en tus modelos,
puedes utilizar la anotación @JsonKey con un parámetro name:
/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;
Lo mejor es que tanto el servidor como el cliente sigan la misma estrategia de nomenclatura.
@JsonSerializable() proporciona el enum fieldRename para convertir totalmente los
campos de Dart en claves JSON.
Modificar @JsonSerializable(fieldRename: FieldRename.snake) es equivalente a
añadir @JsonKey(name: '<snake_case>') a cada campo.
A veces los datos del servidor son inciertos, por lo que es necesario verificar y proteger los datos
en el cliente.
Otras anotaciones @JsonKey de uso común incluyen:
/// Tell json_serializable to use "defaultValue" if the JSON doesn't
/// contain this key or if the value is `null`.
@JsonKey(defaultValue: false)
final bool isAdult;
/// When `true` tell json_serializable that JSON must contain the key,
/// If the key doesn't exist, an exception is thrown.
@JsonKey(required: true)
final String id;
/// When `true` tell json_serializable that generated code should
/// ignore this field completely.
@JsonKey(ignore: true)
final String verificationCode;
Ejecución de la utilidad de generación de código
#Al crear clases de tipo json_serializable por primera vez,
obtendrás errores similares a los siguientes:
Target of URI hasn't been generated: 'user.g.dart'.
Estos errores son completamente normales y se deben simplemente a que el código generado para la clase de modelo aún no existe. Para resolver esto, ejecuta el generador de código que genera el código repetitivo de serialización.
Hay dos formas de ejecutar el generador de código.
Generación de código por única vez
#Al ejecutar dart run build_runner build --delete-conflicting-outputs en la raíz del proyecto,
generas código de serialización JSON para tus modelos siempre que sea necesario.
Esto activa una compilación por única vez que recorre los archivos fuente, selecciona los
relevantes y genera el código de serialización necesario para ellos.
Aunque esto es conveniente, sería genial si no tuvieras que ejecutar la compilación manualmente cada vez que realices cambios en tus clases de modelo.
Generación de código de forma continua
#Un watcher hace que nuestro proceso de generación de código fuente sea más conveniente.
Observa los cambios en los archivos de nuestro proyecto y compila automáticamente los
archivos necesarios cuando se requiere. Inicia el watcher ejecutando
dart run build_runner watch --delete-conflicting-outputs en la raíz del proyecto.
Es seguro iniciar el watcher una vez y dejarlo ejecutándose en segundo plano.
Consumir modelos de json_serializable
#Para decodificar una cadena JSON al estilo de json_serializable,
en realidad no tienes que realizar ningún cambio en nuestro código anterior.
final userMap = jsonDecode(jsonString) as Map<String, dynamic>;
final user = User.fromJson(userMap);
Lo mismo ocurre con la codificación. La API de llamada es la misma que antes.
String json = jsonEncode(user);
Con json_serializable,
puedes olvidarte de cualquier serialización manual de JSON en la clase User.
El generador de código fuente crea un archivo llamado user.g.dart,
que tiene toda la lógica de serialización necesaria.
Ya no tienes que escribir pruebas automatizadas para asegurarte
de que la serialización funcione; ahora es
responsabilidad de la biblioteca asegurarse de que la serialización funcione
adecuadamente.
Generar código para clases anidadas
#Es posible que tengas código que contenga clases anidadas dentro de una clase.
Si ese es el caso, y has intentado pasar la clase en formato JSON
como argumento a un servicio (como Firebase, por ejemplo),
es posible que hayas experimentado un error de argumento no válido (Invalid argument).
Considera la siguiente clase Address:
import 'package:json_annotation/json_annotation.dart';
part 'address.g.dart';
@JsonSerializable()
class Address {
String street;
String city;
Address(this.street, this.city);
factory Address.fromJson(Map<String, dynamic> json) =>
_$AddressFromJson(json);
Map<String, dynamic> toJson() => _$AddressToJson(this);
}
La clase Address está anidada dentro de la clase User:
import 'package:json_annotation/json_annotation.dart';
import 'address.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
User(this.name, this.address);
String name;
Address address;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Al ejecutar
dart run build_runner build --delete-conflicting-outputs
en la terminal se crea
el archivo *.g.dart, pero la función privada _$UserToJson()
se parece a lo siguiente:
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'name': instance.name,
'address': instance.address,
};
Todo parece estar bien ahora, pero si haces un print() del objeto de usuario:
Address address = Address('My st.', 'New York');
User user = User('John', address);
print(user.toJson());
El resultado es:
{name: John, address: Instance of 'address'}
Cuando lo que probablemente quieres es una salida como la siguiente:
{name: John, address: {street: My st., city: New York}}
Para que esto funcione, pasa explicitToJson: true en la anotación @JsonSerializable()
sobre la declaración de la clase. La clase User ahora se ve de la siguiente manera:
import 'package:json_annotation/json_annotation.dart';
import 'address.dart';
part 'user.g.dart';
@JsonSerializable(explicitToJson: true)
class User {
User(this.name, this.address);
String name;
Address address;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Para obtener más información, consulta explicitToJson
en la clase
JsonSerializable
del paquete json_annotation.
Referencias adicionales
#Para más información, consulta los siguientes recursos:
- La documentación de
dart:convertyJsonCodec - El paquete
json_serializableen pub.dev - Los ejemplos de
json_serializableen GitHub - El codelab Sumérgete en los patrones y registros de Dart
- Esta guía definitiva sobre cómo analizar JSON en Dart/Flutter
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.