On this page24 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is JSON?
- 2. Map to JSON with jsonEncode
- 3. JSON to Dart with jsonDecode
- 4. JSON Types and Dart Types
- 5. The int or double Trap
- 6. Reading Nested Values
- 7. Validating with Map Patterns
- 8. JSON Arrays
- 9. Bad JSON Throws a FormatException
- 10. Pretty Printing
- 11. Saving and Loading a JSON File
- 12. 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 108 — JSON
| Category | 18 — Files, JSON & Core Utilities |
| Lecture | 108 of 140 |
| Estimated video length | 10 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
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:
dynamicand casting withas(Lectures 010–011)- maps and lists (Lectures 039 and 049)
- map patterns in
if-case(Lecture 080) try-on-catchandFormatException(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:
void main() {
var student = {'name': 'Sara', 'marks': 88};
print(student.toString());
}{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
dynamicallows - Validate decoded data with map patterns
- Catch the
FormatExceptionthat 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.
{
"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:
import 'dart:convert';
void main() {
var student = {'name': 'Sara', 'marks': 88};
var text = jsonEncode(student);
print(text);
}{"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:
import 'dart:convert';
void main() {
var text = '{"name":"Sara","marks":88}';
var data = jsonDecode(text);
print(data['name']);
print(data['marks'] + 2);
}Sara
90jsonDecode 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:
| JSON | Dart |
|---|---|
object {...} | Map<String, dynamic> |
array [...] | List<dynamic> |
"text" | String |
88 | int |
88.5 | double |
true / false | bool |
null | null |
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);
}true
true
trueThe 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:
import 'dart:convert';
void main() {
var data = jsonDecode('{"temperature": 30}');
double temperature = data['temperature'];
print(temperature);
}Unhandled exception:
type 'int' is not a subtype of type 'double'Cast to num, which covers both, and convert:
import 'dart:convert';
void main() {
var data = jsonDecode('{"temperature": 30}');
var temperature = (data['temperature'] as num).toDouble();
print(temperature);
}30.06. Reading Nested Values
Objects can contain objects. With dynamic, you chain the keys:
import 'dart:convert';
void main() {
var data = jsonDecode('{"name":"Zayan","address":{"city":"Lahore"}}');
print(data['address']['city']);
print(data['school']['city']);
}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:
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');
}
}Zayan lives in LahoreA 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:
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');
}
}
}Ali: 80
Skipped: {name: Sara}
Zayan: 95as 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):
import 'dart:convert';
void main() {
try {
jsonDecode("{'name': 'Ali'}");
} on FormatException catch (e) {
print('Bad JSON: ${e.message}');
}
}Bad JSON: Unexpected characterThe 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:
import 'dart:convert';
void main() {
var student = {
'name': 'Sara',
'marks': [90, 85],
};
var encoder = JsonEncoder.withIndent(' ');
print(encoder.convert(student));
}{
"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:
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']}');
}Ali has 1500.0Encode 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:
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}');
}
}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
import 'dart:convert';
void main() {
print(jsonEncode({'name': 'Ali', 'age': 20}));
}{"name":"Ali","age":20}Example 2 — Encode a List
import 'dart:convert';
void main() {
print(jsonEncode(['Ali', 'Sara', 'Zayan']));
}["Ali","Sara","Zayan"]Example 3 — Decode and Read
import 'dart:convert';
void main() {
var data = jsonDecode('{"city":"Karachi","population":20}');
print(data['city']);
print(data['population']);
}Karachi
20Example 4 — null and bool
import 'dart:convert';
void main() {
var data = jsonDecode('{"active":true,"nickname":null}');
print(data['active']);
print(data['nickname'] ?? 'none');
}true
noneExample 5 — Round Trip
import 'dart:convert';
void main() {
var original = {
'score': 42,
'tags': ['quiz', 'easy'],
};
var copy = jsonDecode(jsonEncode(original));
print(copy['tags'][1]);
}easyExample 6 — Catching Bad JSON
import 'dart:convert';
void main() {
try {
jsonDecode('{"name": "Ali",}');
} on FormatException {
print('Invalid JSON');
}
}Invalid JSONA trailing comma after the last value is not allowed in JSON.
Example 7 — Student Marks
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');
}
}Ali: 240How it works
- The map pattern checks that
marksis a list. - Each item is
dynamic, soas intmakes the addition typed.
Example 8 — Shopping Total
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');
}Total: 1700How it works
- The array decodes to a list of maps.
- Each pattern pulls out typed
priceandqty;500 * 3 + 50 * 4is1700.
Example 9 — Bank Balance
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));
}{"owner":"Sara","balance":2250.5}How it works
2000decodes as anint, so it is read throughnumand made adouble.- The decoded map can be changed and encoded again.
Example 10 — Temperature Report
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));
}{
"city": "Lahore",
"average": 31.166666666666668
}How it works
- The readings mix
intanddouble, so each is cast tonum. - The result is a new map, pretty-printed as JSON.
Common Beginner Mistakes
1. Forgetting the import
void main() {
// print(jsonEncode({'a': 1})); // ✗ no import 'dart:convert';
}Error: Method not found: 'jsonEncode'.2. Single quotes inside JSON
// jsonDecode("{'name': 'Ali'}"); // ✗ JSON needs double quotesFormatException: Unexpected character (at character 2)Dart strings may use single quotes; the JSON inside them may not.
3. Expecting a typed map
// Map<String, int> marks = jsonDecode('{"math": 80}'); // ✗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
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
// jsonEncode({'date': DateTime(2026)}); // ✗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
jsonDecodeintrywithon FormatExceptionfor any outside text. - Validate the shape with a map pattern before using decoded data.
- Read numbers through
numwhen they might be eitherintordouble. - Keep
dynamicvalues short-lived: move them into typed variables quickly. - Use
JsonEncoder.withIndentfor files people read; plainjsonEncodefor 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.
Zayan's best: 9Try it yourself before opening the solution.
Show solution
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');
}
}Zayan's best: 9How it works
- The pattern confirms a
Stringname and aListof scores. s as intmakes each score typed; after the cast,best = sis allowed.
Practice Questions
Beginner
- Encode the map
{'city': 'Karachi', 'zip': 74000}and print the JSON. - Decode
{"fruit":"Mango","price":120}and printMango costs 120. - Decode
[10, 20, 30]and print the number of items.
Application
- Decode
{"name":"Ali","age":"twenty"}. Use a map pattern expectingint age; printValidorInvalid. - Decode
{"price": 99}and store the price in adouble. Print it. - Try to decode
{"a": 1and printBroken:followed by the exception's message. - Pretty-print
{'name': 'Sara', 'city': 'Lahore'}with a 4-space indent.
Challenge
- Decode
{"user":{"name":"Sara","contact":{"email":"sara@mail.com"}}}and print the email using one nested map pattern. - Decode
[{"item":"Bag","price":1500},{"item":"Cap"}]and print only items that have a price, asBag: 1500. - Write
{"temperature": 25}toweather.json, read it back, add1.5, and save it again. Print the final file contents.
Practice Answers
Question 1 — Solution
import 'dart:convert';
void main() {
print(jsonEncode({'city': 'Karachi', 'zip': 74000}));
}{"city":"Karachi","zip":74000}Question 2 — Solution
import 'dart:convert';
void main() {
var data = jsonDecode('{"fruit":"Mango","price":120}');
print('${data['fruit']} costs ${data['price']}');
}Mango costs 120Question 3 — Solution
import 'dart:convert';
void main() {
var list = jsonDecode('[10, 20, 30]') as List;
print(list.length);
}3Question 4 — Solution
import 'dart:convert';
void main() {
var data = jsonDecode('{"name":"Ali","age":"twenty"}');
if (data case {'name': String _, 'age': int _}) {
print('Valid');
} else {
print('Invalid');
}
}Invalid"twenty" is a String, so int _ fails.
Question 5 — Solution
import 'dart:convert';
void main() {
var data = jsonDecode('{"price": 99}');
double price = (data['price'] as num).toDouble();
print(price);
}99.0Question 6 — Solution
import 'dart:convert';
void main() {
try {
jsonDecode('{"a": 1');
} on FormatException catch (e) {
print('Broken: ${e.message}');
}
}Broken: Unexpected end of inputQuestion 7 — Solution
import 'dart:convert';
void main() {
var encoder = JsonEncoder.withIndent(' ');
print(encoder.convert({'name': 'Sara', 'city': 'Lahore'}));
}{
"name": "Sara",
"city": "Lahore"
}Question 8 — Solution
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);
}
}sara@mail.comQuestion 9 — Solution
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');
}
}
}Bag: 1500Question 10 — Solution
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());
}{"temperature":26.5}Quick Revision
- Which library holds
jsonEncodeandjsonDecode? - What Dart type does a JSON object become? A JSON array?
- Why does
jsonDecodereturndynamic? - Does
{"t": 30}give anintor adouble? - What does
jsonDecodethrow for invalid text? - Why is a map pattern safer than
data['a']['b']? - What does
JsonEncoder.withIndent(' ')change?
Key Takeaways
- JSON is plain text for data: objects
{}, arrays[], double-quoted strings. jsonEncodeturns maps and lists into JSON;jsonDecodeturns JSON into Dart values.- Objects become
Map<String, dynamic>, arraysList<dynamic>. 30decodes tointand30.5todouble; read numbers throughnumwhen unsure.dynamiclets missing keys crash at run time; map patterns check keys and types safely.- Invalid JSON throws a
FormatException; catch it for outside text. JsonEncoder.withIndentproduces 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.