platform/README.md

390 lines
10 KiB
Markdown
Raw Normal View History

2017-06-16 19:47:13 +00:00
# serialize
2018-12-08 20:53:49 +00:00
2017-06-17 01:56:38 +00:00
[![Pub](https://img.shields.io/pub/v/angel_serialize.svg)](https://pub.dartlang.org/packages/angel_serialize)
[![build status](https://travis-ci.org/angel-dart/serialize.svg)](https://travis-ci.org/angel-dart/serialize)
2017-07-10 15:15:09 +00:00
Source-generated serialization for Dart objects. This package uses `package:source_gen` to eliminate
2017-06-17 01:56:38 +00:00
the time you spend writing boilerplate serialization code for your models.
2017-07-10 15:15:09 +00:00
`package:angel_serialize` also powers `package:angel_orm`.
2017-06-17 01:56:38 +00:00
2018-12-08 20:53:49 +00:00
- [Usage](#usage)
- [Models](#models)
- [Field Aliases](#aliases)
- [Excluding Keys](#excluding-keys)
- [Required Fields](#required-fields)
2019-01-07 00:56:05 +00:00
- [Adding Annotations to Generated Classes](#adding-annotations-to-generated-classes)
2019-01-07 01:38:04 +00:00
- [Custom Serializers](#custom-serializers)
2018-12-08 20:53:49 +00:00
- [Serialization](#serializaition)
- [Nesting](#nesting)
- [ID and Date Fields](#id-and-dates)
- [Binary Data](#binary-data)
- [TypeScript Definition Generator](#typescript-definitions)
- [Constructor Parameters](#constructor-parameters)
2017-06-17 01:56:38 +00:00
# Usage
2018-12-08 20:53:49 +00:00
2017-07-10 15:15:09 +00:00
In your `pubspec.yaml`, you need to install the following dependencies:
2018-12-08 20:53:49 +00:00
2017-06-17 01:56:38 +00:00
```yaml
dependencies:
2018-12-08 20:53:49 +00:00
angel_model: ^1.0.0
2018-03-02 21:23:00 +00:00
angel_serialize: ^2.0.0
2017-06-17 01:56:38 +00:00
dev_dependencies:
2018-03-02 21:23:00 +00:00
angel_serialize_generator: ^2.0.0
2018-12-08 20:53:49 +00:00
build_runner: ^1.0.0
2017-06-17 01:56:38 +00:00
```
2018-03-02 21:23:00 +00:00
With the recent updates to `package:build_runner`, you can build models in
`lib/src/models/**.dart` automatically by running `pub run build_runner build`.
2017-06-17 01:56:38 +00:00
2018-03-02 21:23:00 +00:00
To tweak this:
https://pub.dartlang.org/packages/build_config
2017-06-17 01:56:38 +00:00
If you want to watch for file changes and re-build when necessary, replace the `build` call
with a call to `watch`. They take the same parameters.
# Models
2018-12-08 20:53:49 +00:00
2017-06-17 01:56:38 +00:00
There are a few changes opposed to normal Model classes. You need to add a `@serializable` annotation to your model
class to have it serialized, and a serializable model class's name should also start
2018-05-13 16:53:43 +00:00
with a leading underscore.
In addition, you may consider using an `abstract` class to ensure immutability
2018-05-13 18:02:47 +00:00
of models.
2017-06-17 01:56:38 +00:00
Rather you writing the public class, `angel_serialize` does it for you. This means that the main class can have
its constructors automatically generated, in addition into serialization functions.
For example, say we have a `Book` model. Create a class named `_Book`:
```dart
library angel_serialize.test.models.book;
2018-06-21 01:40:03 +00:00
import 'package:angel_model/angel_model.dart';
2017-06-17 01:56:38 +00:00
import 'package:angel_serialize/angel_serialize.dart';
2018-05-15 19:01:13 +00:00
import 'package:collection/collection.dart';
2017-06-17 01:56:38 +00:00
part 'book.g.dart';
@serializable
abstract class _Book extends Model {
2018-05-13 16:53:43 +00:00
String get author;
2018-12-08 20:53:49 +00:00
2019-01-07 00:56:05 +00:00
@SerializableField(defaultValue: '[Untitled]')
2018-05-13 16:53:43 +00:00
String get title;
2018-12-08 20:53:49 +00:00
2018-05-13 16:53:43 +00:00
String get description;
2018-12-08 20:53:49 +00:00
2018-05-13 16:53:43 +00:00
int get pageCount;
2018-12-08 20:53:49 +00:00
2018-06-27 05:36:57 +00:00
BookType get type;
}
/// It even supports enums!
enum BookType {
fiction,
nonFiction
2017-06-17 01:56:38 +00:00
}
```
2018-12-08 20:53:49 +00:00
The following file will be generated:
- `book.g.dart`
2018-07-11 15:49:46 +00:00
Producing these classes:
2018-12-08 20:53:49 +00:00
- `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.
2018-03-02 18:55:03 +00:00
# Serialization
2018-12-08 20:53:49 +00:00
2018-03-02 18:55:03 +00:00
You can use the generated files as follows:
```dart
myFunction() {
var warAndPeace = new Book(
author: 'Leo Tolstoy',
title: 'War and Peace',
description: 'You will cry after reading this.',
pageCount: 1225
);
2018-12-08 20:53:49 +00:00
2018-03-02 18:55:03 +00:00
// Easily serialize models into Maps
var map = BookSerializer.toMap(warAndPeace);
2018-12-08 20:53:49 +00:00
2018-03-02 18:55:03 +00:00
// Also deserialize from Maps
2018-05-13 18:02:47 +00:00
var book = BookSerializer.fromMap(map);
2018-03-02 18:55:03 +00:00
print(book.title); // 'War and Peace'
2018-12-08 20:53:49 +00:00
2018-03-02 18:55:03 +00:00
// For compatibility with `JSON.encode`, a `toJson` method
// is included that forwards to `BookSerializer.toMap`:
expect(book.toJson(), map);
2018-12-08 20:53:49 +00:00
2018-05-13 18:02:47 +00:00
// Generated classes act as value types, and thus can be compared.
expect(BookSerializer.fromMap(map), equals(warAndPeace));
2018-03-09 12:39:21 +00:00
}
```
As of `2.0.2`, the generated output also includes information
about the serialized names of keys on your model class.
```dart
myOtherFunction() {
// Relying on the serialized key of a field? No worries.
map[BookFields.author] = 'Zora Neale Hurston';
}
2018-03-02 18:55:03 +00:00
}
```
2018-12-08 20:53:49 +00:00
2018-03-02 18:55:03 +00:00
## Customizing Serialization
2018-12-08 20:53:49 +00:00
2018-03-02 18:55:03 +00:00
Currently, these serialization methods are supported:
2018-12-08 20:53:49 +00:00
- to `Map`
- to JSON
- to TypeScript definitions
2018-03-02 18:55:03 +00:00
You can customize these by means of `serializers`:
```dart
@Serializable(serializers: const [Serializers.map, Serializers.json])
class _MyClass extends Model {}
```
2017-06-17 01:56:38 +00:00
## Aliases
2018-12-08 20:53:49 +00:00
2017-06-17 01:56:38 +00:00
Whereas Dart fields conventionally are camelCased, most database columns
tend to be snake_cased. This is not a problem, because we can define an alias
for a field.
2019-01-07 00:56:05 +00:00
By default `angel_serialize` will transform keys into snake case. Use `alias` to
2017-06-20 22:13:04 +00:00
provide a custom name, or pass `autoSnakeCaseNames`: `false` to the builder;
2017-06-17 01:56:38 +00:00
```dart
2017-06-17 02:00:24 +00:00
@serializable
2017-06-17 01:56:38 +00:00
abstract class _Spy extends Model {
/// Will show up as 'agency_id' in serialized JSON.
2018-12-08 20:53:49 +00:00
///
2017-06-17 01:56:38 +00:00
/// When deserializing JSON, instead of searching for an 'agencyId' key,
/// it will use 'agency_id'.
2018-12-08 20:53:49 +00:00
///
2017-06-17 01:56:38 +00:00
/// Hooray!
String agencyId;
2018-12-08 20:53:49 +00:00
2019-01-07 00:56:05 +00:00
@SerializableField(alias: 'foo')
2017-06-20 22:13:04 +00:00
String someOtherField;
2017-06-17 01:56:38 +00:00
}
```
2018-03-02 18:48:03 +00:00
You can also override `autoSnakeCaseNames` per model:
```dart
@Serializable(autoSnakeCaseNames: false)
abstract class _OtherCasing extends Model {
String camelCasedField;
}
```
2017-06-17 01:56:38 +00:00
## Excluding Keys
2018-12-08 20:53:49 +00:00
2017-06-17 01:56:38 +00:00
In pratice, there may keys that you want to exclude from JSON.
To accomplish this, simply annotate them with `@exclude`:
```dart
2017-06-17 02:00:24 +00:00
@serializable
2017-06-17 01:56:38 +00:00
abstract class _Whisper extends Model {
/// Will never be serialized to JSON
2019-01-07 00:56:05 +00:00
@SerializableField(exclude: true)
2017-06-17 01:56:38 +00:00
String secret;
}
```
2018-03-02 18:48:03 +00:00
There are times, however, when you want to only exclude either serialization
or deserialization, but not both. For example, you might want to deserialize
passwords from a database without sending them to users as JSON.
In this case, use `canSerialize` or `canDeserialize`:
```dart
@serializable
abstract class _Whisper extends Model {
/// Will never be serialized to JSON
2018-12-08 20:53:49 +00:00
///
2018-03-02 18:48:03 +00:00
/// ... But it can be deserialized
2019-01-07 00:56:05 +00:00
@SerializableField(exclude: true, canDeserialize: true)
2018-03-02 18:48:03 +00:00
String secret;
}
```
2018-05-15 19:36:06 +00:00
## Required Fields
2018-12-08 20:53:49 +00:00
2019-01-07 00:56:05 +00:00
It is easy to mark a field as required:
2018-05-15 19:36:06 +00:00
```dart
@serializable
abstract class _Foo extends Model {
2019-01-07 00:56:05 +00:00
@SerializableField(isNullable: false)
2018-05-15 19:36:06 +00:00
int myRequiredInt;
2018-12-08 20:53:49 +00:00
2019-01-07 00:56:05 +00:00
@SerializableField(isNullable: false, errorMessage: 'Custom message')
2018-05-15 19:36:06 +00:00
int myOtherRequiredInt;
}
```
The given field will be marked as `@required` in the
generated constructor, and serializers will check for its
presence, throwing a `FormatException` if it is missing.
2019-01-07 00:56:05 +00:00
## Adding Annotations to Generated Classes
There are times when you need the generated class to have annotations affixed to it:
```dart
@Serializable(
includeAnnotations: [
Deprecated('blah blah blah'),
pragma('something...'),
]
)
abstract class _Foo extends Model {}
```
2019-01-07 01:38:04 +00:00
## 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 `serializer` will always be
`dynamic`.
```dart
DateTime _dateFromString(s) => s is String ? HttpDate.parse(s) : null;
String _dateToString(v) => v == null ? null : HttpDate.format(v);
@Serializable(autoIdAndDateFields: false)
abstract class _HttpRequest {
@SerializableField(serializer: #_dateToString, deserializer: #_dateFromString)
DateTime date;
}
```
2017-06-17 01:56:38 +00:00
# Nesting
2018-12-08 20:53:49 +00:00
2017-06-17 01:56:38 +00:00
`angel_serialize` also supports a few types of nesting of `@serializable` classes:
2018-12-08 20:53:49 +00:00
- 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>`
2017-06-17 01:56:38 +00:00
In other words, the following are all legal, and will be serialized/deserialized.
2018-02-28 02:12:49 +00:00
You can use either the underscored name of a child class (ex. `_Book`), or the
generated class name (ex `Book`):
2017-06-17 01:56:38 +00:00
```dart
@serializable
abstract class _Author extends Model {
2018-03-02 18:48:03 +00:00
List<Book> books;
Book newestBook;
Map<String, Book> booksByIsbn;
2017-06-17 01:56:38 +00:00
}
2017-06-20 22:13:04 +00:00
```
2018-03-02 18:48:03 +00:00
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.
2017-06-20 22:13:04 +00:00
# ID and Dates
2018-12-08 20:53:49 +00:00
2017-06-20 22:13:04 +00:00
This package will automatically generate `id`, `createdAt`, and `updatedAt` fields for you,
2018-03-02 18:48:03 +00:00
in the style of an Angel `Model`. To disable this, set `autoIdAndDateFields` to `false` in the
builder constructor.
2018-03-09 12:43:47 +00:00
You can also override `autoIdAndDateFields` per model:
2018-03-02 18:48:03 +00:00
```dart
@Serializable(autoIdAndDateFields: false)
abstract class _Skinny extends Model {}
```
2018-03-29 19:58:36 +00:00
2018-12-08 20:53:49 +00:00
# Binary Data
`package:angel_serialize` also handles `Uint8List` fields, by means of serialization to
and from `base64` encoding.
2018-03-29 19:58:36 +00:00
# TypeScript Definitions
2018-12-08 20:53:49 +00:00
2018-03-29 19:58:36 +00:00
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:
```dart
@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`:
```typescript
interface Author {
id: string;
name: string;
age: number;
books: Book[];
newest_book: Book;
created_at: any;
updated_at: any;
}
interface Library {
id: string;
collection: BookCollection;
created_at: any;
updated_at: any;
}
interface BookCollection {
[key: string]: Book;
}
```
Fields with an `@Exclude()` that specifies `canSerialize: false` will not be present in the
TypeScript definition. The rationale for this is that if a field (i.e. `password`) will
2018-05-13 17:23:40 +00:00
never be sent to the client, the client shouldn't even know the field exists.
# Constructor Parameters
2018-12-08 20:53:49 +00:00
2018-05-13 17:23:40 +00:00
Sometimes, you may need to have custom constructor parameters, for example, when
using depedency injection frameworks. For these cases, `angel_serialize` can forward
custom constructor parameters.
The following:
2018-12-08 20:53:49 +00:00
2018-05-13 17:23:40 +00:00
```dart
@serializable
abstract class _Bookmark extends _BookmarkBase {
2019-01-07 00:56:05 +00:00
@SerializableField(exclude: true)
2018-05-13 17:23:40 +00:00
final Book book;
int get page;
String get comment;
_Bookmark(this.book);
}
```
Generates:
```dart
class Bookmark extends _Bookmark {
Bookmark(Book book,
{this.id,
this.page,
this.comment,
this.createdAt,
this.updatedAt})
: super(book);
@override
final String id;
// ...
}
2018-06-21 01:40:03 +00:00
```