Sérialisation vers JSON et un objet immuable. À propos du package built_value pour Flutter





Parfois, le JSON d'une API doit être converti en objet et de préférence en une valeur immuable. Sur Dart, c'est possible, mais cela nécessite beaucoup de codage pour chacun des objets. Heureusement, il existe un package qui vous aidera à faire tout cela, et dans cet article, je vais vous parler de cette méthode.



Notre objectif:



1. Sérialisation



final user = User.fromJson({"name": "Maks"});
final json = user.toJson();


2. Utiliser comme valeurs



final user1 = User.fromJson({"name": "Maks"});
final user2 = User((b) => b..name='Maks');
if (user1 == user2) print('    ');


3. Immuabilité



user.name = 'Alex'; // 
final newUser = user.rebuild((b) => b..name='Alex'); // 




Installer des packages



Ouvrez le fichier pubspec.yaml dans notre projet Flutter et ajoutez le package built_value aux dépendances :



  ...
  built_value: ^7.1.0


Et ajoutez également les packages built_value_generator et build_runner à dev_dependencies . Ces packages vous aideront à générer les codes requis.



dev_dependencies :



 ...
  build_runner: ^1.10.2
  built_value_generator: ^7.1.0


Enregistrez le fichier pubspec.yaml et exécutez « flutter pub get » pour obtenir tous les packages requis.



Créer built_value



Créons une classe simple pour voir comment cela fonctionne.



Créez un nouveau fichier user.dart :



import 'package:built_value/built_value.dart';

part 'user.g.dart';

abstract class User implements Built<User, UserBuilder> {
  String get name;

  User._();
  factory User([void Function(UserBuilder) updates]) = _$User;
}


Ainsi, nous avons créé une classe utilisateur abstraite simple avec un champ de nom , indiqué que notre classe fait partie de user.g.dart et que l'implémentation principale est là, y compris UserBuilder . Pour créer automatiquement ce fichier, vous devez l'exécuter sur la ligne de commande:



flutter packages pub run build_runner watch


ou



flutter packages pub run build_runner build


On commence par watch pour ne pas redémarrer à chaque fois que quelque chose change dans la classe.



Après cela, nous voyons qu'un nouveau fichier user.g.dart est apparu . Vous pouvez voir ce qu'il y a à l'intérieur et voir combien de temps nous gagnerons en automatisant ce processus. Lorsque nous ajouterons plus de champs et de sérialisation, ce fichier deviendra encore plus volumineux.



Vérifions ce que nous avons:



final user = User((b) => b..name = "Max");
print(user);
print(user == User((b) => b..name = "Max")); // true
print(user == User((b) => b..name = "Alex")); // false


nullable



Ajoutez un nouveau champ de nom à la classe User :



abstract class User implements Built<User, UserBuilder> {
  String get name;
  String get surname;
...
}


Si vous essayez comme ceci:



final user = User((b) => b..name = 'Max');


Ensuite, nous obtenons une erreur:



Tried to construct class "User" with null field "surname".


Pour rendre le nom de famille facultatif, utiliseznullable:



@nullable
String get surname;


ou vous devez donner le nom de famille à chaque fois :



final user = User((b) => b
  ..name = 'Max'
  ..surname = 'Madov');
print(user);


Collection construite



Utilisons des tableaux. BuiltList nous aidera pour cela :



import 'package:built_collection/built_collection.dart';
...
abstract class User implements Built<User, UserBuilder> {
  ...
  @nullable
  BuiltList<String> get rights;
...


final user = User((b) => b
  ..name = 'Max'
  ..rights.addAll(['read', 'write']));
print(user);


Enum



Il est nécessaire de restreindre les droits afin qu'il ne prenne aucune autre valeur que « read », « write » et « delete ». Pour ce faire, créez un nouveau fichier nommé right.dart créez une nouvelle EnumClass :



import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';

part 'right.g.dart';

class Right extends EnumClass {
  static const Right read = _$read;
  static const Right write = _$write;
  static const Right delete = _$delete;

  const Right._(String name) : super(name);

  static BuiltSet<Right> get values => _$rightValues;
  static Right valueOf(String name) => _$rightValueOf(name);
}


Utilisateur:



@nullable
BuiltList<Right> get rights;


Désormais, les droits n'acceptent que le bon type :



final user = User((b) => b
  ..name = 'Max'
  ..rights.addAll([Right.read, Right.write]));
print(user);


Sérialisation



Pour que ces objets puissent être facilement convertis en JSON et inversement , nous devons ajouter quelques méthodes supplémentaires à nos classes:



...
import 'package:built_value/serializer.dart';
import 'serializers.dart';
...
abstract class User implements Built<User, UserBuilder> {
...
  Map<String, dynamic> toJson() => serializers.serializeWith(User.serializer, this);
  static User fromJson(Map<String, dynamic> json) =>
serializers.deserializeWith(User.serializer, json);

  static Serializer<User> get serializer => _$userSerializer;
}


En principe, cela suffit pour la sérialisation:



static Serializer<User> get serializer => _$userSerializer;


Mais pour plus de commodité, ajoutons les méthodes toJson et fromJson .



Nous ajoutons également une ligne à la classe Right:



import 'package:built_value/serializer.dart';
,,,
class Right extends EnumClass {
...
  static Serializer<Right> get serializer => _$rightSerializer;
}


Et vous devez créer un autre fichier appelé serializers.dart :



import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import 'package:built_value_demo/right.dart';
import 'package:built_value_demo/user.dart';

part 'serializers.g.dart';

@SerializersFor([Right, User])
final Serializers serializers =
(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();


Chaque nouvelle classe Built doit être ajoutée à @SerializersFor ([ ... ]) pour que la sérialisation fonctionne comme prévu .



Maintenant, nous pouvons vérifier ce que nous avons:



final user = User.fromJson({
  "name": "Max",
  "rights": ["read", "write"]
});
print(user);
print(user.toJson());


final user2 = User((b) => b
  ..name = 'Max'
  ..rights.addAll([Right.read, Right.write]));
print(user == user2); // true


Changeons les valeurs:



final user3 = user.rebuild((b) => b
  ..surname = "Madov"
  ..rights.replace([Right.read]));
print(user3);


aditionellement



Du coup, il y aura ceux qui diront qu'il faut encore beaucoup écrire. Mais si vous utilisez Visual Studio Code, je vous recommande d'installer un extrait de code appelé Built Value Snippets , puis vous pouvez générer tout cela automatiquement. Pour ce faire, recherchez le Marketplace ou suivez ce lien .



Après l'installation, écrivez « bv » dans le fichier Dart et vous pourrez voir quelles options existent.



Si vous ne souhaitez pas que Visual Studio Code affiche les fichiers " * .g.dart " générés , vous devez ouvrir Paramètres et rechercher Fichiers: Exclure , puis cliquer sur Ajouter un modèle et ajouter "** / *. g.dart ».



Et après?



À première vue, il peut sembler que tant d'efforts n'en valent pas la peine, mais si vous avez beaucoup de ces classes, cela facilitera et accélérera grandement l'ensemble du processus.



PS Je vous serais très heureux et reconnaissant de bien vouloir partager vos méthodes, que vous trouvez plus pratiques et plus efficaces que celle que j'ai proposée. Paquets de



projet GitHub



:

pub.dev/packages/built_value

pub.dev/packages/built_value_generator

pub.dev/packages/build_runner



All Articles