flutter-implement-json-serialization

Créer des classes de modèle avec les méthodes `fromJson` et `toJson` en utilisant `dart:convert`. À utiliser lors du mappage manuel des clés JSON aux propriétés de classe pour les structures de données simples.

npx skills add https://github.com/flutter/skills --skill flutter-implement-json-serialization

Sérialisation manuelle de JSON dans Flutter

Sommaire

Directives fondamentales

  • Importer dart:convert : Utiliser la bibliothèque intégrée de Flutter dart:convert pour l'encodage (jsonEncode) et le décodage (jsonDecode) manuels de JSON.
  • Appliquer la sécurité des types : Toujours caster le résultat dynamic de jsonDecode() au type attendu, généralement Map<String, dynamic> pour les objets ou List<dynamic> pour les tableaux.
  • Encapsuler la logique de sérialisation : Définir des classes de modèle simples contenant des propriétés correspondant à la structure JSON. Implémenter un constructeur de fabrique fromJson et une méthode toJson au sein du modèle.
  • Gérer l'analyse en arrière-plan : Si l'analyse de grands documents JSON (temps d'exécution > 16 ms), déléguer la logique d'analyse à un isolat séparé en utilisant la fonction compute() de Flutter pour éviter les ralentissements de l'interface utilisateur.
  • Lancer des exceptions en cas d'échec : Lors du traitement des réponses HTTP, lancer une exception si le code de statut n'est pas réussi (par exemple, pas 200 OK ou 201 Created). Ne pas retourner null.

Flux de travail : Implémenter un modèle sérialisable

Utiliser cette liste de contrôle pour implémenter la sérialisation manuelle de JSON pour un modèle de données.

Progression des tâches :

  • [ ] Définir la classe de modèle simple avec des propriétés final.
  • [ ] Implémenter le constructeur factory Model.fromJson(Map<String, dynamic> json).
  • [ ] Implémenter la méthode Map<String, dynamic> toJson().
  • [ ] Écrire des tests unitaires pour les deux méthodes de sérialisation.
  • [ ] Exécuter le validateur -> examiner les erreurs de non-concordance de types -> corriger la logique de casting.
  1. Définir le modèle : Créer une classe avec des propriétés correspondant aux clés JSON.
  2. Implémenter fromJson : Extraire les valeurs de la Map et les caster aux types Dart appropriés. Utiliser la correspondance de motifs ou le casting explicite.
  3. Implémenter toJson : Retourner une Map<String, dynamic> mappant les propriétés de la classe à leurs clés de chaîne JSON.
  4. Valider : Exécuter des tests unitaires pour garantir que la sécurité des types, l'autocomplétion et la gestion des exceptions au moment de la compilation fonctionnent correctement.

Flux de travail : Récupérer et analyser JSON

Utiliser ce flux de travail conditionnel lors de la récupération et de l'analyse de JSON à partir d'une requête réseau.

Progression des tâches :

  • [ ] Exécuter la requête HTTP.
  • [ ] Valider le code de statut de la réponse.
  • [ ] Déterminer la stratégie d'analyse (Synchrone ou Isolat).
  • [ ] Décoder et mapper le JSON au modèle.
  1. Exécuter la requête : Utiliser le package http pour effectuer l'appel réseau.
  2. Valider la réponse :
    • Si response.statusCode == 200 (ou 201 pour POST), procéder à l'analyse.
    • Si le code de statut indique un échec, lancer une Exception.
  3. Déterminer la stratégie d'analyse :
    • Si l'analyse d'une petite charge utile (par exemple, un seul objet), analyser de manière synchrone sur le thread principal.
    • Si l'analyse d'une grande charge utile (par exemple, un tableau de milliers d'objets), utiliser compute(parseFunction, response.body) pour analyser dans un isolat en arrière-plan.
  4. Décoder et mapper : Passer le JSON décodé au constructeur fromJson de votre modèle.

Exemples

Implémentation du modèle haute fidélité

import 'dart:convert';

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

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

  // Factory constructor for deserialization
  factory User.fromJson(Map<String, dynamic> json) {
    return switch (json) {
      {
        'id': int id,
        'name': String name,
        'email': String email,
      } => 
        User(
          id: id,
          name: name,
          email: email,
        ),
      _ => throw const FormatException('Failed to load User.'),
    };
  }

  // Method for serialization
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
    };
  }
}

Analyse synchrone (petite charge utile)

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

Future<User> fetchUser(http.Client client, int userId) async {
  final response = await client.get(
    Uri.parse('https://api.example.com/users/$userId'),
    headers: {'Accept': 'application/json'},
  );

  if (response.statusCode == 200) {
    // Decode returns dynamic, cast to Map<String, dynamic>
    final Map<String, dynamic> jsonMap = jsonDecode(response.body) as Map<String, dynamic>;
    return User.fromJson(jsonMap);
  } else {
    throw Exception('Failed to load user');
  }
}

Analyse en arrière-plan (grande charge utile)

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

// Top-level function required for compute()
List<User> parseUsers(String responseBody) {
  final parsed = (jsonDecode(responseBody) as List<dynamic>).cast<Map<String, dynamic>>();
  return parsed.map<User>((json) => User.fromJson(json)).toList();
}

Future<List<User>> fetchUsers(http.Client client) async {
  final response = await client.get(
    Uri.parse('https://api.example.com/users'),
    headers: {'Accept': 'application/json'},
  );

  if (response.statusCode == 200) {
    // Offload expensive parsing to a background isolate
    return compute(parseUsers, response.body);
  } else {
    throw Exception('Failed to load users');
  }
}