Example
[
{
"id": 1,
"title": "Write docs",
"done": false,
"priority": 2.5,
"tags": [
"docs"
],
"owner": {
"id": 7,
"name": "Ada"
}
},
{
"id": 2,
"title": "Ship it",
"done": true,
"priority": 1,
"tags": [],
"due_date": "2026-11-01"
}
]
// The JSON root is an array. Decode it with:
// (jsonDecode(text) as List).map((e) => TaskItem.fromJson(e as Map<String, dynamic>)).toList()
class TaskItem {
final int id;
final String title;
final bool done;
final double priority;
final List<String> tags;
final Owner? owner;
final String? dueDate;
const TaskItem({
required this.id,
required this.title,
required this.done,
required this.priority,
required this.tags,
this.owner,
this.dueDate,
});
factory TaskItem.fromJson(Map<String, dynamic> json) => TaskItem(
id: json['id'] as int,
title: json['title'] as String,
done: json['done'] as bool,
priority: (json['priority'] as num).toDouble(),
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
owner: json['owner'] == null ? null : Owner.fromJson(json['owner'] as Map<String, dynamic>),
dueDate: json['due_date'] as String?,
);
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'done': done,
'priority': priority,
'tags': tags,
'owner': owner?.toJson(),
'due_date': dueDate,
};
}
class Owner {
final int id;
final String name;
const Owner({required this.id, required this.name});
factory Owner.fromJson(Map<String, dynamic> json) => Owner(
id: json['id'] as int,
name: json['name'] as String,
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
};
}
due_dateandowneronly appear in one task, so they are nullable and notrequired.priorityhas a decimal in one sample, so it is adoubleread throughnum.- Nested lists of objects are mapped with
.map(...).toList()in both directions.
Using the classes
import 'dart:convert';
import 'package:http/http.dart' as http;
final res = await http.get(Uri.parse('https://api.example.com/tasks'));
final tasks = (jsonDecode(res.body) as List)
.map((e) => TaskItem.fromJson(e as Map<String, dynamic>))
.toList();
final body = jsonEncode(tasks.first.toJson());
With json_serializable
Pick json_serializable to get annotated classes with a part directive. Then add the
packages and run the generator:
# pubspec.yaml: json_annotation under dependencies,
# json_serializable and build_runner under dev_dependencies
dart pub add json_annotation dev:json_serializable dev:build_runner
# Generate the *.g.dart files (add --delete-conflicting-outputs after renames)
dart run build_runner build
Building the same API client for iOS? Generate Swift Codable structs, or Kotlin data classes for native Android.
Frequently asked questions
How do I convert JSON to a Dart class?
Paste a JSON sample into the tool above. It writes a null-safe Dart class for every object, with final fields, a const constructor, a fromJson factory and a toJson method. Keys become camelCase field names; the original keys are kept in fromJson and toJson.
Should I use json_serializable or hand-written fromJson?
Hand-written fromJson and toJson (the default) need no packages or code generation, so you can paste the result and run it. json_serializable generates the same code from annotations with build_runner, which is easier to maintain in large apps. Switch the Dart style to get annotated classes.
Why does fromJson use (json[...] as num).toDouble()?
JSON has only one number type, so a field that is usually 2.5 can arrive as 2. jsonDecode then returns an int, and a direct as double cast throws. Reading it as num and calling toDouble() works for both.
Which fields are nullable?
Fields that are null in a sample, or missing from some objects when you paste an array, get a nullable type (String?) and are not required in the constructor. Fields present in every sample are required.