# Serialization

![Pub Version (including pre-releases)](https://img.shields.io/pub/v/angel3_serialize?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) [![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/master/packages/serialize/angel_serialize/LICENSE)

Source-generated serialization for Dart objects. This package uses `package:source_gen` to eliminate the time you spend writing boilerplate serialization code for your models. `package:angel3_serialize` also powers `package:angel3_orm`.

* [Angel3 Serialization](#angel3-serialization)
  * [Usage](#usage)
  * [Models](#models)
  * [Serialization](#serialization)
  * [Customizing Serialization](#customizing-serialization)
  * [Subclasses](#subclasses)
  * [Aliases](#aliases)
  * [Excluding Keys](#excluding-keys)
  * [Required Fields](#required-fields)
  * [Adding Annotations to Generated Classes](#adding-annotations-to-generated-classes)
  * [Custom Serializers](#custom-serializers)
  * [Nesting](#nesting)
  * [ID and Dates](#id-and-dates)
  * [Binary Data](#binary-data)
  * [TypeScript Definitions](#typescript-definitions)
  * [Constructor Parameters](#constructor-parameters)

## Usage

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

```yaml
dependencies:
  angel3_model: ^3.0.0
  angel3_serialize: ^4.0.0
dev_dependencies:
  angel3_serialize_generator: ^4.2.0
  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: [Build Config](https://pub.dartlang.org/packages/build_config)

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

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 with a leading underscore.

In addition, you may consider using an `abstract` class to ensure immutability of models.

Rather you writing the public class, `angel3_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
import 'package:angel3_model/angel_model.dart';
import 'package:angel3_serialize/angel3_serialize.dart';
import 'package:collection/collection.dart';
part 'book.g.dart';

@serializable
abstract class _Book extends Model {
  String get author;

  @SerializableField(defaultValue: '[Untitled]')
  String get title;

  String get description;

  int get pageCount;

  BookType get type;
}

/// It even supports enums!
enum BookType {
  fiction,
  nonFiction
}
```

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:

```dart
myFunction() {
  var warAndPeace = new Book(
    author: 'Leo Tolstoy',
    title: 'War and Peace',
    description: 'You will cry after reading this.',
    pageCount: 1225
  );

  // Easily serialize models into Maps
  var map = BookSerializer.toMap(warAndPeace);

  // Also deserialize from Maps
  var book = BookSerializer.fromMap(map);
  print(book.title); // 'War and Peace'

  // For compatibility with `JSON.encode`, a `toJson` method
  // is included that forwards to `BookSerializer.toMap`:
  expect(book.toJson(), map);

  // Generated classes act as value types, and thus can be compared.
  expect(BookSerializer.fromMap(map), equals(warAndPeace));
}
```

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';
  }
```

## Customizing Serialization

Currently, these serialization methods are supported:

* to `Map`
* to JSON
* to TypeScript definitions

You can customize these by means of `serializers`:

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

## Subclasses

`angel3_serialize` pulls in fields from parent classes, as well as implemented interfaces, so it is extremely easy to share attributes among model classes:

```dart
import 'package:angel3_serialize/angel3_serialize.dart';
part 'subclass.g.dart';

@serializable
class _Animal {
  @notNull
  String genus;
  @notNull
  String species;
}

@serializable
class _Bird extends _Animal {
  @DefaultsTo(false)
  bool isSparrow;
}

var saxaulSparrow = Bird(
  genus: 'Passer',
  species: 'ammodendri',
  isSparrow: true,
);
```

## Aliases

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.

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

```dart
@serializable
abstract 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;

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

You can also override `autoSnakeCaseNames` per model:

```dart
@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`:

```dart
@serializable
abstract 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 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
  ///
  /// ... But it can be deserialized
  @SerializableField(exclude: true, canDeserialize: true)
  String secret;
}
```

## Required Fields

It is easy to mark a field as required:

```dart
@serializable
abstract class _Foo extends Model {
  @SerializableField(isNullable: false)
  int myRequiredInt;

  @SerializableField(isNullable: false, errorMessage: 'Custom message')
  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.

## 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 {}
```

## Custom Serializers

`package:angel3_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 be `dynamic`, 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 rules and validations.

```dart
DateTime _dateFromString(s) => s is String ? HttpDate.parse(s) : null;
String _dateToString(DateTime v) => v == null ? null : HttpDate.format(v);

@serializable
abstract class _HttpRequest {
  @SerializableField(
    serializer: #_dateToString,
    deserializer: #_dateFromString,
    serializesTo: String)
  DateTime date;
}
```

## Nesting

`angel3_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 the generated class name (ex `Book`):

```dart
@serializable
abstract 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 Angel3 `Model`. This will automatically be generated, **only** for classes extending `Model`.

## Binary Data

`package:angel3_serialize` also handles `Uint8List` fields, by means of serialization to and 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:

```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 never 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, when using depedency injection frameworks. For these cases, `angel3_serialize` can forward custom constructor parameters.

The following:

```dart
@serializable
abstract class _Bookmark extends _BookmarkBase {
  @SerializableField(exclude: true)
  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;

  // ...
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://angel3-docs.dukefirehawk.com/under-the-hood/serialization.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
