Watch the video (10 min)
On this page24 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is JSON?
  5. 2. Map to JSON with jsonEncode
  6. 3. JSON to Dart with jsonDecode
  7. 4. JSON Types and Dart Types
  8. 5. The int or double Trap
  9. 6. Reading Nested Values
  10. 7. Validating with Map Patterns
  11. 8. JSON Arrays
  12. 9. Bad JSON Throws a FormatException
  13. 10. Pretty Printing
  14. 11. Saving and Loading a JSON File
  15. 12. A Real-World Example
  16. Code Examples
  17. Common Beginner Mistakes
  18. Best Practices
  19. Mini Challenge
  20. Practice Questions
  21. Practice Answers
  22. Quick Revision
  23. Key Takeaways
  24. What's Next?

Lecture 108 — JSON

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

A map printed to a file as {name: Ali, marks: 85} can never be turned back into a map. JSON is a text format that both sides understand: save it, send it, and read it back.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every output and 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 an error are left out.


Prerequisites

Students should already understand, from Lectures 011–107:

  • dynamic and casting with as (Lectures 010–011)
  • maps and lists (Lectures 039 and 049)
  • map patterns in if-case (Lecture 080)
  • try-on-catch and FormatException (Lecture 084)
  • async/await (Lecture 089), import (Lecture 101), reading and writing files (Lecture 106)

Connection With Previous Lectures

In Lecture 106 we wrote text into files. Now try to save something with structure, such as a student and her marks:

dart
void main() {
  var student = {'name': 'Sara', 'marks': 88};
  print(student.toString());
}
output
{name: Sara, marks: 88}

That text looks fine, but nothing in Dart can turn it back into a map. Is Sara a string? Is 88 a number or the text "88"? We need a format that keeps those answers, and one that other programs can read too.


Learning Objectives

By the end of this lecture you will be able to:

  • Explain what JSON is and why programs use it
  • Convert maps and lists to JSON text with jsonEncode
  • Convert JSON text back to Dart values with jsonDecode
  • Match each JSON type to the Dart type it becomes
  • Read nested values and avoid the crashes dynamic allows
  • Validate decoded data with map patterns
  • Catch the FormatException that bad JSON throws
  • Pretty-print JSON and save it to a file and load it back

1. What Is JSON?

JSON (JavaScript Object Notation) is a way to write data as plain text. Almost every language, website and app can read it.

Analogy — JSON is like a printed form. Any office, in any country, can fill it in and read it back, because everyone agrees on where the boxes are.

json
{
  "name": "Sara",
  "marks": 88,
  "passed": true,
  "subjects": ["Math", "English"]
}

Curly braces hold an object (keys and values), square brackets hold an array. Keys and strings always use double quotes.


2. Map to JSON with jsonEncode

JSON tools live in dart:convert (Lecture 101). jsonEncode turns a map or list into a JSON string:

dart
import 'dart:convert';

void main() {
  var student = {'name': 'Sara', 'marks': 88};
  var text = jsonEncode(student);
  print(text);
}
output
{"name":"Sara","marks":88}

Compare it with the toString() output above: the quotes now tell any reader that Sara is text and 88 is a number.


3. JSON to Dart with jsonDecode

jsonDecode goes the other way. It reads JSON text and builds Dart values:

dart
import 'dart:convert';

void main() {
  var text = '{"name":"Sara","marks":88}';
  var data = jsonDecode(text);
  print(data['name']);
  print(data['marks'] + 2);
}
output
Sara
90

jsonDecode returns dynamic (Lecture 011): Dart cannot know what the text holds until the program runs.


4. JSON Types and Dart Types

Each JSON value becomes one Dart type:

JSONDart
object {...}Map<String, dynamic>
array [...]List<dynamic>
"text"String
88int
88.5double
true / falsebool
nullnull
dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"name":"Ali","marks":[80,75],"average":77.5}');
  print(data is Map<String, dynamic>);
  print(data['marks'] is List<dynamic>);
  print(data['average'] is double);
}
output
true
true
true

The values inside are dynamic, because one JSON object can mix strings, numbers and lists.


5. The int or double Trap

JSON has one kind of number. Dart decides from the text: 30 becomes an int, 30.5 a double. A temperature written as 30 therefore breaks a double variable:

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"temperature": 30}');
  double temperature = data['temperature'];
  print(temperature);
}
output
Unhandled exception:
type 'int' is not a subtype of type 'double'

Cast to num, which covers both, and convert:

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"temperature": 30}');
  var temperature = (data['temperature'] as num).toDouble();
  print(temperature);
}
output
30.0

6. Reading Nested Values

Objects can contain objects. With dynamic, you chain the keys:

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"name":"Zayan","address":{"city":"Lahore"}}');
  print(data['address']['city']);
  print(data['school']['city']);
}
output
Lahore
Unhandled exception:
NoSuchMethodError: The method '[]' was called on null.
Receiver: null
Tried calling: []("city")

There is no school key, so data['school'] is null, and null['city'] crashes. Dart did not warn us, because dynamic switches the checks off (Lecture 011).


7. Validating with Map Patterns

The map patterns from Lecture 080 check keys and types in one step, and hand back typed variables:

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"name":"Zayan","address":{"city":"Lahore"}}');
  if (data case {'name': String name, 'address': {'city': String city}}) {
    print('$name lives in $city');
  } else {
    print('Unexpected data');
  }
}
output
Zayan lives in Lahore

A missing key, a null, or a wrong type simply fails the match and the else runs. No crash, and name and city are real Strings from here on.


8. JSON Arrays

A JSON array decodes to a List<dynamic>. Loop over it and check each item:

dart
import 'dart:convert';

void main() {
  var text =
      '[{"name":"Ali","marks":80},{"name":"Sara"},{"name":"Zayan","marks":95}]';
  var students = jsonDecode(text) as List;
  for (var item in students) {
    if (item case {'name': String name, 'marks': int marks}) {
      print('$name: $marks');
    } else {
      print('Skipped: $item');
    }
  }
}
output
Ali: 80
Skipped: {name: Sara}
Zayan: 95

as List tells Dart we expect an array; Sara's record has no marks, so it is skipped instead of crashing the loop.


9. Bad JSON Throws a FormatException

Text that is not valid JSON makes jsonDecode throw a FormatException, the same exception as int.parse (Lecture 084):

dart
import 'dart:convert';

void main() {
  try {
    jsonDecode("{'name': 'Ali'}");
  } on FormatException catch (e) {
    print('Bad JSON: ${e.message}');
  }
}
output
Bad JSON: Unexpected character

The single quotes are the problem: JSON only accepts double quotes. Always wrap jsonDecode in try when the text comes from a file, a user or the internet.


10. Pretty Printing

jsonEncode packs everything on one line. JsonEncoder.withIndent adds line breaks and indentation so people can read it:

dart
import 'dart:convert';

void main() {
  var student = {
    'name': 'Sara',
    'marks': [90, 85],
  };
  var encoder = JsonEncoder.withIndent('  ');
  print(encoder.convert(student));
}
output
{
  "name": "Sara",
  "marks": [
    90,
    85
  ]
}

The ' ' argument is the indent for each level. Both outputs hold the same data.


11. Saving and Loading a JSON File

JSON is just text, so the file methods from Lecture 106 store it directly:

dart
import 'dart:convert';
import 'dart:io';

Future<void> main() async {
  var file = File('account.json');
  await file.writeAsString(jsonEncode({'owner': 'Ali', 'balance': 1500.0}));

  var text = await file.readAsString();
  var data = jsonDecode(text);
  print('${data['owner']} has ${data['balance']}');
}
output
Ali has 1500.0

Encode before writing, decode after reading. Turning JSON into your own classes, such as an Account object, is Lecture 109.


12. A Real-World Example

Sara's marks are stored in a file. The program loads them, checks them, adds a mark, and saves them back in readable form:

dart
import 'dart:convert';
import 'dart:io';

Future<void> main() async {
  var file = File('marks.json');
  await file.writeAsString('{"student":"Sara","marks":[88,92]}');

  try {
    var data = jsonDecode(await file.readAsString());
    if (data case {'student': String name, 'marks': List marks}) {
      marks.add(79);
      print('$name now has ${marks.length} marks');
      await file.writeAsString(JsonEncoder.withIndent('  ').convert(data));
      print(await file.readAsString());
    } else {
      print('marks.json has the wrong shape');
    }
  } on FormatException catch (e) {
    print('marks.json is not valid JSON: ${e.message}');
  }
}
output
Sara now has 3 marks
{
  "student": "Sara",
  "marks": [
    88,
    92,
    79
  ]
}

FormatException guards against broken text, the map pattern guards against the wrong shape, and withIndent keeps the saved file easy to read.


Code Examples

Example 1 — Encode a Map

dart
import 'dart:convert';

void main() {
  print(jsonEncode({'name': 'Ali', 'age': 20}));
}
output
{"name":"Ali","age":20}

Example 2 — Encode a List

dart
import 'dart:convert';

void main() {
  print(jsonEncode(['Ali', 'Sara', 'Zayan']));
}
output
["Ali","Sara","Zayan"]

Example 3 — Decode and Read

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"city":"Karachi","population":20}');
  print(data['city']);
  print(data['population']);
}
output
Karachi
20

Example 4 — null and bool

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"active":true,"nickname":null}');
  print(data['active']);
  print(data['nickname'] ?? 'none');
}
output
true
none

Example 5 — Round Trip

dart
import 'dart:convert';

void main() {
  var original = {
    'score': 42,
    'tags': ['quiz', 'easy'],
  };
  var copy = jsonDecode(jsonEncode(original));
  print(copy['tags'][1]);
}
output
easy

Example 6 — Catching Bad JSON

dart
import 'dart:convert';

void main() {
  try {
    jsonDecode('{"name": "Ali",}');
  } on FormatException {
    print('Invalid JSON');
  }
}
output
Invalid JSON

A trailing comma after the last value is not allowed in JSON.

Example 7 — Student Marks

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"name":"Ali","marks":[80,75,85]}');
  if (data case {'name': String name, 'marks': List marks}) {
    var total = 0;
    for (var mark in marks) {
      total += mark as int;
    }
    print('$name: $total');
  }
}
output
Ali: 240

How it works

  1. The map pattern checks that marks is a list.
  2. Each item is dynamic, so as int makes the addition typed.

Example 8 — Shopping Total

dart
import 'dart:convert';

void main() {
  var text =
      '[{"item":"Notebook","price":500,"qty":3},{"item":"Pen","price":50,"qty":4}]';
  var total = 0;
  for (var line in jsonDecode(text) as List) {
    if (line case {'price': int price, 'qty': int qty}) {
      total += price * qty;
    }
  }
  print('Total: $total');
}
output
Total: 1700

How it works

  1. The array decodes to a list of maps.
  2. Each pattern pulls out typed price and qty; 500 * 3 + 50 * 4 is 1700.

Example 9 — Bank Balance

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"owner":"Sara","balance":2000}');
  var balance = (data['balance'] as num).toDouble();
  balance += 250.5;
  data['balance'] = balance;
  print(jsonEncode(data));
}
output
{"owner":"Sara","balance":2250.5}

How it works

  1. 2000 decodes as an int, so it is read through num and made a double.
  2. The decoded map can be changed and encoded again.

Example 10 — Temperature Report

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"city":"Lahore","readings":[31,33.5,29]}');
  var readings = data['readings'] as List;
  var sum = 0.0;
  for (var r in readings) {
    sum += r as num;
  }
  var report = {'city': data['city'], 'average': sum / readings.length};
  print(JsonEncoder.withIndent('  ').convert(report));
}
output
{
  "city": "Lahore",
  "average": 31.166666666666668
}

How it works

  1. The readings mix int and double, so each is cast to num.
  2. The result is a new map, pretty-printed as JSON.

Common Beginner Mistakes

1. Forgetting the import

dart
void main() {
  // print(jsonEncode({'a': 1})); // ✗ no import 'dart:convert';
}
output
Error: Method not found: 'jsonEncode'.

2. Single quotes inside JSON

dart
// jsonDecode("{'name': 'Ali'}"); // ✗ JSON needs double quotes
output
FormatException: Unexpected character (at character 2)

Dart strings may use single quotes; the JSON inside them may not.

3. Expecting a typed map

dart
// Map<String, int> marks = jsonDecode('{"math": 80}'); // ✗
output
type '_Map<String, dynamic>' is not a subtype of type 'Map<String, int>'

jsonDecode always builds Map<String, dynamic>. Read values one by one, or use a pattern.

4. Assigning a JSON 30 to a double

output
type 'int' is not a subtype of type 'double'

Use (value as num).toDouble().

5. Chaining keys that may be missing

data['school']['city'] crashes with NoSuchMethodError when school is absent. Use a map pattern.

6. Encoding a value JSON has no type for

dart
// jsonEncode({'date': DateTime(2026)}); // ✗
output
Converting object to an encodable object failed: Instance of 'DateTime'

Only maps, lists, strings, numbers, booleans and null can be encoded. Encoding your own objects is Lecture 109; dates are Lecture 110.


Best Practices

  • Wrap jsonDecode in try with on FormatException for any outside text.
  • Validate the shape with a map pattern before using decoded data.
  • Read numbers through num when they might be either int or double.
  • Keep dynamic values short-lived: move them into typed variables quickly.
  • Use JsonEncoder.withIndent for files people read; plain jsonEncode for compact data.

Mini Challenge

Decode {"name":"Zayan","scores":[7,9,8]}, find the best score with a loop, and print it. If the shape is wrong, print Bad data.

output
Zayan's best: 9

Try it yourself before opening the solution.

Show solution
dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"name":"Zayan","scores":[7,9,8]}');
  if (data case {'name': String name, 'scores': List scores}) {
    var best = 0;
    for (var s in scores) {
      if (s as int > best) best = s;
    }
    print("$name's best: $best");
  } else {
    print('Bad data');
  }
}
output
Zayan's best: 9

How it works

  1. The pattern confirms a String name and a List of scores.
  2. s as int makes each score typed; after the cast, best = s is allowed.

Practice Questions

Beginner

  1. Encode the map {'city': 'Karachi', 'zip': 74000} and print the JSON.
  2. Decode {"fruit":"Mango","price":120} and print Mango costs 120.
  3. Decode [10, 20, 30] and print the number of items.

Application

  1. Decode {"name":"Ali","age":"twenty"}. Use a map pattern expecting int age; print Valid or Invalid.
  2. Decode {"price": 99} and store the price in a double. Print it.
  3. Try to decode {"a": 1 and print Broken: followed by the exception's message.
  4. Pretty-print {'name': 'Sara', 'city': 'Lahore'} with a 4-space indent.

Challenge

  1. Decode {"user":{"name":"Sara","contact":{"email":"sara@mail.com"}}} and print the email using one nested map pattern.
  2. Decode [{"item":"Bag","price":1500},{"item":"Cap"}] and print only items that have a price, as Bag: 1500.
  3. Write {"temperature": 25} to weather.json, read it back, add 1.5, and save it again. Print the final file contents.

Practice Answers

Question 1 — Solution

dart
import 'dart:convert';

void main() {
  print(jsonEncode({'city': 'Karachi', 'zip': 74000}));
}
output
{"city":"Karachi","zip":74000}

Question 2 — Solution

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"fruit":"Mango","price":120}');
  print('${data['fruit']} costs ${data['price']}');
}
output
Mango costs 120

Question 3 — Solution

dart
import 'dart:convert';

void main() {
  var list = jsonDecode('[10, 20, 30]') as List;
  print(list.length);
}
output
3

Question 4 — Solution

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"name":"Ali","age":"twenty"}');
  if (data case {'name': String _, 'age': int _}) {
    print('Valid');
  } else {
    print('Invalid');
  }
}
output
Invalid

"twenty" is a String, so int _ fails.

Question 5 — Solution

dart
import 'dart:convert';

void main() {
  var data = jsonDecode('{"price": 99}');
  double price = (data['price'] as num).toDouble();
  print(price);
}
output
99.0

Question 6 — Solution

dart
import 'dart:convert';

void main() {
  try {
    jsonDecode('{"a": 1');
  } on FormatException catch (e) {
    print('Broken: ${e.message}');
  }
}
output
Broken: Unexpected end of input

Question 7 — Solution

dart
import 'dart:convert';

void main() {
  var encoder = JsonEncoder.withIndent('    ');
  print(encoder.convert({'name': 'Sara', 'city': 'Lahore'}));
}
output
{
    "name": "Sara",
    "city": "Lahore"
}

Question 8 — Solution

dart
import 'dart:convert';

void main() {
  var data = jsonDecode(
    '{"user":{"name":"Sara","contact":{"email":"sara@mail.com"}}}',
  );
  if (data case {'user': {'contact': {'email': String email}}}) {
    print(email);
  }
}
output
sara@mail.com

Question 9 — Solution

dart
import 'dart:convert';

void main() {
  var text = '[{"item":"Bag","price":1500},{"item":"Cap"}]';
  for (var line in jsonDecode(text) as List) {
    if (line case {'item': String item, 'price': int price}) {
      print('$item: $price');
    }
  }
}
output
Bag: 1500

Question 10 — Solution

dart
import 'dart:convert';
import 'dart:io';

Future<void> main() async {
  var file = File('weather.json');
  await file.writeAsString(jsonEncode({'temperature': 25}));

  var data = jsonDecode(await file.readAsString());
  data['temperature'] = (data['temperature'] as num) + 1.5;
  await file.writeAsString(jsonEncode(data));
  print(await file.readAsString());
}
output
{"temperature":26.5}

Quick Revision

  1. Which library holds jsonEncode and jsonDecode?
  2. What Dart type does a JSON object become? A JSON array?
  3. Why does jsonDecode return dynamic?
  4. Does {"t": 30} give an int or a double?
  5. What does jsonDecode throw for invalid text?
  6. Why is a map pattern safer than data['a']['b']?
  7. What does JsonEncoder.withIndent(' ') change?

Key Takeaways

  • JSON is plain text for data: objects {}, arrays [], double-quoted strings.
  • jsonEncode turns maps and lists into JSON; jsonDecode turns JSON into Dart values.
  • Objects become Map<String, dynamic>, arrays List<dynamic>.
  • 30 decodes to int and 30.5 to double; read numbers through num when unsure.
  • dynamic lets missing keys crash at run time; map patterns check keys and types safely.
  • Invalid JSON throws a FormatException; catch it for outside text.
  • JsonEncoder.withIndent produces readable JSON; files store JSON as ordinary text.

What's Next?

Lecture 109 — JSON & Dart Objects. We now read JSON into maps, but our programs work with classes like Student and Account. Next we will convert JSON straight into our own objects and back, with fromJson constructors and toJson methods.


Video lecture

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