Intermediate · Networking, storage and Firebase · topic 32 of 50 · 30 min
JSON models
Explanation
Why model classes?
jsonDecode gives you maps and lists of dynamic values. Writing post['title'] everywhere is fragile: a typo in a key or a wrong type only fails at run time, maybe on a user's phone. A model class turns JSON into a typed Dart object once, at the edge of your app. After that, the compiler checks every use of order.total, and editors can autocomplete fields.
A hand-written model
{"id": 9081, "item": "Paneer tikka", "qty": 2, "price_paise": 32000, "coupon": null}class Order {
const Order({required this.id, required this.item, required this.qty, required this.pricePaise, this.coupon});
final int id;
final String item;
final int qty;
final int pricePaise;
final String? coupon;
factory Order.fromJson(Map<String, dynamic> json) => Order(
id: json['id'] as int,
item: json['item'] as String,
qty: (json['qty'] as int?) ?? 1, // default if missing
pricePaise: json['price_paise'] as int,
coupon: json['coupon'] as String?, // nullable field
);
Map<String, dynamic> toJson() => {
'id': id,
'item': item,
'qty': qty,
'price_paise': pricePaise,
'coupon': coupon,
};
int get totalPaise => qty * pricePaise;
}final list = (jsonDecode(response.body) as List<dynamic>)
.map((e) => Order.fromJson(e as Map<String, dynamic>))
.toList();Notice the naming: the API uses price_paise (snake_case) while Dart uses pricePaise (camelCase). The fromJson and toJson methods are the single place where the two styles meet, so the rest of your code never sees the server's naming.
Handling bad data
Servers change, and bugs happen. A field might be missing, null, or a string instead of a number. With as int, a wrong type throws a TypeError at the moment of parsing, which is good: it happens in one predictable place. Catch errors around parsing in your repository and turn them into a clear app error ("Could not read orders"). Use nullable types and defaults only for fields that are truly optional.
Write a small unit test for each fromJson with a real sample response. It catches mistakes cheaply.
Generating code with json_serializable
Writing fromJson and toJson for twenty models is boring and error-prone. json_serializable generates them from annotations. build_runner is the tool that runs code generators.
flutter pub add json_annotation
flutter pub add --dev build_runner json_serializableimport 'package:json_annotation/json_annotation.dart';
part 'order.g.dart'; // the generated file
@JsonSerializable(fieldRename: FieldRename.snake)
class Order {
Order({required this.id, required this.item, this.qty = 1, required this.pricePaise, this.coupon});
final int id;
final String item;
final int qty;
final int pricePaise;
final String? coupon;
factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
Map<String, dynamic> toJson() => _$OrderToJson(this);
}dart run build_runner build --delete-conflicting-outputs
dart run build_runner watch # regenerate on every saveOther options exist, such as freezed (immutable classes with copyWith and unions) built on the same generator system. Start with hand-written models to understand them, then use generation when the number of models grows.
Common mistakes
- Using Map<String, dynamic> throughout the UI instead of models.
- Making every field nullable to avoid crashes: then every widget must handle null.
- Forgetting part 'order.g.dart' or not running build_runner: "Undefined name _$OrderFromJson".
- Using double for money from APIs. Prefer integer paise or a decimal type agreed with the backend.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
JSON models · 1/6
Convert JSON into model classes once, at the edge of the app. Do not pass raw maps around.
Prefer a video by a teacher? Find free videos on “flutter json serialization fromjson tojson json_serializable build_runner” on YouTube ↗
In real life: Train search results
A travel app receives train search results as JSON with fields like train_no, name, departs and available_seats, where the fare may be missing for waitlisted classes. A Train model with fromJson converts each result, with a nullable fare. The UI shows "Fare on request" when fare is null, and a parsing error shows "Could not load results" instead of crashing.
class Train {
const Train(this.number, this.name, this.fare);
final int number;
final String name;
final int? fare;
factory Train.fromJson(Map<String, dynamic> j) =>
Train(j['train_no'] as int, j['name'] as String, j['fare'] as int?);
}
String fareText(Train t) => t.fare == null ? 'Fare on request' : 'Rs ${t.fare}';Practice (0/3 done)
JSON is {"name": "Asha"} and your fromJson does json['age'] as int. What happens?
You see "Undefined name _$OrderFromJson". What is missing?
Write a Product model by hand for this JSON: {"id": 7, "name": "Kurta", "price_paise": 89900, "sizes": ["S", "M", "L"], "discount": null}. Include fromJson, toJson and a priceText getter. Parse a JSON array of three products and print each name and price. Then generate the same model with json_serializable.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Local storageNetworking, storage and Firebase · same module
- Firebase in FlutterNetworking, storage and Firebase · same module
- JSON models and handling errorsKotlin and Android App Development · Saving data and calling APIs
- JSON: data in and outJavaScript Programming (language in depth) · Modules, errors, JSON and dates
- Handling errors wellBackend Development · APIs and request handling
- Interview questions: DartFlutter careers and interviews
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Why convert JSON into model classes?
Answer: So the compiler checks field names and types everywhere else
Type safety after one parsing step.
Which tool runs code generators like json_serializable?
Answer: build_runner
dart run build_runner build.
Where should json_serializable and build_runner be listed?
Answer: dev_dependencies
They are only needed while developing.
What does toJson do?
Answer: Turns a model into a Map ready for jsonEncode
Used when sending data.
Why do Flutter widgets often use const constructors?Related
Answer: Constant widgets are created once and can be skipped during rebuilds
const saves work at run time.
How do you make a JSON field optional in the model?Related
Answer: Give it a nullable type and/or a default value
Defaults and nullability cover missing fields.
JSON.parse("{'x': 1}") throws which error?Related
Answer: SyntaxError
Single quotes are not valid JSON, so parsing fails with SyntaxError.
What happens if a handler throws and nothing catches it?Related
Answer: The request can hang or the server can crash
Uncaught errors can stop the request or the process. Wrap handlers in try and catch.
From Handling errors well
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Networking, storage and Firebase” before its exam: Local storage, Firebase in Flutter.
- Module 8 exam: Networking, storage and Firebase · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Animations and performance. 50 topics left in the course before the final exam.