On this page25 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. Why Turn JSON into Objects?
- 2. A fromJson Factory Constructor
- 3. A toJson Method
- 4. jsonEncode Calls toJson for You
- 5. Optional Fields and Defaults
- 6. Numbers: int or double?
- 7. Nested Objects
- 8. Lists of Objects
- 9. Enums as Names
- 10. Dates as ISO Strings
- 11. Validating with a Map Pattern
- 12. Code Generation
- 13. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 109 — JSON & Dart Objects
| Category | 18 — Files, JSON & Core Utilities |
| Lecture | 109 of 140 |
| Estimated video length | 13 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
jsonDecodehands us aMap<String, dynamic>, and one misspelled key gives a silentnull. AfromJsonconstructor and atoJsonmethod turn JSON into real, typed objects and back again.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2. Stack trace lines after
Unhandled exception:are left out to save space.
Prerequisites
Students should already understand, from Lectures 054–108:
- constructors, named constructors and
factoryconstructors (Lectures 054–055) - enums with
nameandvalues.byName(Lecture 067) - map patterns and
switchexpressions (Lectures 080–081) - throwing
FormatException(Lecture 085) jsonEncode,jsonDecodeandMap<String, dynamic>(Lecture 108)
Connection With Previous Lectures
In Lecture 108 we decoded JSON into maps:
import 'dart:convert';
void main() {
var data = jsonDecode('{"name": "Ali", "marks": 85}') as Map<String, dynamic>;
print(data['nmae']);
}nullOne typo, and Dart says nothing — the value is dynamic, so the compiler cannot help. Real
programs pass a student, an order or an account around in dozens of places. We want the
safety of a class, with fields Dart checks, while still reading and writing JSON.
Learning Objectives
By the end of this lecture you will be able to:
- Write a
fromJsonfactory constructor that builds an object from a map - Write a
toJsonmethod that turns an object back into a map - Explain how
jsonEncodecallstoJsonfor you - Handle optional fields with nullable types and defaults
- Convert nested objects and lists of objects
- Store enums by
nameand dates as ISO strings - Reject bad data with a map pattern and
FormatException - Recognize where code generation fits in larger projects
1. Why Turn JSON into Objects?
A map accepts any key and returns dynamic. A class has a fixed set of typed fields, and a
typo becomes a compile error.
Analogy — a map is a pile of sticky notes; a class is a printed form. The form has one box per field, and you cannot write in a box that does not exist.
The plan: decode JSON to a map once, convert it to an object, and work only with objects from then on.
2. A fromJson Factory Constructor
In Lecture 055 we wrote Student.fromMap. The Dart convention for JSON is the same idea under
the name fromJson:
import 'dart:convert';
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
factory Student.fromJson(Map<String, dynamic> json) {
return Student(json['name'] as String, json['marks'] as int);
}
}
void main() {
var data = jsonDecode('{"name": "Ali", "marks": 85}') as Map<String, dynamic>;
var ali = Student.fromJson(data);
print('${ali.name}: ${ali.marks}');
}Ali: 85Each as turns a dynamic value into a real type. After that line, ali.nmae would be a
compile error.
3. A toJson Method
The way back is a method that returns a map using JSON-friendly values only: strings,
numbers, booleans, null, lists and maps.
import 'dart:convert';
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
Map<String, dynamic> toJson() => {'name': name, 'marks': marks};
}
void main() {
var sara = Student('Sara', 92);
print(jsonEncode(sara.toJson()));
}{"name":"Sara","marks":92}4. jsonEncode Calls toJson for You
When jsonEncode meets an object it cannot encode, it calls that object's toJson() method.
So you can pass the object itself — even inside a list:
import 'dart:convert';
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
Map<String, dynamic> toJson() => {'name': name, 'marks': marks};
}
void main() {
print(jsonEncode(Student('Ali', 85)));
print(jsonEncode([Student('Sara', 92), Student('Zayan', 70)]));
}{"name":"Ali","marks":85}
[{"name":"Sara","marks":92},{"name":"Zayan","marks":70}]Without a toJson method, the same call fails while the program runs:
Unhandled exception:
Converting object to an encodable object failed: Instance of 'Student'5. Optional Fields and Defaults
A key may be missing or null. Cast to a nullable type, then decide: keep the null, or
use ?? for a default.
class Student {
final String name;
final String city;
final String? email;
Student(this.name, this.city, this.email);
factory Student.fromJson(Map<String, dynamic> json) {
return Student(
json['name'] as String,
json['city'] as String? ?? 'Karachi',
json['email'] as String?,
);
}
Map<String, dynamic> toJson() => {
'name': name,
'city': city,
if (email != null) 'email': email,
};
}
void main() {
var zayan = Student.fromJson({'name': 'Zayan'});
print('${zayan.city}, ${zayan.email}');
print(zayan.toJson());
}Karachi, null
{name: Zayan, city: Karachi}The collection if from Lecture 046 leaves email out of the JSON when there is none.
6. Numbers: int or double?
JSON has one number type. jsonDecode gives an int for 500 and a double for 500.5,
so as double fails on a whole number. Cast to num and convert:
import 'dart:convert';
void main() {
var data = jsonDecode('{"price": 500}') as Map<String, dynamic>;
var price = (data['price'] as num).toDouble();
print(price);
}500.07. Nested Objects
When a field is another class, its JSON is a map inside the map. Each class converts
itself: the outer fromJson hands the inner map to the inner fromJson.
import 'dart:convert';
class Address {
final String city;
Address(this.city);
factory Address.fromJson(Map<String, dynamic> json) =>
Address(json['city'] as String);
Map<String, dynamic> toJson() => {'city': city};
}
class Customer {
final String name;
final Address address;
Customer(this.name, this.address);
factory Customer.fromJson(Map<String, dynamic> json) => Customer(
json['name'] as String,
Address.fromJson(json['address'] as Map<String, dynamic>),
);
Map<String, dynamic> toJson() => {'name': name, 'address': address.toJson()};
}
void main() {
var text = '{"name": "Sara", "address": {"city": "Lahore"}}';
var sara = Customer.fromJson(jsonDecode(text) as Map<String, dynamic>);
print(sara.address.city);
print(jsonEncode(sara));
}Lahore
{"name":"Sara","address":{"city":"Lahore"}}8. Lists of Objects
A JSON array decodes to a List<dynamic>. Cast it to List, then map each element through
fromJson:
import 'dart:convert';
class Item {
final String name;
final int price;
Item(this.name, this.price);
factory Item.fromJson(Map<String, dynamic> json) =>
Item(json['name'] as String, json['price'] as int);
}
void main() {
var text =
'{"items": [{"name": "Pen", "price": 50}, {"name": "Bag", "price": 1500}]}';
var data = jsonDecode(text) as Map<String, dynamic>;
var items = (data['items'] as List)
.map((e) => Item.fromJson(e as Map<String, dynamic>))
.toList();
print(items.length);
print(items[1].name);
}2
Bagitems is a real List<Item>. Going back, items.map((i) => i.toJson()).toList() — or
simply the list itself, since jsonEncode calls each toJson.
9. Enums as Names
JSON has no enums. Store the value's name and read it back with values.byName
(Lecture 067):
enum OrderStatus { pending, shipped, delivered }
void main() {
var status = OrderStatus.shipped;
var json = {'status': status.name};
print(json);
var back = OrderStatus.values.byName(json['status'] as String);
print(back == OrderStatus.shipped);
}{status: shipped}
trueAn unknown name fails loudly with Invalid argument (name): No enum value with that name.
10. Dates as ISO Strings
JSON has no dates either. The usual format is an ISO 8601 string:
toIso8601String() writes one and DateTime.parse reads it.
void main() {
var placed = DateTime(2026, 10, 17, 9, 30);
var text = placed.toIso8601String();
print(text);
print(DateTime.parse(text) == placed);
}2026-10-17T09:30:00.000
trueWe will study DateTime properly in Lecture 110 — Date & Time.
11. Validating with a Map Pattern
json['marks'] as int crashes with a confusing TypeError when data is bad. A map pattern
(Lecture 080) checks every key and type in one step, and we throw a clear FormatException
(Lecture 085) when it does not match:
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
factory Student.fromJson(Map<String, dynamic> json) => switch (json) {
{'name': String name, 'marks': int marks} => Student(name, marks),
_ => throw FormatException('Invalid student: $json'),
};
}
void main() {
print(Student.fromJson({'name': 'Ali', 'marks': 85}).marks);
try {
Student.fromJson({'name': 'Sara', 'marks': '92'});
} on FormatException catch (e) {
print(e.message);
}
}85
Invalid student: {name: Sara, marks: 92}'92' is a String, not an int, so the pattern fails. Remember that a map pattern fails on
a missing key, so read optional fields separately, as in section 5.
12. Code Generation
Large projects often use the json_serializable package, which reads your class and
generates both methods — the same code you wrote by hand today. Adding packages was covered in Lectures 103–105; we
will not use code generation in this course.
13. A Real-World Example
An online order with a customer name, an enum status, a date and a list of items:
import 'dart:convert';
enum OrderStatus { pending, shipped }
class Item {
final String name;
final int price;
Item(this.name, this.price);
factory Item.fromJson(Map<String, dynamic> json) => switch (json) {
{'name': String name, 'price': int price} => Item(name, price),
_ => throw FormatException('Invalid item: $json'),
};
Map<String, dynamic> toJson() => {'name': name, 'price': price};
}
class Order {
final String customer;
final OrderStatus status;
final DateTime placed;
final List<Item> items;
Order(this.customer, this.status, this.placed, this.items);
int get total => items.fold(0, (sum, i) => sum + i.price);
factory Order.fromJson(Map<String, dynamic> json) => switch (json) {
{
'customer': String customer,
'status': String status,
'placed': String placed,
'items': List items,
} =>
Order(
customer,
OrderStatus.values.byName(status),
DateTime.parse(placed),
items.map((e) => Item.fromJson(e as Map<String, dynamic>)).toList(),
),
_ => throw FormatException('Invalid order: $json'),
};
Map<String, dynamic> toJson() => {
'customer': customer,
'status': status.name,
'placed': placed.toIso8601String(),
'items': items,
};
}
void main() {
var text = '''
{"customer": "Ali", "status": "pending", "placed": "2026-10-17T09:30:00.000",
"items": [{"name": "Pen", "price": 50}, {"name": "Bag", "price": 1500}]}''';
var order = Order.fromJson(jsonDecode(text) as Map<String, dynamic>);
print('${order.customer}: ${order.items.length} items, total ${order.total}');
print(jsonEncode(order));
}Ali: 2 items, total 1550
{"customer":"Ali","status":"pending","placed":"2026-10-17T09:30:00.000","items":[{"name":"Pen","price":50},{"name":"Bag","price":1500}]}Inside the program everything is typed: order.status is an OrderStatus, order.placed a
DateTime. toJson puts items in as objects, and jsonEncode calls each Item.toJson.
Code Examples
Example 1 — A Book from JSON
import 'dart:convert';
class Book {
final String title;
final int pages;
Book(this.title, this.pages);
factory Book.fromJson(Map<String, dynamic> json) =>
Book(json['title'] as String, json['pages'] as int);
}
void main() {
var book = Book.fromJson(
jsonDecode('{"title": "Dart Basics", "pages": 240}')
as Map<String, dynamic>,
);
print('${book.title} has ${book.pages} pages');
}Dart Basics has 240 pagesExample 2 — A City to JSON
import 'dart:convert';
class City {
final String name;
final int population;
City(this.name, this.population);
Map<String, dynamic> toJson() => {'name': name, 'population': population};
}
void main() {
print(jsonEncode(City('Karachi', 20000000)));
}{"name":"Karachi","population":20000000}Example 3 — A Round Trip
import 'dart:convert';
class Pet {
final String name;
final int age;
Pet(this.name, this.age);
factory Pet.fromJson(Map<String, dynamic> json) =>
Pet(json['name'] as String, json['age'] as int);
Map<String, dynamic> toJson() => {'name': name, 'age': age};
}
void main() {
var text = jsonEncode(Pet('Milo', 3));
var copy = Pet.fromJson(jsonDecode(text) as Map<String, dynamic>);
print(text);
print('${copy.name}, ${copy.age}');
}{"name":"Milo","age":3}
Milo, 3Example 4 — Default Values
class Player {
final String name;
final int level;
final String? team;
Player(this.name, this.level, this.team);
factory Player.fromJson(Map<String, dynamic> json) => Player(
json['name'] as String,
json['level'] as int? ?? 1,
json['team'] as String?,
);
}
void main() {
var p = Player.fromJson({'name': 'Zayan'});
print('${p.name} L${p.level} ${p.team ?? 'no team'}');
}Zayan L1 no teamExample 5 — An Enum Field
import 'dart:convert';
enum Size { small, medium, large }
class Coffee {
final Size size;
Coffee(this.size);
factory Coffee.fromJson(Map<String, dynamic> json) =>
Coffee(Size.values.byName(json['size'] as String));
Map<String, dynamic> toJson() => {'size': size.name};
}
void main() {
var c = Coffee.fromJson({'size': 'large'});
print(c.size);
print(jsonEncode(c));
}Size.large
{"size":"large"}Example 6 — A Top-Level JSON Array
import 'dart:convert';
class Tag {
final String label;
Tag(this.label);
factory Tag.fromJson(Map<String, dynamic> json) =>
Tag(json['label'] as String);
}
void main() {
var text = '[{"label": "dart"}, {"label": "json"}]';
var tags = (jsonDecode(text) as List)
.map((e) => Tag.fromJson(e as Map<String, dynamic>))
.toList();
print(tags.map((t) => t.label).join(', '));
}dart, jsonExample 7 — Student Marks
import 'dart:convert';
class Student {
final String name;
final int marks;
Student(this.name, this.marks);
factory Student.fromJson(Map<String, dynamic> json) =>
Student(json['name'] as String, json['marks'] as int);
}
void main() {
var text = '[{"name": "Ali", "marks": 85}, {"name": "Sara", "marks": 92}]';
var students = (jsonDecode(text) as List)
.map((e) => Student.fromJson(e as Map<String, dynamic>))
.toList();
var total = students.fold(0, (sum, s) => sum + s.marks);
print('Average: ${total / students.length}');
}Average: 88.5fold adds the typed marks fields: 85 + 92 = 177, and 177 / 2 is 88.5.
Example 8 — Shopping Cart
import 'dart:convert';
class CartItem {
final String name;
final double price;
final int quantity;
CartItem(this.name, this.price, this.quantity);
factory CartItem.fromJson(Map<String, dynamic> json) => CartItem(
json['name'] as String,
(json['price'] as num).toDouble(),
json['quantity'] as int? ?? 1,
);
double get total => price * quantity;
}
void main() {
var text =
'[{"name": "Notebook", "price": 500, "quantity": 3}, '
'{"name": "Pen", "price": 49.5}]';
var cart = (jsonDecode(text) as List)
.map((e) => CartItem.fromJson(e as Map<String, dynamic>))
.toList();
print('Total: ${cart.fold(0.0, (sum, i) => sum + i.total)}');
}Total: 1549.5How it works
500decodes as anintand49.5as adouble;(as num).toDouble()accepts both.- The pen has no
quantity, so?? 1supplies the default. 1500.0 + 49.5gives1549.5.
Example 9 — Bank Account with Validation
class Account {
final String owner;
final double balance;
Account(this.owner, this.balance);
factory Account.fromJson(Map<String, dynamic> json) => switch (json) {
{'owner': String owner, 'balance': num balance} when balance >= 0 =>
Account(owner, balance.toDouble()),
_ => throw FormatException('Invalid account: $json'),
};
}
void main() {
var inputs = [
{'owner': 'Sara', 'balance': 2000},
{'owner': 'Ali', 'balance': -50},
];
for (var json in inputs) {
try {
var a = Account.fromJson(json);
print('${a.owner}: ${a.balance}');
} on FormatException catch (e) {
print('Rejected — ${e.message}');
}
}
}Sara: 2000.0
Rejected — Invalid account: {owner: Ali, balance: -50}How it works
- The pattern requires a
Stringowner and a numeric balance. when balance >= 0adds a rule the types alone cannot express.- Bad data never becomes an
Account; the caller gets a clearFormatException.
Example 10 — Temperature Reading with a Date
import 'dart:convert';
class Reading {
final double celsius;
final DateTime takenAt;
Reading(this.celsius, this.takenAt);
factory Reading.fromJson(Map<String, dynamic> json) => Reading(
(json['celsius'] as num).toDouble(),
DateTime.parse(json['takenAt'] as String),
);
Map<String, dynamic> toJson() => {
'celsius': celsius,
'takenAt': takenAt.toIso8601String(),
};
}
void main() {
var r = Reading.fromJson({
'celsius': 25,
'takenAt': '2026-10-17T14:00:00.000',
});
print('${r.celsius * 9 / 5 + 32} °F at hour ${r.takenAt.hour}');
print(jsonEncode(r));
}77.0 °F at hour 14
{"celsius":25.0,"takenAt":"2026-10-17T14:00:00.000"}Common Beginner Mistakes
1. Casting a JSON number straight to double
// var price = data['price'] as double; // ✗ when the JSON says 500Unhandled exception:
type 'int' is not a subtype of type 'double' in type castWrite (data['price'] as num).toDouble().
2. Casting a JSON array to a typed list
// var items = data['items'] as List<Map<String, dynamic>>; // ✗Unhandled exception:
type 'List<dynamic>' is not a subtype of type 'List<Map<String, dynamic>>' in type castThe list is a List<dynamic>. Cast to List and cast each element inside map.
3. Forgetting toJson
// print(jsonEncode(Pen('Blue'))); // ✗ Pen has no toJson methodUnhandled exception:
Converting object to an encodable object failed: Instance of 'Pen'4. A required cast on an optional key
// var marks = json['marks'] as int; // ✗ when 'marks' is missingUnhandled exception:
type 'Null' is not a subtype of type 'int' in type castUse as int? with a default, or validate with a pattern and throw a FormatException.
5. Putting an enum or a DateTime into toJson directly
// Map<String, dynamic> toJson() => {'status': status}; // ✗Unhandled exception:
Converting object to an encodable object failed: Instance of 'OrderStatus'Write status.name, and placed.toIso8601String() for dates.
Best Practices
- Convert JSON to objects at the edge of the program; pass objects everywhere else.
- Name the pair
fromJson(afactory) andtoJson, so every Dart developer recognizes them. - Use the same key names in both directions, so a round trip loses nothing.
- Validate untrusted data with a map pattern and throw a
FormatExceptionwith a clear message. - Read numbers as
numwhen the field is adouble. - Store enums by
nameand dates withtoIso8601String().
Mini Challenge
Write a Movie class with title (String), rating (double) and genre (an enum with
drama and comedy). Decode the JSON {"title": "Up", "rating": 8, "genre": "comedy"},
print the movie, then encode it again.
Your program should print exactly:
Up (comedy) 8.0
{"title":"Up","rating":8.0,"genre":"comedy"}Try it yourself before opening the solution.
Show solution
import 'dart:convert';
enum Genre { drama, comedy }
class Movie {
final String title;
final double rating;
final Genre genre;
Movie(this.title, this.rating, this.genre);
factory Movie.fromJson(Map<String, dynamic> json) => Movie(
json['title'] as String,
(json['rating'] as num).toDouble(),
Genre.values.byName(json['genre'] as String),
);
Map<String, dynamic> toJson() => {
'title': title,
'rating': rating,
'genre': genre.name,
};
}
void main() {
var text = '{"title": "Up", "rating": 8, "genre": "comedy"}';
var movie = Movie.fromJson(jsonDecode(text) as Map<String, dynamic>);
print('${movie.title} (${movie.genre.name}) ${movie.rating}');
print(jsonEncode(movie));
}Up (comedy) 8.0
{"title":"Up","rating":8.0,"genre":"comedy"}How it works
8decodes as anint;toDouble()makes it8.0.byNameturns'comedy'intoGenre.comedy, andnameturns it back.jsonEncode(movie)callstoJsonautomatically.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Write a
Fruitclass with anamefield and afromJsonfactory. Build one from{'name': 'Mango'}and print its name. - Give a
Carclass (brand,year) atoJsonmethod and printjsonEncode(Car('Honda', 2024)). - Without running it: what does
jsonEncodedo with an object that has atoJsonmethod? And with one that has none?
Application
- Write
Teacher.fromJsonwheresubjectdefaults to'Math'when missing. Print the subject for{'name': 'Sara'}. - Decode
[{"name": "Ali"}, {"name": "Zayan"}]into aList<Friend>and print the names joined with' & '. - A
Producthasnameandprice(double). Decode{"name": "Bag", "price": 1500}and printBag costs 1500.0. - A
Taskhas atitleand apriorityenum (low,high). EncodeTask('Study', Priority.high)to JSON.
Challenge
- A
Schoolhas anameand a nestedPrincipal(with aname). Write bothfromJsonfactories and print the principal's name from{"name": "City School", "principal": {"name": "Sara"}}. - Write
Score.fromJsonwith aswitchand a map pattern that accepts only{'player': String, 'points': int}and throws aFormatException('Bad score')otherwise. Try it on{'player': 'Ali', 'points': 'ten'}and print the message. - An
Eventhas atitleand aDateTime date. WritetoJsonandfromJson, encodeEvent('Exam', DateTime(2026, 12, 1)), decode the result, and print thedayof the copy's date.
Practice Answers
Question 1 — Solution
class Fruit {
final String name;
Fruit(this.name);
factory Fruit.fromJson(Map<String, dynamic> json) =>
Fruit(json['name'] as String);
}
void main() {
print(Fruit.fromJson({'name': 'Mango'}).name);
}MangoQuestion 2 — Solution
import 'dart:convert';
class Car {
final String brand;
final int year;
Car(this.brand, this.year);
Map<String, dynamic> toJson() => {'brand': brand, 'year': year};
}
void main() {
print(jsonEncode(Car('Honda', 2024)));
}{"brand":"Honda","year":2024}Question 3 — Solution
With toJson, jsonEncode calls it and encodes the returned map. Without one, it throws:
Converting object to an encodable object failed: Instance of '...'.
Question 4 — Solution
class Teacher {
final String name;
final String subject;
Teacher(this.name, this.subject);
factory Teacher.fromJson(Map<String, dynamic> json) =>
Teacher(json['name'] as String, json['subject'] as String? ?? 'Math');
}
void main() {
print(Teacher.fromJson({'name': 'Sara'}).subject);
}MathQuestion 5 — Solution
import 'dart:convert';
class Friend {
final String name;
Friend(this.name);
factory Friend.fromJson(Map<String, dynamic> json) =>
Friend(json['name'] as String);
}
void main() {
var friends = (jsonDecode('[{"name": "Ali"}, {"name": "Zayan"}]') as List)
.map((e) => Friend.fromJson(e as Map<String, dynamic>))
.toList();
print(friends.map((f) => f.name).join(' & '));
}Ali & ZayanQuestion 6 — Solution
import 'dart:convert';
class Product {
final String name;
final double price;
Product(this.name, this.price);
factory Product.fromJson(Map<String, dynamic> json) =>
Product(json['name'] as String, (json['price'] as num).toDouble());
}
void main() {
var data =
jsonDecode('{"name": "Bag", "price": 1500}') as Map<String, dynamic>;
var bag = Product.fromJson(data);
print('${bag.name} costs ${bag.price}');
}Bag costs 1500.0Question 7 — Solution
import 'dart:convert';
enum Priority { low, high }
class Task {
final String title;
final Priority priority;
Task(this.title, this.priority);
Map<String, dynamic> toJson() => {'title': title, 'priority': priority.name};
}
void main() {
print(jsonEncode(Task('Study', Priority.high)));
}{"title":"Study","priority":"high"}Question 8 — Solution
import 'dart:convert';
class Principal {
final String name;
Principal(this.name);
factory Principal.fromJson(Map<String, dynamic> json) =>
Principal(json['name'] as String);
}
class School {
final String name;
final Principal principal;
School(this.name, this.principal);
factory School.fromJson(Map<String, dynamic> json) => School(
json['name'] as String,
Principal.fromJson(json['principal'] as Map<String, dynamic>),
);
}
void main() {
var text = '{"name": "City School", "principal": {"name": "Sara"}}';
var school = School.fromJson(jsonDecode(text) as Map<String, dynamic>);
print(school.principal.name);
}SaraQuestion 9 — Solution
class Score {
final String player;
final int points;
Score(this.player, this.points);
factory Score.fromJson(Map<String, dynamic> json) => switch (json) {
{'player': String player, 'points': int points} => Score(player, points),
_ => throw FormatException('Bad score'),
};
}
void main() {
try {
Score.fromJson({'player': 'Ali', 'points': 'ten'});
} on FormatException catch (e) {
print(e.message);
}
}Bad scoreQuestion 10 — Solution
import 'dart:convert';
class Event {
final String title;
final DateTime date;
Event(this.title, this.date);
factory Event.fromJson(Map<String, dynamic> json) =>
Event(json['title'] as String, DateTime.parse(json['date'] as String));
Map<String, dynamic> toJson() => {
'title': title,
'date': date.toIso8601String(),
};
}
void main() {
var text = jsonEncode(Event('Exam', DateTime(2026, 12, 1)));
var copy = Event.fromJson(jsonDecode(text) as Map<String, dynamic>);
print(copy.date.day);
}1Quick Revision
- What does a
fromJsonfactory receive, and what does it return? - Which value types may a
toJsonmap contain? - What does
jsonEncodedo when it meets an object with atoJsonmethod? - How do you read an optional key with a default?
- Why is
as doublerisky for a JSON number? - How do you convert a JSON array into a
Listof objects? - How are enums and dates stored in JSON?
- What does a map pattern check, and what should you throw when it fails?
Key Takeaways
- A
fromJsonfactory turns aMap<String, dynamic>into a typed object; atoJsonmethod turns it back. jsonEncodecallstoJsonautomatically, even for objects inside lists and maps.- Optional fields use
as Type?, with??for a default. - Read doubles as
(value as num).toDouble(). - Nested objects call the inner class's
fromJson; lists usemap(...).toList(). - Enums travel as
name/values.byName; dates as ISO 8601 strings. - Map patterns plus
FormatExceptionkeep bad data from ever becoming an object. json_serializablecan generate this code in large projects.
What's Next?
Lecture 110 — Date & Time. Today a date was just a string we passed to DateTime.parse.
Next we will look at DateTime itself: creating dates, reading their parts, comparing them,
formatting them, and handling local time versus UTC.