built_value de Dart para serialización
La semana pasada introduje built_value para modelos de objetos inmutables. Vimos cómo definir modelos de objetos en built_value; son inmutables, fáciles…
La semana pasada introduje built_value para modelos de objetos inmutables. Vimos cómo definir modelos de objetos en built_value; son inmutables, fáciles de usar y, si te gusta ese tipo de cosas, muy divertidos.
Este artículo cubre el resto del paquete built_value. El elemento más grande es que, como puedes haber adivinado por el título, también son serializables.
Así es como se ve la serialización de built_value al usarla:
// Value type defined using built_value.
abstract class Login implements Built<Login, LoginBuilder> {
// Add serialization support by defining this static getter.
static Serializer<Login> get serializer => _$loginSerializer;
...
}
// Once per app, define a top level "Serializer" to gather together
// all the generated serializers.
Serializers serializers = _$serializers;
// Use it!
var login = new Login((b) => b
..username = 'johnsmith'
..password = '123456');
print(JSON.encode(serializers.serialize(login)));
-->
["Login", "username", "johnsmith", "password", "123456"]
¿Notas el “JSON.encode”? El serializador en realidad no llega tan lejos como para serializar a un String; más bien convierte a primitivos que la serialización JSON incorporada de Dart sabe manejar. Así que si quieres, puedes usar algo distinto a JSON.
Probablemente piensas en la serialización como algo que debería “simplemente funcionar”, pero hay algunas compensaciones sutiles involucradas. Profundicemos en la serialización de built_value.
Polimorfismo
#El aspecto más importante de la serialización de built_value es que soporta polimorfismo. Específicamente, puedes tener campos de tipos abstractos, y
cualquier implementación serializable de ese tipo abstracto puede ser serializada;
se escribirá suficiente información en el medio de transmisión para deserializar a los tipos correctos.
El ejemplo más simple es que puede serializar una lista de Object:
serializers.serialize(new BuiltList<Object>([1, 'two', 3]));
-->
['list', ['int', 1, 'string', 'two', 'int', 3]]
Información extra se añade en el medio de transmisión solo cuando sea necesario para desambiguar al deserializar. Así que si tienes un campo de tipo “BuiltList<int>”, se serializará como “[1, 2, 3]” y no como “[‘int’, 1, ‘int’, 2, ‘int’, 3]”.
Lo fundamental es que puedes definir tu modelo de objetos como quieras, y built_value lo serializará. Si quieres ver esto con más detalle, el test del map serializer explora todas las posibilidades.
Múltiples implementaciones
#Otro problema que todos los mecanismos de serialización deben enfrentar es de alguna manera definir el universo de tipos serializables. Aquí built_json hace algo un poco inusual al permitir múltiples implementaciones de un “tipo”.
Esto funciona porque los tipos se definen en el medio de transmisión por su nombre de clase únicamente. No se intenta desambiguar entre diferentes clases llamadas “Login”, por ejemplo; se asume que tanto el emisor como el receptor tienen un serializador compatible para una clase llamada “Login” disponible.
Esto añade flexibilidad útil. Si estás usando Dart en el servidor y en el cliente, por ejemplo, tienes opción para cada clase en el modelo de objetos:
Puedes usar la misma clase en el cliente y el servidor.
-
O puedes usar clases diferentes. Las implementaciones deben tener el mismo nombre y campos compatibles.
Por ejemplo, podrías tener una clase “Login” para el cliente que se encarga de la renderización y el parsing; y una clase “Login” separada para el servidor que se encarga de la autenticación y las bases de datos. Por supuesto, la implementación solo del servidor es libre de usar paquetes como “dart:io”, y la implementación solo del cliente paquetes como “dart:html”.
Múltiples lenguajes
#Como la serialización de built_value identifica los tipos únicamente por el nombre de la clase, los datos serializados se mapean bien a cualquier lenguaje orientado a objetos. El soporte para Java está planeado vía AutoValue.
Múltiples versiones
#Los datos serializados de built_value son compatibles hacia atrás/adelante de una forma muy simple: se basa en nombres de clase y nombres de campo. Los cambios de nombre de clase y los cambios de nombre de campos obligatorios son disruptivos.
Los campos que admiten null son más flexibles: al serializar, solo se escribirán si no son null; al deserializar, serán null por defecto si no se encuentran. Así que los campos que admiten null pueden ser añadidos, eliminados o renombrados y esto no es un cambio disruptivo.
Los campos no reconocidos simplemente se ignoran.
Sin mirrors
#Finalmente, y crucial para el rendimiento, built_value no usa mirrors de ninguna forma. Todo el análisis se hace en tiempo de codegen, dejándote con código de serialización mínimo y performante.
Esa fue la serialización con built_value. Puedes simplemente sentarte y escribir un modelo de objetos y de inmediato es serializable para usar en RPCs o para almacenamiento a largo plazo.
EnumClass
#Finalmente, built_value viene con una característica más: EnumClass. Los enums de Dart no son clases, pero un modelo de objetos poderoso necesita enums que se comporten como clases. El patrón obvio es crear una clase con campos “static const”, y EnumClass hace esto un poco más fácil. Proporciona:
Código generado para “values” y “valueOf”.
Serialización vía serializadores de built_value.
-
Un beneficio extra para usuarios de Angular o Angular2: el codegen puede producir opcionalmente un mixin para ayudarte a usar el enum desde las plantillas.
Todas estas características se pueden ver en el ejemplo.
¡Eso es todo por esta semana! Habiendo cubierto los conceptos básicos de built_value estoy listo para profundizar en el ejemplo de chat en detalle la próxima semana. ¡Estén atentos!
Edición: siguiente artículo.
Más de Dart
Historia del interop con JS en Dart
El soporte para Wasm acaba de llegar al beta actual de Flutter, gracias a un emocionante hito de interop con JavaScript alcanzado en Dart 3.3. Para…
Dart DevTools: Analizando el rendimiento de la aplicación con el CPU Profiler
Ya seas un desarrollador de backend escribiendo una herramienta de línea de comandos con Dart, o un ingeniero de UX construyendo aplicaciones con Flutter, el rendimiento del programa es crítico para el éxito de tu proyecto. Las herramientas de línea de comandos deberían minimizar la latencia y las apps deberían ser responsivas y ágiles sin fotogramas perdidos. Como desarrolladores, hacemos todo lo posible para escribir código performante pero a veces no está claro por qué nuestro código no funciona tan bien como esperaríamos.