Watch the video (13 min)
On this page25 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Why Turn JSON into Objects?
  5. 2. A fromJson Factory Constructor
  6. 3. A toJson Method
  7. 4. jsonEncode Calls toJson for You
  8. 5. Optional Fields and Defaults
  9. 6. Numbers: int or double?
  10. 7. Nested Objects
  11. 8. Lists of Objects
  12. 9. Enums as Names
  13. 10. Dates as ISO Strings
  14. 11. Validating with a Map Pattern
  15. 12. Code Generation
  16. 13. A Real-World Example
  17. Code Examples
  18. Common Beginner Mistakes
  19. Best Practices
  20. Mini Challenge
  21. Practice Questions
  22. Practice Answers
  23. Quick Revision
  24. Key Takeaways
  25. What's Next?

Lecture 109 — JSON & Dart Objects

Category18 — Files, JSON & Core Utilities
Lecture109 of 140
Estimated video length13 min
TargetDart 3.12.2
LevelIntermediate

jsonDecode hands us a Map<String, dynamic>, and one misspelled key gives a silent null. A fromJson constructor and a toJson method 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 factory constructors (Lectures 054–055)
  • enums with name and values.byName (Lecture 067)
  • map patterns and switch expressions (Lectures 080–081)
  • throwing FormatException (Lecture 085)
  • jsonEncode, jsonDecode and Map<String, dynamic> (Lecture 108)

Connection With Previous Lectures

In Lecture 108 we decoded JSON into maps:

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"name": "Ali", "marks": 85}') as Map<String, dynamic>;
  print(data['nmae']);
}
output
null

One 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 fromJson factory constructor that builds an object from a map
  • Write a toJson method that turns an object back into a map
  • Explain how jsonEncode calls toJson for you
  • Handle optional fields with nullable types and defaults
  • Convert nested objects and lists of objects
  • Store enums by name and 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:

dart
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}');
}
output
Ali: 85

Each 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.

dart
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()));
}
output
{"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:

dart
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)]));
}
output
{"name":"Ali","marks":85}
[{"name":"Sara","marks":92},{"name":"Zayan","marks":70}]

Without a toJson method, the same call fails while the program runs:

output
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.

dart
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());
}
output
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:

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"price": 500}') as Map<String, dynamic>;
  var price = (data['price'] as num).toDouble();
  print(price);
}
output
500.0

7. 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.

dart
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));
}
output
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:

dart
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);
}
output
2
Bag

items 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):

dart
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);
}
output
{status: shipped}
true

An 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.

dart
void main() {
  var placed = DateTime(2026, 10, 17, 9, 30);
  var text = placed.toIso8601String();
  print(text);
  print(DateTime.parse(text) == placed);
}
output
2026-10-17T09:30:00.000
true

We 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:

dart
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);
  }
}
output
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:

dart
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));
}
output
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

dart
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');
}
output
Dart Basics has 240 pages

Example 2 — A City to JSON

dart
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)));
}
output
{"name":"Karachi","population":20000000}

Example 3 — A Round Trip

dart
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}');
}
output
{"name":"Milo","age":3}
Milo, 3

Example 4 — Default Values

dart
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'}');
}
output
Zayan L1 no team

Example 5 — An Enum Field

dart
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));
}
output
Size.large
{"size":"large"}

Example 6 — A Top-Level JSON Array

dart
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(', '));
}
output
dart, json

Example 7 — Student Marks

dart
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}');
}
output
Average: 88.5

fold adds the typed marks fields: 85 + 92 = 177, and 177 / 2 is 88.5.

Example 8 — Shopping Cart

dart
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)}');
}
output
Total: 1549.5

How it works

  1. 500 decodes as an int and 49.5 as a double; (as num).toDouble() accepts both.
  2. The pen has no quantity, so ?? 1 supplies the default.
  3. 1500.0 + 49.5 gives 1549.5.

Example 9 — Bank Account with Validation

dart
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}');
    }
  }
}
output
Sara: 2000.0
Rejected — Invalid account: {owner: Ali, balance: -50}

How it works

  1. The pattern requires a String owner and a numeric balance.
  2. when balance >= 0 adds a rule the types alone cannot express.
  3. Bad data never becomes an Account; the caller gets a clear FormatException.

Example 10 — Temperature Reading with a Date

dart
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));
}
output
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

dart
// var price = data['price'] as double; // ✗ when the JSON says 500
output
Unhandled exception:
type 'int' is not a subtype of type 'double' in type cast

Write (data['price'] as num).toDouble().

2. Casting a JSON array to a typed list

dart
// var items = data['items'] as List<Map<String, dynamic>>; // ✗
output
Unhandled exception:
type 'List<dynamic>' is not a subtype of type 'List<Map<String, dynamic>>' in type cast

The list is a List<dynamic>. Cast to List and cast each element inside map.

3. Forgetting toJson

dart
// print(jsonEncode(Pen('Blue'))); // ✗ Pen has no toJson method
output
Unhandled exception:
Converting object to an encodable object failed: Instance of 'Pen'

4. A required cast on an optional key

dart
// var marks = json['marks'] as int; // ✗ when 'marks' is missing
output
Unhandled exception:
type 'Null' is not a subtype of type 'int' in type cast

Use as int? with a default, or validate with a pattern and throw a FormatException.

5. Putting an enum or a DateTime into toJson directly

dart
// Map<String, dynamic> toJson() => {'status': status}; // ✗
output
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 (a factory) and toJson, 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 FormatException with a clear message.
  • Read numbers as num when the field is a double.
  • Store enums by name and dates with toIso8601String().

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:

output
Up (comedy) 8.0
{"title":"Up","rating":8.0,"genre":"comedy"}

Try it yourself before opening the solution.

Show solution
dart
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));
}
output
Up (comedy) 8.0
{"title":"Up","rating":8.0,"genre":"comedy"}

How it works

  1. 8 decodes as an int; toDouble() makes it 8.0.
  2. byName turns 'comedy' into Genre.comedy, and name turns it back.
  3. jsonEncode(movie) calls toJson automatically.

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Write a Fruit class with a name field and a fromJson factory. Build one from {'name': 'Mango'} and print its name.
  2. Give a Car class (brand, year) a toJson method and print jsonEncode(Car('Honda', 2024)).
  3. Without running it: what does jsonEncode do with an object that has a toJson method? And with one that has none?

Application

  1. Write Teacher.fromJson where subject defaults to 'Math' when missing. Print the subject for {'name': 'Sara'}.
  2. Decode [{"name": "Ali"}, {"name": "Zayan"}] into a List<Friend> and print the names joined with ' & '.
  3. A Product has name and price (double). Decode {"name": "Bag", "price": 1500} and print Bag costs 1500.0.
  4. A Task has a title and a priority enum (low, high). Encode Task('Study', Priority.high) to JSON.

Challenge

  1. A School has a name and a nested Principal (with a name). Write both fromJson factories and print the principal's name from {"name": "City School", "principal": {"name": "Sara"}}.
  2. Write Score.fromJson with a switch and a map pattern that accepts only {'player': String, 'points': int} and throws a FormatException('Bad score') otherwise. Try it on {'player': 'Ali', 'points': 'ten'} and print the message.
  3. An Event has a title and a DateTime date. Write toJson and fromJson, encode Event('Exam', DateTime(2026, 12, 1)), decode the result, and print the day of the copy's date.

Practice Answers

Question 1 — Solution

dart
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);
}
output
Mango

Question 2 — Solution

dart
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)));
}
output
{"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

dart
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);
}
output
Math

Question 5 — Solution

dart
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(' & '));
}
output
Ali & Zayan

Question 6 — Solution

dart
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}');
}
output
Bag costs 1500.0

Question 7 — Solution

dart
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)));
}
output
{"title":"Study","priority":"high"}

Question 8 — Solution

dart
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);
}
output
Sara

Question 9 — Solution

dart
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);
  }
}
output
Bad score

Question 10 — Solution

dart
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);
}
output
1

Quick Revision

  1. What does a fromJson factory receive, and what does it return?
  2. Which value types may a toJson map contain?
  3. What does jsonEncode do when it meets an object with a toJson method?
  4. How do you read an optional key with a default?
  5. Why is as double risky for a JSON number?
  6. How do you convert a JSON array into a List of objects?
  7. How are enums and dates stored in JSON?
  8. What does a map pattern check, and what should you throw when it fails?

Key Takeaways

  • A fromJson factory turns a Map<String, dynamic> into a typed object; a toJson method turns it back.
  • jsonEncode calls toJson automatically, 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 use map(...).toList().
  • Enums travel as name / values.byName; dates as ISO 8601 strings.
  • Map patterns plus FormatException keep bad data from ever becoming an object.
  • json_serializable can 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.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.