JSON a Dart

Genera clases Dart desde JSON con fromJson y toJson

Prueba un ejemplo
Nombre de la clase raíz:

Entrada JSON

Salida Dart

Se ejecuta localmente · Es seguro pegar secretos
Las clases Dart aparecerán aquí…

¿Qué es la conversión de JSON a Dart?

La conversión de JSON a Dart toma un objeto JSON en bruto y produce definiciones de clases Dart con campos tipados, un constructor con nombre, una factory fromJson y un método toJson. Dart no tiene reflexión en tiempo de ejecución en Flutter (dart:mirrors está deshabilitado), por lo que no puedes deserializar JSON en objetos tipados sin escribir código de mapeo explícito. Cada respuesta de API REST, documento de Firebase o payload de configuración necesita una clase modelo Dart correspondiente antes de poder acceder a sus campos con seguridad de tipos.

Una clase modelo Dart típica para JSON declara campos final por cada clave, un constructor con parámetros con nombre (usando la palabra clave required para los campos no nulables), una factory constructor llamada fromJson que lee desde un Map de String a dynamic, y un método toJson que devuelve un Map de String a dynamic. Los objetos JSON anidados se convierten en clases separadas. Los arrays se convierten en campos List tipados. Los valores JSON nulables usan la sintaxis null-safety de Dart con el sufijo ? en el tipo.

Escribir estas clases modelo a mano implica leer cada clave JSON, decidir el tipo Dart, crear el cast de fromJson para cada campo (incluyendo el mapeo de listas con .map().toList()), construir el mapa literal de toJson y repetir el proceso para cada objeto anidado. Para un objeto JSON con 12 campos y 2 objetos anidados, eso significa 3 clases, 6 líneas de factory y decenas de expresiones de cast. Un conversor produce todo esto en milisegundos a partir de un único pegado.

¿Por qué usar un conversor de JSON a Dart?

Escribir clases modelo Dart desde JSON de forma manual implica leer nombres de campo, inferir tipos a partir de valores de ejemplo, escribir casts fromJson con manejo correcto de nulos y repetir el proceso para los objetos anidados. Cuando cambia la estructura de la API, cada actualización de campo toca el constructor, el fromJson y el toJson. Un conversor elimina ese trabajo repetitivo.

Generación instantánea de clases
Pega tu JSON y obtén clases Dart completas con constructores, factories fromJson y métodos toJson en menos de un segundo. Los objetos anidados y las listas se detectan y mapean automáticamente.
🔒
Procesamiento con privacidad garantizada
La conversión se ejecuta íntegramente en tu navegador con JavaScript. Tu JSON nunca sale de tu máquina. Las claves de API, los tokens y los datos de producción se mantienen privados.
📝
Salida con null safety
Las clases generadas usan null safety de Dart. Los campos JSON nulables reciben el sufijo de tipo ? y omiten la palabra clave required en los constructores. Los campos no nulos se marcan como required.
📦
Sin instalación ni registro
Abre la página y pega tu JSON. No se necesita el SDK de Dart, ni dependencias de pub, ni cuenta que crear. Funciona en cualquier dispositivo con un navegador.

Casos de uso de JSON a Dart

Desarrollo de apps Flutter
Genera clases modelo para respuestas de API REST consumidas por los paquetes http o dio. Pega el JSON que devuelve tu backend y obtén clases Dart listas para jsonDecode y la renderización de widgets.
Integración con Firebase / Firestore
Crea clases modelo tipadas para snapshots de documentos Firestore. Pega un documento de ejemplo como JSON, genera la clase Dart y usa fromJson con snapshot.data() en tu capa de repositorio.
Modelos de gestión de estado
Define objetos de estado tipados para Riverpod, Bloc o Provider. Pega la forma de estado esperada como JSON y obtén clases Dart inmutables con toJson para la persistencia e hidratación del estado.
Generación de código para clientes de API
Produce modelos de solicitud y respuesta para clientes de API como Retrofit o Chopper. Pega payloads JSON de ejemplo y obtén clases Dart que coincidan con tus esquemas de respuesta de OpenAPI.
Pruebas automatizadas
Construye fixtures tipadas a partir de respuestas reales de la API. Pega una muestra JSON, genera la clase modelo y úsala directamente en pruebas unitarias y de widgets sin escribir structs de fixture a mano.
Aprendizaje de patrones Dart
Los estudiantes que aprenden Flutter pueden pegar cualquier estructura JSON y ver cómo Dart la representa: campos final, constructores con nombre, patrones factory fromJson y anotaciones de null safety en la práctica.

Mapeo de tipos JSON a Dart

Cada valor JSON se mapea a un tipo Dart específico. La tabla a continuación muestra cómo el conversor traduce cada tipo JSON. La columna Alternativa muestra los tipos usados en escenarios menos comunes o de mapeo manual.

Tipo JSONEjemploTipo DartAlternativa
string"hello"StringString
number (integer)42intint
number (float)3.14doubledouble
booleantrueboolbool
nullnulldynamicNull / dynamic
object{"k": "v"}NestedClassMap<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>

Enfoques de serialización JSON en Dart

Dart y Flutter ofrecen múltiples formas de gestionar la serialización JSON. El enfoque manual con fromJson/toJson es el más sencillo y no requiere generación de código. Para proyectos más grandes, las soluciones basadas en build_runner como json_serializable y freezed generan el boilerplate en tiempo de compilación, reduciendo errores y manteniendo los modelos coherentes con el contrato JSON.

EnfoqueDescripciónFuente
json_serializableCode-generation-based JSON serialization. Generates .g.dart files at build time via build_runner. Type-safe and compile-time verified.Official (Google)
freezedGenerates immutable data classes with copyWith, fromJson/toJson, equality, and pattern matching support. Built on top of json_serializable.Community (rrousselGit)
built_valueGenerates immutable value types with serialization. Enforces immutability patterns. Used in larger codebases with strict data modeling.Google
dart_mappableAnnotation-based mapper that generates fromJson, toJson, copyWith, and equality. Simpler setup than freezed with similar features.Community
Manual fromJson/toJsonHand-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 tiene tres enfoques comunes para las clases modelo JSON. El fromJson/toJson manual no tiene dependencias. json_serializable automatiza el código de mapeo. freezed añade inmutabilidad, copyWith y pattern matching sobre json_serializable.

Manual fromJson/toJson
Escribe factories fromJson y métodos toJson a mano. Sin dependencias, sin paso de build_runner. Tienes control total sobre la lógica de cast y el manejo de errores. Ideal para proyectos pequeños con pocos modelos, o cuando necesitas deserialización personalizada que los generadores de código no pueden expresar. Este es el formato de salida que genera esta herramienta.
json_serializable
Anota tu clase con @JsonSerializable() y ejecuta dart run build_runner build. El generador crea un archivo .g.dart con las funciones _$ClassFromJson y _$ClassToJson. Gestiona @JsonKey para renombrar campos, valores por defecto y conversores personalizados. La opción estándar para proyectos Flutter de tamaño mediano o grande.
freezed
Anota con @freezed y obtén clases inmutables con fromJson, toJson, copyWith, igualdad (== y hashCode) y toString generados automáticamente. Admite tipos union para patrones de clases selladas. Requiere tanto freezed como json_serializable como dependencias de desarrollo. Ideal para modelos de gestión de estado y objetos de dominio complejos.

Ejemplos de código

Estos ejemplos muestran cómo usar las clases Dart generadas para deserialización JSON, cómo configurar json_serializable con build_runner y cómo generar clases Dart de forma programática desde JavaScript y Python.

Dart (manual fromJson / toJson)
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
}
Dart (json_serializable + build_runner)
// 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-outputs
JavaScript (generate Dart classes from JSON)
function 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;
// }
Python (generate Dart model from JSON)
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;
# }

Preguntas frecuentes

¿Por qué Flutter no puede usar dart:mirrors para deserializar JSON?
Flutter deshabilita dart:mirrors (la librería de reflexión) porque el tree-shaking requiere saber en tiempo de compilación qué código se usa. La reflexión impediría al compilador eliminar las clases no utilizadas, aumentando el tamaño de la app. Por eso los proyectos Flutter necesitan métodos fromJson/toJson explícitos o generación de código en lugar de reflexión en tiempo de ejecución.
¿Debo usar json_serializable o escribir fromJson a mano?
Para proyectos con menos de 10 clases modelo, el fromJson/toJson escrito a mano es directo y no añade dependencias de compilación. Una vez que tienes más de 10 modelos, o modelos que cambian con frecuencia, json_serializable ahorra tiempo y reduce errores de copiar y pegar. Genera el código de mapeo a partir de anotaciones, por lo que añadir campos solo requiere volver a ejecutar build_runner.
¿Cómo gestiona el conversor los objetos JSON anidados?
Cada objeto JSON anidado se convierte en una clase Dart separada. Si un campo JSON llamado "address" contiene un objeto con las claves "street" y "city", el conversor crea una clase Address con esos campos y tipifica el campo del padre como Address. La factory fromJson castea el mapa anidado y lo pasa a Address.fromJson.
¿Qué ocurre cuando un campo JSON es nulo?
Los campos nulos se tipifican como dynamic con un sufijo ? porque el conversor no puede determinar el tipo previsto a partir de un valor nulo por sí solo. Reemplaza dynamic? con el tipo correcto (String?, int?, etc.) una vez que lo conozcas a partir de tu contrato de API. El null safety de Dart aplicará entonces las comprobaciones de nulos correctas en tiempo de compilación.
¿Cómo gestiono las claves JSON que no son identificadores Dart válidos?
Las claves JSON como "first-name" o "2nd_place" no son nombres de campo Dart válidos. Con fromJson manual, puedes mapear cualquier clave JSON a cualquier nombre de campo Dart en el constructor factory. Con json_serializable, usa @JsonKey(name: 'first-name') para anotar el campo. El conversor convierte automáticamente los nombres de clave a campos Dart en camelCase.
¿Puedo usar freezed con la salida de esta herramienta?
La herramienta genera clases Dart estándar con fromJson/toJson manual. Para convertir a freezed, cambia la clase para usar la anotación @freezed, elimina el constructor y la factory escritos a mano y añade el mixin de freezed. Luego ejecuta build_runner para generar los archivos .freezed.dart y .g.dart. Los nombres de campo y los tipos de la salida generada se transfieren directamente.
¿Cuál es la diferencia entre un mapa en bruto y una clase Dart tipada para JSON?
Un mapa en bruto (Map de String a dynamic) te da acceso sin procesar a los datos JSON sin comprobación de tipos. Debes castear cada valor en el punto de uso, y los errores tipográficos en los nombres de clave fallan silenciosamente en tiempo de ejecución. Una clase Dart tipada captura los errores de tipo en la frontera de fromJson, proporciona autocompletado del IDE para los nombres de campo y hace que la refactorización sea segura. La diferencia de rendimiento es insignificante; el beneficio es enteramente en corrección y mantenibilidad.