Serialización a JSON y un objeto inmutable. Acerca del paquete built_value para Flutter





A veces, JSON de una API debe convertirse en un objeto y preferiblemente en un valor inmutable. En Dart es posible, pero requiere mucha codificación para cada uno de los objetos. Afortunadamente, existe un paquete que te ayudará a hacer todo esto, y en este artículo te contaré sobre este método.



Nuestro objetivo:



1. Serialización



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


2. Usar como valores



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


3. Inmutabilidad



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




Instalar paquetes



Abra el archivo pubspec.yaml en nuestro proyecto Flutter y agregue el paquete built_value a las dependencias :



  ...
  built_value: ^7.1.0


Y también agregue los paquetes built_value_generator y build_runner a dev_dependencies . Estos paquetes le ayudarán a generar los códigos necesarios.



dev_dependencies :



 ...
  build_runner: ^1.10.2
  built_value_generator: ^7.1.0


Guarde el archivo pubspec.yaml y ejecute " flutter pub get " para obtener todos los paquetes necesarios.



Crear valor_construido



Creemos una clase simple para ver cómo funciona.



Cree un nuevo archivo 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;
}


Entonces, creamos una clase de usuario abstracta simple con un campo de nombre , indicando que nuestra clase es parte de user.g.dart y la implementación principal está allí, incluido UserBuilder . Para crear este archivo automáticamente, debe ejecutarlo en la línea de comando:



flutter packages pub run build_runner watch


o



flutter packages pub run build_runner build


Comenzamos con watch para no reiniciar cada vez que algo cambia en la clase.



Después de eso, vemos que ha aparecido un nuevo archivo user.g.dart . Puede ver lo que hay dentro y ver cuánto tiempo ahorraremos automatizando este proceso. Cuando agreguemos más campos y serialización, este archivo se hará aún más grande.



Veamos lo que tenemos:



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


anulable



Agregue un nuevo campo de apellido a la clase de usuario :



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


Si lo intentas así:



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


Entonces obtenemos un error:



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


Para que el apellido sea opcional, utiliceanulable:



@nullable
String get surname;


o necesita dar apellido cada vez :



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


Colección construida



Usemos matrices. BuiltList nos ayudará en esto :



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



Es necesario restringir los derechos para que no tome ningún otro valor que ' leer ', ' escribir ' y ' eliminar '. Para hacer esto, cree un nuevo archivo llamado right.dart cree una nueva 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);
}


Usuario:



@nullable
BuiltList<Right> get rights;


Ahora los derechos solo aceptan el tipo correcto :



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


Publicación por entregas



Para que estos objetos se puedan convertir fácilmente a JSON y viceversa, necesitamos agregar un par de métodos más a nuestras clases:



...
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 principio, esto es suficiente para la serialización:



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


Pero por conveniencia, agreguemos los métodos toJson y fromJson .



También agregamos una línea a la clase Derecha:



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


Y necesita crear otro archivo llamado 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();


Cada nueva clase construida debe agregarse a @SerializersFor ([ ... ]) para que la serialización funcione como se esperaba .



Ahora podemos comprobar lo que obtuvimos:



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


Cambiemos los valores:



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


Adicionalmente



Como resultado, habrá quienes dirán que todavía es necesario escribir bastante. Pero si usa Visual Studio Code, le recomiendo instalar un fragmento llamado Built Value Snippets y luego puede generar todo esto automáticamente. Para hacer esto, busque en Marketplace o siga este enlace .



Después de la instalación, escriba " bv " en el archivo Dart y podrá ver qué opciones existen.



Si no desea que Visual Studio Code muestre los archivos " * .g.dart " generados , debe abrir Configuración y buscar Archivos: Excluir , luego hacer clic en Agregar patrón y agregar "** / *. g.dart ”.



¿Que sigue?



A primera vista, puede parecer que tanto esfuerzo no vale la pena, pero si tiene muchas de esas clases, esto facilitará y acelerará enormemente todo el proceso.



PD: Le agradecería y le agradecería mucho que compartiera sus métodos, que le parecen más prácticos y eficaces que el propuesto por mí. Paquetes de



proyectos de GitHub



:

pub.dev/packages/built_value

pub.dev/packages/built_value_generator

pub.dev/packages/build_runner



All Articles