Working with JSON
Prerequisites: Constructors, Maps
JSON is the most common format for sending data between programs.
Dart's built-in dart:convert library converts between JSON text and
Dart maps and lists.
Encoding and decoding
import 'dart:convert';
void main() {
final user = {'name': 'Ada', 'age': 36, 'tags': ['admin'], 'manager': null};
final text = jsonEncode(user);
print(text);
final decoded = jsonDecode('{"name": "Linus", "age": 28}') as Map<String, dynamic>;
print(decoded['name']);
print(decoded['age'] + 1);
}
{"name":"Ada","age":36,"tags":["admin"],"manager":null}
Linus
29
jsonDecode returns dynamic data — maps, lists, strings, numbers,
booleans, and null. Working with raw maps loses type safety, so real
apps convert them into classes.
fromJson and toJson
import 'dart:convert';
class User {
final String name;
final int age;
final String? email;
User({required this.name, required this.age, this.email});
factory User.fromJson(Map<String, dynamic> json) => User(
name: json['name'] as String,
age: json['age'] as int,
email: json['email'] as String?,
);
Map<String, dynamic> toJson() => {
'name': name,
'age': age,
if (email != null) 'email': email,
};
}
void main() {
final user = User.fromJson(jsonDecode('{"name":"Ada","age":36}'));
print('${user.name} is ${user.age}, email: ${user.email ?? 'none'}');
print(jsonEncode(user.toJson()));
final list = (jsonDecode('[{"name":"A","age":1},{"name":"B","age":2}]') as List)
.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList();
print(list.length);
}
Ada is 36, email: none
{"name":"Ada","age":36}
2
jsonEncode calls toJson() automatically if you pass the object
itself: jsonEncode(user) gives the same result.
Pattern matching for validation
Patterns (from the earlier lesson) check the shape and types in one go:
import 'dart:convert';
void main() {
final data = jsonDecode('{"name": "Ada", "age": "thirty-six"}');
if (data case {'name': String name, 'age': int age}) {
print('$name is $age');
} else {
print('Invalid user data');
}
}
Invalid user data
Code generation
For larger apps, packages such as json_serializable or freezed
generate fromJson/toJson code from annotations, removing the
hand-written mapping. Paste a sample API response into the
JSON Formatter to see its structure before
writing the matching classes.
Common mistakes
- Casting too trustingly:
json['age'] as intthrows if the API sends"36"as a string. Validate data from outside your app. - Numbers: JSON has one number type; a value like
3decodes as anint, sojson['price'] as doublecan fail. Use(json['price'] as num).toDouble().