Skip to content
AgileLogics
6 min read

Working with JSON

Prerequisites: Constructors, Maps

JSON is the most common format for sending data between programs. Dart's built-in dart:convert library converts between JSON text and Dart maps and lists.

Encoding and decoding

import 'dart:convert';

void main() {
  final user = {'name': 'Ada', 'age': 36, 'tags': ['admin'], 'manager': null};
  final text = jsonEncode(user);
  print(text);

  final decoded = jsonDecode('{"name": "Linus", "age": 28}') as Map<String, dynamic>;
  print(decoded['name']);
  print(decoded['age'] + 1);
}
{"name":"Ada","age":36,"tags":["admin"],"manager":null}
Linus
29

jsonDecode returns dynamic data — maps, lists, strings, numbers, booleans, and null. Working with raw maps loses type safety, so real apps convert them into classes.

fromJson and toJson

import 'dart:convert';

class User {
  final String name;
  final int age;
  final String? email;

  User({required this.name, required this.age, this.email});

  factory User.fromJson(Map<String, dynamic> json) => User(
        name: json['name'] as String,
        age: json['age'] as int,
        email: json['email'] as String?,
      );

  Map<String, dynamic> toJson() => {
        'name': name,
        'age': age,
        if (email != null) 'email': email,
      };
}

void main() {
  final user = User.fromJson(jsonDecode('{"name":"Ada","age":36}'));
  print('${user.name} is ${user.age}, email: ${user.email ?? 'none'}');
  print(jsonEncode(user.toJson()));

  final list = (jsonDecode('[{"name":"A","age":1},{"name":"B","age":2}]') as List)
      .map((e) => User.fromJson(e as Map<String, dynamic>))
      .toList();
  print(list.length);
}
Ada is 36, email: none
{"name":"Ada","age":36}
2

jsonEncode calls toJson() automatically if you pass the object itself: jsonEncode(user) gives the same result.

Pattern matching for validation

Patterns (from the earlier lesson) check the shape and types in one go:

import 'dart:convert';

void main() {
  final data = jsonDecode('{"name": "Ada", "age": "thirty-six"}');

  if (data case {'name': String name, 'age': int age}) {
    print('$name is $age');
  } else {
    print('Invalid user data');
  }
}
Invalid user data

Code generation

For larger apps, packages such as json_serializable or freezed generate fromJson/toJson code from annotations, removing the hand-written mapping. Paste a sample API response into the JSON Formatter to see its structure before writing the matching classes.

Common mistakes

  • Casting too trustingly: json['age'] as int throws if the API sends "36" as a string. Validate data from outside your app.
  • Numbers: JSON has one number type; a value like 3 decodes as an int, so json['price'] as double can fail. Use (json['price'] as num).toDouble().