JSON to Dart

Генерация Dart-классов из JSON с fromJson и toJson

Попробовать пример
Имя корневого класса:

Ввод JSON

Вывод Dart

Работает локально · Безопасно вставлять секреты
Dart-классы появятся здесь…

Что такое конвертация JSON в Dart?

Конвертация JSON в Dart берёт исходный JSON-объект и создаёт определения Dart-классов с типизированными полями, именованным конструктором, фабричным конструктором fromJson и методом toJson. В Flutter отсутствует рефлексия во время выполнения (dart:mirrors отключён), поэтому десериализовать JSON в типизированные объекты без явного кода отображения невозможно. Каждый ответ REST API, документ Firebase или конфигурационная полезная нагрузка требует соответствующего класса модели Dart, прежде чем можно будет обращаться к полям с контролем типов.

Типичный Dart-класс модели для JSON объявляет финальные поля для каждого ключа, конструктор с именованными параметрами (с ключевым словом required для ненулевых полей), фабричный конструктор fromJson, считывающий данные из Map<String, dynamic>, и метод toJson, возвращающий Map<String, dynamic>. Вложенные JSON-объекты становятся отдельными классами. Массивы превращаются в типизированные поля List. Nullable-значения JSON используют синтаксис null-безопасности Dart с суффиксом ? после типа.

Ручное написание таких классов означает: прочитать каждый ключ JSON, определить тип Dart, создать приведение в fromJson для каждого поля (включая маппинг списков через .map().toList()), построить литерал карты в toJson и повторить это для каждого вложенного объекта. Для JSON-объекта с 12 полями и 2 вложенными объектами это 3 класса, 6 строк фабричного конструктора и десятки выражений приведения типов. Конвертер создаёт всё это за миллисекунды из одной вставки.

Зачем использовать конвертер JSON в Dart?

Ручное написание Dart-классов моделей из JSON требует чтения имён полей, угадывания типов по примерным значениям, написания приведений в fromJson с правильной обработкой null и повторения этого процесса для вложенных объектов. При изменении структуры API каждое обновление поля затрагивает конструктор, fromJson и toJson. Конвертер устраняет эту повторяющуюся работу.

Мгновенная генерация классов
Вставьте JSON — и получите полноценные Dart-классы с конструкторами, фабриками fromJson и методами toJson менее чем за секунду. Вложенные объекты и списки обнаруживаются и отображаются автоматически.
🔒
Обработка с приоритетом конфиденциальности
Конвертация выполняется полностью в браузере с помощью JavaScript. Ваши JSON-данные никогда не покидают ваш компьютер. Ключи API, токены и промышленные данные остаются приватными.
📝
Вывод с поддержкой null-безопасности
Сгенерированные классы используют строгую null-безопасность Dart. Nullable-поля JSON получают суффикс ? и пропускают ключевое слово required в конструкторах. Ненулевые поля помечаются как required.
📦
Без установки и регистрации
Откройте страницу и вставьте JSON. Не нужны Dart SDK, pub-зависимости или аккаунт. Работает на любом устройстве с браузером.

Сценарии использования JSON to Dart

Разработка Flutter-приложений
Генерируйте классы моделей для ответов REST API, потребляемых пакетами http или dio. Вставьте JSON, который возвращает ваш бэкенд, и получите Dart-классы, готовые для jsonDecode и рендеринга виджетов.
Интеграция с Firebase / Firestore
Создавайте типизированные классы моделей для снимков документов Firestore. Вставьте пример документа в виде JSON, сгенерируйте Dart-класс и используйте fromJson с snapshot.data() в слое репозитория.
Модели управления состоянием
Определяйте типизированные объекты состояния для Riverpod, Bloc или Provider. Вставьте ожидаемую форму состояния в виде JSON и получите неизменяемые Dart-классы с toJson для сохранения и восстановления состояния.
Генерация кода API-клиента
Создавайте модели запросов и ответов для API-клиентов Retrofit или Chopper. Вставьте примеры JSON-данных и получите Dart-классы, соответствующие схемам ответов OpenAPI.
Автоматизированное тестирование
Создавайте типизированные тестовые фикстуры из реальных ответов API. Вставьте JSON-образец, сгенерируйте класс модели и используйте его непосредственно в модульных и виджет-тестах без ручного написания структур фикстур.
Изучение паттернов Dart
Студенты, изучающие Flutter, могут вставить любую JSON-структуру и увидеть, как Dart её представляет: финальные поля, именованные конструкторы, паттерны фабричного fromJson и аннотации null-безопасности на практике.

Таблица соответствия типов JSON и Dart

Каждое значение JSON соответствует определённому типу Dart. В таблице ниже показано, как конвертер транслирует каждый тип JSON. В столбце «Альтернатива» указаны типы, используемые в менее распространённых или ручных сценариях отображения.

Тип JSONПримерТип DartАльтернатива
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>

Подходы к сериализации JSON в Dart

Dart и Flutter предлагают несколько способов обработки сериализации JSON. Ручной fromJson/toJson — самый простой подход, не требующий генерации кода. Для крупных проектов решения на основе build_runner — json_serializable и freezed — генерируют шаблонный код во время компиляции, снижая количество ошибок и сохраняя согласованность моделей с JSON-контрактом.

ПодходОписаниеИсточник
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 существует три распространённых подхода к классам модели JSON. Ручной fromJson/toJson не имеет зависимостей. json_serializable автоматизирует код отображения. freezed добавляет неизменяемость, copyWith и сопоставление с образцом поверх json_serializable.

Manual fromJson/toJson
Пишите фабрики fromJson и методы toJson вручную. Никаких зависимостей, никакого шага build_runner. Вы полностью контролируете логику приведения типов и обработку ошибок. Лучший выбор для небольших проектов с несколькими моделями или когда требуется нестандартная десериализация, которую генераторы кода не могут выразить. Именно этот формат вывода производит данный инструмент.
json_serializable
Аннотируйте класс с @JsonSerializable() и запустите dart run build_runner build. Генератор создаёт файл .g.dart с функциями _$ClassFromJson и _$ClassToJson. Поддерживает @JsonKey для переименования полей, значений по умолчанию и пользовательских конвертеров. Стандартный выбор для средних и крупных Flutter-проектов.
freezed
Аннотируйте с @freezed и получите неизменяемые классы с fromJson, toJson, copyWith, равенством (== и hashCode) и toString, сгенерированными автоматически. Поддерживает union-типы для паттернов sealed-классов. Требует и freezed, и json_serializable в качестве dev-зависимостей. Лучший выбор для моделей управления состоянием и сложных объектов предметной области.

Примеры кода

Эти примеры показывают, как использовать сгенерированные Dart-классы для десериализации JSON, как настроить json_serializable с build_runner и как программно генерировать Dart-классы из JavaScript и 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;
# }

Часто задаваемые вопросы

Почему Flutter не может использовать dart:mirrors для десериализации JSON?
Flutter отключает dart:mirrors (библиотеку рефлексии), потому что tree-shaking требует знать во время компиляции, какой код используется. Рефлексия не позволила бы компилятору удалять неиспользуемые классы, увеличивая размер приложения. Именно поэтому Flutter-проекты нуждаются в явных методах fromJson/toJson или в генерации кода вместо рефлексии во время выполнения.
Использовать json_serializable или писать fromJson вручную?
Для проектов с менее чем 10 классами моделей ручной fromJson/toJson прост и не добавляет зависимостей сборки. Когда моделей становится больше 10 или они часто меняются, json_serializable экономит время и снижает количество ошибок копирования. Он генерирует код отображения из аннотаций, поэтому добавление поля требует лишь повторного запуска build_runner.
Как конвертер обрабатывает вложенные JSON-объекты?
Каждый вложенный JSON-объект становится отдельным Dart-классом. Если поле JSON с именем "address" содержит объект с ключами "street" и "city", конвертер создаёт класс Address с этими полями и типизирует родительское поле как Address. Фабрика fromJson приводит вложенную карту и передаёт её в Address.fromJson.
Что происходит, когда поле JSON равно null?
Null-поля типизируются как dynamic с суффиксом ?, поскольку конвертер не может определить предполагаемый тип только по значению null. Замените dynamic? на правильный тип (String?, int? и т. д.), как только узнаете его из контракта API. Null-безопасность Dart тогда обеспечит корректные проверки на null во время компиляции.
Как обрабатывать ключи JSON, которые не являются допустимыми идентификаторами Dart?
Ключи JSON вроде "first-name" или "2nd_place" не являются допустимыми именами полей Dart. При ручном fromJson вы можете сопоставить любой ключ JSON с любым именем поля Dart в фабричном конструкторе. С json_serializable используйте @JsonKey(name: 'first-name') для аннотирования поля. Конвертер автоматически преобразует имена ключей в поля Dart в стиле camelCase.
Можно ли использовать freezed с выводом этого инструмента?
Инструмент генерирует стандартные Dart-классы с ручным fromJson/toJson. Чтобы перейти на freezed, измените класс, добавив аннотацию @freezed, удалите написанный вручную конструктор и фабрику и добавьте миксин freezed. Затем запустите build_runner для генерации файлов .freezed.dart и .g.dart. Имена и типы полей из сгенерированного вывода переносятся напрямую.
В чём разница между сырой картой и типизированным Dart-классом для JSON?
Сырая карта (Map<String, dynamic>) даёт прямой доступ к данным JSON без контроля типов. Вы должны приводить каждое значение в точке вызова, а опечатки в именах ключей незаметно дают сбой во время выполнения. Типизированный Dart-класс перехватывает несоответствия типов на границе fromJson, обеспечивает автодополнение IDE для имён полей и делает рефакторинг безопасным. Разница в производительности незначительна; выгода целиком заключается в корректности и удобстве сопровождения.