Parse JSON in Dart / Flutter
Dart's built-in dart:convert library provides jsonDecode() for parsing JSON strings and jsonEncode() for serializing. For type-safe models, you define fromJson() factory constructors or use the json_serializable package to generate boilerplate automatically. No third-party parser is needed for basic Flutter JSON work — everything you need ships with the SDK.
Validate your JSON before parsing it in Dart or Flutter.
Open JSON FormatterBasic parsing with jsonDecode()
Import dart:convert and call jsonDecode() with any valid JSON string. The function returns dynamic, which is a Map<String, dynamic> for JSON objects and List<dynamic> for JSON arrays. You cast values to the expected Dart types when accessing them.
import 'dart:convert';
void main() {
final jsonStr = '{"id": 42, "name": "Alice", "active": true}';
final Map<String, dynamic> data = jsonDecode(jsonStr);
print(data['name']); // Alice
print(data['id']); // 42
print(data['active']); // true
}Because jsonDecode returns dynamic, Dart won't catch type mismatches until runtime. Always cast to a concrete type immediately — either with as int for a hard cast (throws if wrong) or with a null-safe pattern like (data['id'] as num?)?.toInt() ?? 0 for resilience.
Parse a JSON array
When the top-level JSON value is an array, jsonDecode returns List<dynamic>. Iterate over it and cast each element to Map<String, dynamic> before accessing fields.
import 'dart:convert';
void main() {
final jsonStr = '[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]';
final List<dynamic> list = jsonDecode(jsonStr);
for (final item in list) {
print(item['name']); // Alice, then Bob
}
}Type-safe model classes with fromJson()
The standard Flutter pattern is to define a model class with a fromJson() factory constructor and a toJson() method. The factory casts each field explicitly from the Map<String, dynamic>, giving you compile-time field names and runtime type checking. Use nullable types (String?, int?) for JSON fields that may be absent or null.
import 'dart:convert';
class User {
final int id;
final String name;
final String? email; // nullable — might be absent in JSON
User({required this.id, required this.name, this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String?,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
if (email != null) 'email': email, // omit null fields from output
};
}
// Usage:
void main() {
final jsonStr = '{"id": 1, "name": "Alice", "email": "alice@example.com"}';
final user = User.fromJson(jsonDecode(jsonStr));
print(user.name); // Alice
print(user.email); // alice@example.com
// Serialize back to JSON string
print(jsonEncode(user.toJson()));
// {"id":1,"name":"Alice","email":"alice@example.com"}
}Parse a list of model objects
Combine the list parsing pattern with your fromJson() factory to convert a JSON array into a typed List<User> in one expression. The cast to List before .map() is necessary because jsonDecode returns dynamic.
import 'dart:convert';
// (User class defined above with fromJson factory)
void main() {
final jsonStr = '[{"id":1,"name":"Alice"},{"id":2,"name":"Bob","email":"bob@example.com"}]';
final List<User> users = (jsonDecode(jsonStr) as List)
.map((item) => User.fromJson(item as Map<String, dynamic>))
.toList();
print(users.length); // 2
print(users[0].name); // Alice
print(users[1].email); // bob@example.com
}Fetch and parse JSON from an HTTP API
Add the http package to your pubspec.yaml (http: ^1.2.0), then use it alongside dart:convert to fetch and decode API responses. Always check response.statusCode before parsing — a non-200 response body is often an error message, not valid JSON.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<User> fetchUser(int id) async {
final response = await http.get(
Uri.parse('https://api.example.com/users/$id'),
);
if (response.statusCode == 200) {
return User.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load user: ${response.statusCode}');
}
}
// Use in a Flutter widget:
// final user = await fetchUser(42);Never call jsonDecode on the main isolate for large payloads — it can cause frame drops. Use Flutter's compute() function to offload parsing to a background isolate: final data = await compute(jsonDecode, response.body);
json_serializable: code generation for large projects
The json_serializable package reads annotations on your model classes and generates the fromJson and toJson boilerplate automatically. This eliminates manual casting errors and is the recommended approach for projects with many models or complex types like DateTime, enums, or nested lists.
Add the dependencies to pubspec.yaml:
dependencies:
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.0
json_serializable: ^6.8.0Annotate your model class with @JsonSerializable() and add the part directive. Use @JsonKey(name: ...) to rename fields — for example, mapping a snake_case API field to a camelCase Dart property.
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart'; // generated file — do not edit by hand
@JsonSerializable()
class User {
final int id;
final String name;
@JsonKey(name: 'created_at') // maps JSON "created_at" → Dart createdAt
final DateTime createdAt;
User({required this.id, required this.name, required this.createdAt});
// Delegate to the generated _$UserFromJson function
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
// Delegate to the generated _$UserToJson function
Map<String, dynamic> toJson() => _$UserToJson(this);
}Run the code generator once (or in watch mode during development) to produce the user.g.dart file:
# One-time generation
dart run build_runner build
# Watch mode (re-generates on file save)
dart run build_runner watchAfter generation, User.fromJson() and user.toJson() work exactly like a manually written implementation — but with zero manual casting code. Re-run the generator whenever you add or rename fields.
Validate your JSON first
Paste your API response into Jsonic's JSON Formatter to catch syntax errors and inspect the structure before writing your Dart model classes.
Open JSON FormatterFrequently asked questions
What function parses JSON in Dart?
Use jsonDecode() from dart:convert (import 'dart:convert'). It accepts a JSON string and returns a dynamic value: a Map<String, dynamic> for JSON objects, List<dynamic> for arrays, and primitive Dart types for strings, numbers, booleans, and null. For type safety, cast the result immediately: final data = jsonDecode(str) as Map<String, dynamic>. No external packages are needed for basic parsing.
How do I make JSON parsing type-safe in Flutter?
Define a model class with a fromJson() factory constructor and a toJson() method. The factory casts each field from the Map<String, dynamic>: id: json['id'] as int. For optional fields use nullable types with as String?. For large projects, the json_serializable package auto-generates fromJson/toJson from annotations, eliminating manual casting errors.
What does jsonDecode return for a JSON array?
jsonDecode returns List<dynamic> for a JSON array. Each element is also dynamic, so you must cast items before use. The typical Flutter pattern is: (jsonDecode(str) as List).map((e) => User.fromJson(e as Map<String, dynamic>)).toList(). This returns a properly typed List<User>.
How do I handle nullable JSON fields in Dart?
Use nullable types (String?, int?, bool?) in your model class. In fromJson(), cast with json['field'] as String? — if the key is absent or the value is null, the result is null. To handle entirely missing keys and provide a fallback, use the null-coalescing operator: json['field'] as String? ?? 'default'.
How do I parse JSON in a Flutter background isolate?
Use Flutter's compute() function to run jsonDecode on a background isolate and avoid blocking the UI thread: final data = await compute(jsonDecode, jsonString). For Dart's Isolate API directly, pass the raw string to the isolate and call jsonDecode inside it. Code generated by json_serializable is isolate-safe and can be used inside compute() callbacks.
What is the difference between json_serializable and manual fromJson in Flutter?
Manual fromJson() gives you full control and zero dependencies but requires writing repetitive casting code for every field. json_serializable generates that boilerplate from annotations, reducing errors and maintenance burden. Use json_serializable for projects with many models or complex types like DateTime or nested lists. Use manual fromJson for small projects or when you need custom parsing logic that annotations cannot express.