JSON to Dart

Генерація класів Dart з JSON з методами fromJson та toJson

Спробувати приклад
Назва кореневого класу:

Введення JSON

Виведення Dart

Працює локально · Безпечно вставляти секрети
Класи Dart з'являться тут…

Що таке конвертація JSON у Dart?

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

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

Написання таких класів моделей вручну означає читання кожного JSON-ключа, визначення типу Dart, створення приведення типів у fromJson для кожного поля (включно з відображенням списків через .map().toList()), формування літерала map у toJson та повторення цього для кожного вкладеного об'єкта. Для JSON-об'єкта з 12 полями та 2 вкладеними об'єктами це означає 3 класи, 6 рядків фабрики та десятки виразів приведення типів. Конвертер генерує все це за мілісекунди з одного вставленого фрагмента.

Навіщо використовувати конвертер JSON to Dart?

Ручне написання класів моделей Dart з JSON передбачає читання назв полів, визначення типів за зразками значень, написання приведень типів у fromJson з коректною обробкою null та повторення цього для вкладених об'єктів. Коли форма API змінюється, кожне оновлення поля стосується конструктора, fromJson і toJson. Конвертер усуває цю рутинну роботу.

Миттєва генерація класів
Вставте JSON і отримайте повні класи Dart з конструкторами, фабриками fromJson та методами toJson менш ніж за секунду. Вкладені об'єкти та списки виявляються й відображаються автоматично.
🔒
Конфіденційність даних
Конвертація виконується повністю у вашому браузері засобами JavaScript. Ваші JSON-дані ніколи не залишають ваш пристрій. API-ключі, токени та виробничі пейлоади залишаються приватними.
📝
Вихідні дані з null-safety
Згенеровані класи використовують повну null-safety Dart. Nullable JSON-поля отримують суфікс ? у типі та не потребують ключового слова required у конструкторах. Non-null поля позначаються як 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-safety на практиці.

Таблиця відповідності типів 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 class. Потребує і freezed, і json_serializable як залежності розробки. Найкраще для моделей управління станом та складних доменних об'єктів.

Приклади коду

Ці приклади показують, як використовувати згенеровані класи 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 приводить вкладений map і передає його до Address.fromJson.
Що відбувається, коли JSON-поле має значення null?
Null-поля типізуються як dynamic з суфіксом ?, оскільки конвертер не може визначити призначений тип лише за значенням null. Замініть dynamic? на правильний тип (String?, int? тощо), щойно дізнаєтеся це з вашого API-контракту. Null-safety 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, видаліть написаний вручну конструктор та фабрику і додайте mixin freezed. Потім запустіть build_runner для генерації файлів .freezed.dart та .g.dart. Назви полів і типи зі згенерованого виводу переносяться безпосередньо.
Яка різниця між сирим map та типізованим класом Dart для JSON?
Сирий map (Map<String, dynamic>) надає прямий доступ до JSON-даних без перевірки типів. Ви повинні приводити кожне значення в місці використання, а друкарські помилки в назвах ключів дають збій мовчки під час виконання. Типізований клас Dart виявляє невідповідності типів на межі fromJson, надає автодоповнення IDE для назв полів і робить рефакторинг безпечним. Різниця у продуктивності є незначною; перевага полягає виключно у коректності та зручності підтримки.