100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Dart and JSON Serialization

How to encode and decode JSON in Dart, from manual fromJson/toJson methods to code generation with json_serializable.

Practical DartIntermediate9 min readJul 10, 2026
Analogies

Working with JSON in Dart

dart:convert provides jsonEncode() to turn Dart objects into a JSON string and jsonDecode() to parse a JSON string back into nested Map<String, dynamic> and List<dynamic> structures. Because the decoded data is dynamic, it must be explicitly cast or converted into typed Dart objects before use.

🏏

Cricket analogy: jsonDecode() turning a raw JSON string into a Map<String, dynamic> is like a scorer converting handwritten scoresheet notes into the structured digital scorecard format used by broadcasters.

Manual Serialization with fromJson/toJson

The idiomatic way to deserialize a class is a factory constructor named ClassName.fromJson(Map<String, dynamic> json), paired with a toJson() method for the reverse direction. Nested objects and lists must be recursively parsed, for example by calling a nested type's own fromJson constructor inside the parent's.

🏏

Cricket analogy: Writing a factory User.fromJson() constructor that also parses a nested Team object is like a scorecard app reading a match JSON and correctly nesting each player's stats under their respective team entry.

Code Generation with json_serializable

json_serializable, driven by the @JsonSerializable() annotation and run through build_runner, generates the fromJson and toJson boilerplate automatically into a companion .g.dart file, so developers only declare the model's fields and let the generator keep the parsing logic in sync with them.

🏏

Cricket analogy: Using @JsonSerializable to auto-generate parsing code is like a team relying on Hawk-Eye's automated ball-tracking instead of manually eyeballing whether a delivery was in line — the machine generates the precise result reliably.

Handling Nulls and Type Safety

Real-world JSON APIs frequently omit fields or return null unexpectedly, so robust Dart models use nullable types and the null-coalescing operator ?? to supply sensible defaults, and cast decoded values defensively, for example as int? rather than assuming a field is always present and correctly typed.

🏏

Cricket analogy: Providing a default value like runs ?? 0 when a JSON field is missing is like a scorecard defaulting an unavailable statistic to zero rather than crashing the whole scoreboard display.

dart
import 'dart:convert';

class Address {
  final String city;
  final String country;

  Address({required this.city, required this.country});

  factory Address.fromJson(Map<String, dynamic> json) {
    return Address(
      city: json['city'] as String? ?? 'Unknown',
      country: json['country'] as String? ?? 'Unknown',
    );
  }

  Map<String, dynamic> toJson() => {'city': city, 'country': country};
}

class User {
  final String name;
  final int age;
  final Address address;

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

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      age: json['age'] as int? ?? 0,
      address: Address.fromJson(json['address'] as Map<String, dynamic>),
    );
  }

  Map<String, dynamic> toJson() => {
        'name': name,
        'age': age,
        'address': address.toJson(),
      };
}

void main() {
  const raw = '{"name":"Asha","age":29,"address":{"city":"Pune","country":"India"}}';
  final user = User.fromJson(jsonDecode(raw) as Map<String, dynamic>);
  print(jsonEncode(user.toJson()));
}

jsonDecode() returns dynamic data, typically Map<String, dynamic>, so mistyped keys like json['naem'] or wrong-type casts like json['age'] as String won't be caught by the compiler — they'll throw at runtime instead. Always write defensive fromJson constructors, and consider json_serializable so a single source of truth, your model class, drives both encoding and decoding consistently.

  • dart:convert provides jsonEncode() and jsonDecode() to convert between JSON strings and Dart Map/List structures.
  • jsonDecode() returns dynamic data, typically Map<String, dynamic>, so type casting mistakes only surface as runtime errors.
  • Manual fromJson factory constructors and toJson methods give full control over field mapping and nested object or list parsing.
  • json_serializable with build_runner generates .g.dart files, eliminating repetitive and error-prone manual boilerplate.
  • The @JsonSerializable() annotation marks a class for code generation; running build_runner regenerates code whenever the model changes.
  • Nullable fields and null-coalescing (??) should be used to handle optional or missing JSON keys gracefully.
  • Consistent serialization logic between fromJson and toJson prevents subtle round-trip bugs when re-encoding parsed data.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#DartStudyNotes#DartAndJSONSerialization#Dart#JSON#Serialization#Manual#StudyNotes#SkillVeris#ExamPrep