Question #84MediumFlutter Basics

How json serialisation work in flutter ?

#flutter#json

Answer

JSON Serialization in Flutter

JSON serialization is the process of converting Dart objects to JSON format (serialization) and JSON data back to Dart objects (deserialization). This is essential for working with REST APIs, local storage, and data persistence in Flutter applications.

Two Main Approaches

ApproachMethodBest For
Manual SerializationWrite
text
toJson()
and
text
fromJson()
methods
Small projects, simple models
Code GenerationUse
text
json_serializable
package
Large projects, complex models

1. Manual Serialization

Write serialization logic manually for each model class.

Basic Example

dart
class User {
  final int id;
  final String name;
  final String email;
  final bool isActive;

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

  // Serialization: Dart object → JSON
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
      'is_active': isActive,
    };
  }

  // Deserialization: JSON → Dart object
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
      isActive: json['is_active'] as bool,
    );
  }
}

// Usage
void main() {
  // Deserialization example
  String jsonString = '{"id": 1, "name": "John", "email": "john@example.com", "is_active": true}';
  Map<String, dynamic> jsonMap = jsonDecode(jsonString);
  User user = User.fromJson(jsonMap);
  print(user.name); // John

  // Serialization example
  User newUser = User(
    id: 2,
    name: 'Jane',
    email: 'jane@example.com',
    isActive: false,
  );
  String encoded = jsonEncode(newUser.toJson());
  print(encoded); // {"id":2,"name":"Jane","email":"jane@example.com","is_active":false}
}

Handling Nested Objects

dart
class Address {
  final String street;
  final String city;
  final String country;

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

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

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

class UserWithAddress {
  final int id;
  final String name;
  final Address address; // Nested object

  UserWithAddress({
    required this.id,
    required this.name,
    required this.address,
  });

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'address': address.toJson(), // Serialize nested object
    };
  }

  factory UserWithAddress.fromJson(Map<String, dynamic> json) {
    return UserWithAddress(
      id: json['id'] as int,
      name: json['name'] as String,
      address: Address.fromJson(json['address'] as Map<String, dynamic>), // Deserialize nested
    );
  }
}

Handling Lists

dart
class Post {
  final int id;
  final String title;
  final List<String> tags;

  Post({
    required this.id,
    required this.title,
    required this.tags,
  });

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'title': title,
      'tags': tags, // List serializes automatically
    };
  }

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'] as int,
      title: json['title'] as String,
      tags: List<String>.from(json['tags'] as List), // Convert to List<String>
    );
  }
}

// Handling list of objects
class BlogResponse {
  final List<Post> posts;

  BlogResponse({required this.posts});

  Map<String, dynamic> toJson() {
    return {
      'posts': posts.map((post) => post.toJson()).toList(),
    };
  }

  factory BlogResponse.fromJson(Map<String, dynamic> json) {
    return BlogResponse(
      posts: (json['posts'] as List)
          .map((postJson) => Post.fromJson(postJson as Map<String, dynamic>))
          .toList(),
    );
  }
}

2. Code Generation with json_serializable

Automatically generate serialization code using the

text
json_serializable
package.

Setup

yaml
# pubspec.yaml
dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  build_runner: ^2.4.6
  json_serializable: ^6.7.1

Model Class with Annotations

dart
import 'package:json_annotation/json_annotation.dart';

// This is required for code generation
part 'user.g.dart';

()
class User {
  final int id;
  final String name;
  final String email;

  (name: 'is_active') // Map JSON key to Dart field
  final bool isActive;

  (defaultValue: 0) // Default value if missing
  final int age;

  (ignore: true) // Don't serialize this field
  String? tempPassword;

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

  // Generated methods
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

Generate Code

bash
# Generate serialization code
flutter pub run build_runner build

# Watch for changes and regenerate automatically
flutter pub run build_runner watch

# Delete conflicting outputs and rebuild
flutter pub run build_runner build --delete-conflicting-outputs

Generated Code (user.g.dart)

dart
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'user.dart';

User _$UserFromJson(Map<String, dynamic> json) {
  return User(
    id: json['id'] as int,
    name: json['name'] as String,
    email: json['email'] as String,
    isActive: json['is_active'] as bool,
    age: json['age'] as int? ?? 0,
  );
}

Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
      'id': instance.id,
      'name': instance.name,
      'email': instance.email,
      'is_active': instance.isActive,
      'age': instance.age,
    };

Advanced Features

Custom Converters

dart
// Custom DateTime converter
class DateTimeConverter implements JsonConverter<DateTime, String> {
  const DateTimeConverter();

  
  DateTime fromJson(String json) => DateTime.parse(json);

  
  String toJson(DateTime object) => object.toIso8601String();
}

()
class Event {
  final String name;

  ()
  final DateTime date;

  Event({required this.name, required this.date});

  factory Event.fromJson(Map<String, dynamic> json) => _$EventFromJson(json);
  Map<String, dynamic> toJson() => _$EventToJson(this);
}

Nullable Fields

dart
()
class Product {
  final int id;
  final String name;
  final String? description; // Nullable field
  final double? discount; // Nullable field

  Product({
    required this.id,
    required this.name,
    this.description,
    this.discount,
  });

  factory Product.fromJson(Map<String, dynamic> json) => _$ProductFromJson(json);
  Map<String, dynamic> toJson() => _$ProductToJson(this);
}

Generic Types

dart
(genericArgumentFactories: true)
class ApiResponse<T> {
  final bool success;
  final T data;
  final String? error;

  ApiResponse({
    required this.success,
    required this.data,
    this.error,
  });

  factory ApiResponse.fromJson(
    Map<String, dynamic> json,
    T Function(Object? json) fromJsonT,
  ) =>
      _$ApiResponseFromJson(json, fromJsonT);

  Map<String, dynamic> toJson(Object Function(T value) toJsonT) =>
      _$ApiResponseToJson(this, toJsonT);
}

// Usage
ApiResponse<User> response = ApiResponse.fromJson(
  jsonData,
  (json) => User.fromJson(json as Map<String, dynamic>),
);

Real-World API Integration

dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class ApiService {
  static const String baseUrl = 'https://api.example.com

  // Fetch list of users
  Future<List<User>> fetchUsers() async {
    final response = await http.get(Uri.parse('$baseUrl/users'));

    if (response.statusCode == 200) {
      final List<dynamic> jsonList = jsonDecode(response.body);
      return jsonList
          .map((json) => User.fromJson(json as Map<String, dynamic>))
          .toList();
    } else {
      throw Exception('Failed to load users');
    }
  }

  // Create new user
  Future<User> createUser(User user) async {
    final response = await http.post(
      Uri.parse('$baseUrl/users'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(user.toJson()),
    );

    if (response.statusCode == 201) {
      return User.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to create user');
    }
  }

  // Update user
  Future<User> updateUser(int id, User user) async {
    final response = await http.put(
      Uri.parse('$baseUrl/users/$id'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(user.toJson()),
    );

    if (response.statusCode == 200) {
      return User.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to update user');
    }
  }
}

Error Handling

dart
User? parseUser(String jsonString) {
  try {
    final json = jsonDecode(jsonString);
    return User.fromJson(json);
  } on FormatException catch (e) {
    print('Invalid JSON format: $e');
    return null;
  } on TypeError catch (e) {
    print('Type mismatch: $e');
    return null;
  } catch (e) {
    print('Unknown error: $e');
    return null;
  }
}

Best Practices

  1. Use Code Generation for Large Projects: Reduces boilerplate and errors
  2. Handle Null Safety Properly: Use nullable types and default values
  3. Validate Data: Check for null and invalid values before deserialization
  4. Use Consistent Naming: Match JSON keys with Dart field names or use
    text
    @JsonKey
  5. Test Serialization: Write unit tests for toJson/fromJson methods
  6. Handle Dates Properly: Use custom converters for DateTime
  7. Separate Models from UI: Keep data models in separate files

Comparison Table

FeatureManual SerializationCode Generation
Setup ComplexityLowMedium
Code MaintainabilityLow (repetitive)High (automated)
Compile TimeFastSlower (generation step)
Type SafetyManualAutomatic
Best ForSmall projectsLarge projects
Learning CurveEasyModerate

Popular JSON Packages

  • json_serializable: Most popular code generation tool
  • freezed: Immutable data classes with JSON serialization
  • built_value: Alternative code generation with builders
yaml
dependencies:
  freezed_annotation: ^2.4.1

dev_dependencies:
  freezed: ^2.4.5
  build_runner: ^2.4.6

Important: Choose manual serialization for simple apps and code generation for production apps with complex models. Always handle errors and validate JSON data.

Documentation: Flutter JSON and Serialization