serialize

Pubbuild status

Source-generated serialization for Dart objects. This package uses package:source_gen to eliminatethe time you spend writing boilerplate serialization code for your models.package:angel_serialize also powers package:angel_orm.

Usage

In your pubspec.yaml, you need to install the following dependencies:

  1. dependencies:
  2. angel_model: ^1.0.0
  3. angel_serialize: ^2.0.0
  4. dev_dependencies:
  5. angel_serialize_generator: ^2.0.0
  6. build_runner: ^1.0.0

With the recent updates to package:build_runner, you can build models automatically,anywhere in your project structure,by running pub run build_runner build.

To tweak this:https://pub.dartlang.org/packages/build_config

If you want to watch for file changes and re-build when necessary, replace the build callwith a call to watch. They take the same parameters.

Models

There are a few changes opposed to normal Model classes. You need to add a @serializable annotation to your modelclass to have it serialized, and a serializable model class's name should also startwith a leading underscore.

In addition, you may consider using an abstract class to ensure immutabilityof models.

Rather you writing the public class, angel_serialize does it for you. This means that the main class can haveits constructors automatically generated, in addition into serialization functions.

For example, say we have a Book model. Create a class named _Book:

  1. library angel_serialize.test.models.book;
  2.  
  3. import 'package:angel_model/angel_model.dart';
  4. import 'package:angel_serialize/angel_serialize.dart';
  5. import 'package:collection/collection.dart';
  6. part 'book.g.dart';
  7.  
  8. @serializable
  9. abstract class _Book extends Model {
  10. String get author;
  11.  
  12. @SerializableField(defaultValue: '[Untitled]')
  13. String get title;
  14.  
  15. String get description;
  16.  
  17. int get pageCount;
  18.  
  19. BookType get type;
  20. }
  21.  
  22. /// It even supports enums!
  23. enum BookType {
  24. fiction,
  25. nonFiction
  26. }

The following file will be generated:

  • book.g.dart

Producing these classes:

  • Book: Extends or implements _Book; may be const-enabled.
  • BookSerializer: static functionality for serializing Book models.
  • BookFields: The names of all fields from the Book model, statically-available.
  • BookEncoder: Allows BookSerializer to extend Codec<Book, Map>.
  • BookDecoder: Also allows BookSerializer to extend Codec<Book, Map>.

And the following other features:

  • bookSerializer: A top-level, const instance of BookSerializer.
  • Book.toString: Prints out all of a Book instance's fields.

Serialization

You can use the generated files as follows:

  1. myFunction() {
  2. var warAndPeace = new Book(
  3. author: 'Leo Tolstoy',
  4. title: 'War and Peace',
  5. description: 'You will cry after reading this.',
  6. pageCount: 1225
  7. );
  8.  
  9. // Easily serialize models into Maps
  10. var map = BookSerializer.toMap(warAndPeace);
  11.  
  12. // Also deserialize from Maps
  13. var book = BookSerializer.fromMap(map);
  14. print(book.title); // 'War and Peace'
  15.  
  16. // For compatibility with `JSON.encode`, a `toJson` method
  17. // is included that forwards to `BookSerializer.toMap`:
  18. expect(book.toJson(), map);
  19.  
  20. // Generated classes act as value types, and thus can be compared.
  21. expect(BookSerializer.fromMap(map), equals(warAndPeace));
  22. }

As of 2.0.2, the generated output also includes informationabout the serialized names of keys on your model class.

  1. myOtherFunction() {
  2. // Relying on the serialized key of a field? No worries.
  3. map[BookFields.author] = 'Zora Neale Hurston';
  4. }
  5. }

Customizing Serialization

Currently, these serialization methods are supported:

  • to Map
  • to JSON
  • to TypeScript definitions

You can customize these by means of serializers:

  1. @Serializable(serializers: const [Serializers.map, Serializers.json])class _MyClass extends Model {}

Subclasses

angel_serialize pulls in fields from parent classes, as well asimplemented interfaces, so it is extremely easy to share attributes amongmodel classes:

  1. import 'package:angel_serialize/angel_serialize.dart';
  2. part 'subclass.g.dart';
  3.  
  4. @serializable
  5. class _Animal {
  6. @notNull
  7. String genus;
  8. @notNull
  9. String species;
  10. }
  11.  
  12. @serializable
  13. class _Bird extends _Animal {
  14. @DefaultsTo(false)
  15. bool isSparrow;
  16. }
  17.  
  18. var saxaulSparrow = Bird(
  19. genus: 'Passer',
  20. species: 'ammodendri',
  21. isSparrow: true,
  22. );

Aliases

Whereas Dart fields conventionally are camelCased, most database columnstend to be snake_cased. This is not a problem, because we can define an aliasfor a field.

By default angel_serialize will transform keys into snake case. Use alias toprovide a custom name, or pass autoSnakeCaseNames: false to the builder;

  1. @serializableabstract class _Spy extends Model { /// Will show up as 'agency_id' in serialized JSON. /// /// When deserializing JSON, instead of searching for an 'agencyId' key, /// it will use 'agency_id'. /// /// Hooray! String agencyId;

  2. @SerializableField(alias: 'foo') String someOtherField;}

You can also override autoSnakeCaseNames per model:

  1. @Serializable(autoSnakeCaseNames: false)abstract class _OtherCasing extends Model { String camelCasedField;}

Excluding Keys

In pratice, there may keys that you want to exclude from JSON.To accomplish this, simply annotate them with @exclude:

  1. @serializableabstract class _Whisper extends Model { /// Will never be serialized to JSON @SerializableField(exclude: true) String secret;}

There are times, however, when you want to only exclude either serializationor deserialization, but not both. For example, you might want to deserializepasswords from a database without sending them to users as JSON.

In this case, use canSerialize or canDeserialize:

  1. @serializableabstract class _Whisper extends Model { /// Will never be serialized to JSON /// /// … But it can be deserialized @SerializableField(exclude: true, canDeserialize: true) String secret;}

Required Fields

It is easy to mark a field as required:

  1. @serializableabstract class _Foo extends Model { @SerializableField(isNullable: false) int myRequiredInt;

  2. @SerializableField(isNullable: false, errorMessage: 'Custom message') int myOtherRequiredInt;}

The given field will be marked as @required in thegenerated constructor, and serializers will check for itspresence, throwing a FormatException if it is missing.

Adding Annotations to Generated Classes

There are times when you need the generated class to have annotations affixed to it:

  1. @Serializable( includeAnnotations: [ Deprecated('blah blah blah'), pragma('something…'), ])abstract class _Foo extends Model {}

Custom Serializers

package:angel_serialize does not cover every known Dart data type; you can add support for your own.Provide serializer and deserializer arguments to @SerializableField() as you see fit.

They are typically used together. Note that the argument to deserializer will always bedynamic, while serializer can receive the data type in question.

In such a case, you might want to also provide a serializesTo argument.This lets the generator, as well as the ORM, apply the correct (de)serialization rulesand validations.

  1. DateTime _dateFromString(s) => s is String ? HttpDate.parse(s) : null;
  2. String _dateToString(DateTime v) => v == null ? null : HttpDate.format(v);
  3.  
  4. @serializable
  5. abstract class _HttpRequest {
  6. @SerializableField(
  7. serializer: #_dateToString,
  8. deserializer: #_dateFromString,
  9. serializesTo: String)
  10. DateTime date;
  11. }

Nesting

angel_serialize also supports a few types of nesting of @serializable classes:

  • As a class member, ex. Book myField
  • As the type argument to a List, ex. List<Book>
  • As the second type argument to a Map, ex. Map<String, Book>

In other words, the following are all legal, and will be serialized/deserialized.You can use either the underscored name of a child class (ex. _Book), or thegenerated class name (ex Book):

  1. @serializableabstract class _Author extends Model { List<Book> books; Book newestBook; Map<String, Book> booksByIsbn;}

If your model (Author) depends on a model defined in another file (Book),then you will need to generate book.g.dart before, author.g.dart,in a separate build action. This way, the analyzer can resolve the Book type.

ID and Dates

This package will automatically generate id, createdAt, and updatedAt fields for you,in the style of an Angel Model. This will automatically be generated, only for classesextending Model.

Binary Data

package:angel_serialize also handles Uint8List fields, by means of serialization toand from base64 encoding.

TypeScript Definitions

It is quite common to build frontends with JavaScript and/or TypeScript,so why not generate typings as well?

To accomplish this, add Serializers.typescript to your @Serializable() declaration:

  1. @Serializable(serializers: const [Serializers.map, Serializers.json, Serializers.typescript])class _Foo extends Model {}

The aforementioned _Author class will generate the following in author.d.ts:

  1. interface Author {
  2. id: string;
  3. name: string;
  4. age: number;
  5. books: Book[];
  6. newest_book: Book;
  7. created_at: any;
  8. updated_at: any;
  9. }
  10. interface Library {
  11. id: string;
  12. collection: BookCollection;
  13. created_at: any;
  14. updated_at: any;
  15. }
  16. interface BookCollection {
  17. [key: string]: Book;
  18. }

Fields with an @Exclude() that specifies canSerialize: false will not be present in theTypeScript definition. The rationale for this is that if a field (i.e. password) willnever be sent to the client, the client shouldn't even know the field exists.

Constructor Parameters

Sometimes, you may need to have custom constructor parameters, for example, whenusing depedency injection frameworks. For these cases, angel_serialize can forwardcustom constructor parameters.

The following:

  1. @serializableabstract class _Bookmark extends _BookmarkBase { @SerializableField(exclude: true) final Book book;

  2. int get page; String get comment;

  3. _Bookmark(this.book);}

Generates:

  1. class Bookmark extends _Bookmark {
  2. Bookmark(Book book,
  3. {this.id,
  4. this.page,
  5. this.comment,
  6. this.createdAt,
  7. this.updatedAt})
  8. : super(book);
  9.  
  10. @override
  11. final String id;
  12.  
  13. // ...
  14. }