JSON转Dart

从JSON生成带fromJson和toJson的Dart类

加载示例
根类名称:

JSON输入

Dart输出

本地运行 · 粘贴密钥安全无忧
Dart类将显示在这里…

什么是JSON转Dart转换?

JSON转Dart转换将原始JSON对象转换为带有类型化字段、命名构造函数、fromJson工厂方法和toJson方法的Dart类定义。Flutter中的Dart不支持运行时反射(dart:mirrors已被禁用),因此若不编写显式映射代码,就无法将JSON反序列化为类型化对象。每一个REST API响应、Firebase文档或配置数据,在能够以类型安全的方式访问其字段之前,都需要一个对应的Dart模型类。

典型的Dart JSON模型类会为每个键声明final字段,构造函数使用命名参数(非可空字段使用required关键字),工厂构造函数fromJson从Map<String, dynamic>中读取数据,toJson方法返回Map<String, dynamic>。嵌套的JSON对象会成为独立的类,数组会成为类型化的List字段,可空的JSON值在类型后使用Dart的空安全语法添加?后缀。

手动编写这些模型类意味着需要逐一读取每个JSON键、确定Dart类型、为每个字段编写fromJson类型转换(包括使用.map().toList()的列表映射)、构建toJson映射字面量,并对每个嵌套对象重复上述过程。一个包含12个字段和2个嵌套对象的JSON对象需要3个类、6行工厂方法和数十个类型转换表达式。转换工具只需一次粘贴,即可在毫秒内生成所有代码。

为什么使用JSON转Dart转换工具?

手动编写Dart模型类需要读取字段名、根据示例值推断类型、编写带有正确空值处理的fromJson转换,并对嵌套对象重复此过程。当API结构发生变化时,每次字段更新都需要同时修改构造函数、fromJson和toJson。转换工具可以消除这些重复性工作。

即时生成类
粘贴JSON,不到一秒即可获得完整的Dart类,包含构造函数、fromJson工厂方法和toJson方法。嵌套对象和列表会被自动检测并进行映射。
🔒
隐私优先处理
转换完全在浏览器中通过JavaScript运行,您的JSON数据不会离开本机。API密钥、令牌和生产环境数据均保持私密。
📝
空安全输出
生成的类遵循Dart的健全空安全规范。可空的JSON字段会添加?类型后缀,并在构造函数中省略required关键字;非空字段则标记为required。
📦
无需安装或注册
打开页面,粘贴JSON即可使用。无需Dart SDK、无需pub依赖项、无需账户,在任何有浏览器的设备上均可使用。

JSON转Dart使用场景

Flutter应用开发
为http或dio包处理的REST API响应生成模型类。粘贴后端返回的JSON,即可获得可直接用于jsonDecode和Widget渲染的Dart类。
Firebase / Firestore集成
为Firestore文档快照创建类型化模型类。将示例文档粘贴为JSON,生成Dart类,然后在数据仓库层中将fromJson与snapshot.data()配合使用。
状态管理模型
为Riverpod、Bloc或Provider定义类型化状态对象。将预期的状态结构粘贴为JSON,获得不可变的Dart类,并通过toJson实现状态持久化和恢复。
API客户端代码生成
为Retrofit或Chopper API客户端生成请求和响应模型。粘贴JSON示例数据,获得与OpenAPI响应模式匹配的Dart类。
自动化测试
从真实API响应中构建类型化测试夹具。粘贴JSON示例,生成模型类,然后直接用于单元测试和Widget测试,无需手动编写夹具结构。
学习Dart模式
正在学习Flutter的开发者可以粘贴任意JSON结构,查看Dart如何表示它:final字段、命名构造函数、fromJson工厂模式,以及空安全注解的实际应用。

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>

Dart JSON序列化方案

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

手动方式 vs json_serializable vs freezed

Dart有三种常见的JSON模型类处理方案。手动fromJson/toJson无需任何依赖;json_serializable自动化映射代码;freezed在json_serializable的基础上增加了不可变性、copyWith和模式匹配。

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的不可变类。支持用于密封类模式的联合类型。需要将freezed和json_serializable同时作为开发依赖项。最适合状态管理模型和复杂领域对象。

代码示例

以下示例展示如何使用生成的Dart类进行JSON反序列化、如何配合build_runner使用json_serializable,以及如何通过JavaScript和Python以编程方式生成Dart类。

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类。如果名为"address"的JSON字段包含一个具有"street"和"city"键的对象,转换工具会创建一个Address类并包含这些字段,同时将父级字段的类型设为Address。fromJson工厂方法会对嵌套的map进行类型转换,并将其传递给Address.fromJson。
JSON字段为null时会怎样?
由于转换工具无法仅从null值推断出预期类型,空字段会被类型化为带?后缀的dynamic。在根据API契约确认正确类型后,请将dynamic?替换为正确的类型(如String?、int?等)。届时Dart的空安全机制将在编译时强制执行正确的空值检查。
如何处理不是有效Dart标识符的JSON键?
"first-name"或"2nd_place"这类JSON键不是合法的Dart字段名。使用手动fromJson时,可以在工厂构造函数中将任意JSON键映射到任意Dart字段名。使用json_serializable时,可以通过@JsonKey(name: 'first-name')注解字段。转换工具会自动将键名转换为驼峰式命名的Dart字段。
可以将本工具的输出与freezed配合使用吗?
本工具生成的是带有手动fromJson/toJson的标准Dart类。若要转换为freezed风格,需要将类改为使用@freezed注解,移除手动编写的构造函数和工厂方法,并添加freezed mixin。然后运行build_runner生成.freezed.dart和.g.dart文件。生成输出中的字段名和类型可以直接沿用。
原始map和类型化Dart类处理JSON有什么区别?
原始map(Map<String, dynamic>)提供对JSON数据的直接访问,但没有类型检查。每次访问值都必须在调用处进行类型转换,键名拼写错误会在运行时静默失败。类型化Dart类会在fromJson边界捕获类型不匹配问题,为字段名提供IDE自动补全,并使重构更加安全。性能差异可以忽略不计,其优势完全体现在正确性和可维护性上。