Document json type converters

This commit is contained in:
Simon Binder 2022-04-22 23:53:11 +02:00
parent 5f6beb76ca
commit e607ffe183
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
4 changed files with 86 additions and 51 deletions

View File

@ -0,0 +1,55 @@
// #docregion start
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:json_annotation/json_annotation.dart' as j;
part 'converters.g.dart';
@j.JsonSerializable()
class Preferences {
bool receiveEmails;
String selectedTheme;
Preferences(this.receiveEmails, this.selectedTheme);
factory Preferences.fromJson(Map<String, dynamic> json) =>
_$PreferencesFromJson(json);
Map<String, dynamic> toJson() => _$PreferencesToJson(this);
}
// #enddocregion start
// #docregion converter
// stores preferences as strings
class PreferenceConverter extends TypeConverter<Preferences, String>
with JsonTypeConverter<Preferences, String> {
const PreferenceConverter();
@override
Preferences? mapToDart(String? fromDb) {
if (fromDb == null) {
return null;
}
return Preferences.fromJson(json.decode(fromDb) as Map<String, dynamic>);
}
@override
String? mapToSql(Preferences? value) {
if (value == null) {
return null;
}
return json.encode(value.toJson());
}
}
// #enddocregion converter
// #docregion table
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
TextColumn get preferences =>
text().map(const PreferenceConverter()).nullable()();
}
// #enddocregion table

View File

@ -8,70 +8,25 @@ template: layouts/docs/single
---
Drift supports a variety of types out of the box, but sometimes you need to store more complex data.
You can achieve this by using `TypeConverters`. In this example, we'll use the the
You can achieve this by using `TypeConverters`. In this example, we'll use the the
[json_serializable](https://pub.dev/packages/json_annotation) package to store a custom object in a
text column. Drift supports any Dart type for which you provide a `TypeConverter`, we're using that
package here to make the example simpler.
{% assign dart = 'package:moor_documentation/snippets/type_converters/converters.dart.excerpt.json' | readString | json_decode %}
## Using converters in Dart
```dart
import 'dart:convert';
import 'package:json_annotation/json_annotation.dart' as j;
import 'package:drift/drift.dart';
part 'database.g.dart';
@j.JsonSerializable()
class Preferences {
bool receiveEmails;
String selectedTheme;
Preferences(this.receiveEmails, this.selectedTheme);
factory Preferences.fromJson(Map<String, dynamic> json) =>
_$PreferencesFromJson(json);
Map<String, dynamic> toJson() => _$PreferencesToJson(this);
}
```
{% include "blocks/snippet" snippets = dart name = 'start' %}
Next, we have to tell drift how to store a `Preferences` object in the database. We write
a `TypeConverter` for that:
```dart
// stores preferences as strings
class PreferenceConverter extends TypeConverter<Preferences, String> {
const PreferenceConverter();
@override
Preferences? mapToDart(String? fromDb) {
if (fromDb == null) {
return null;
}
return Preferences.fromJson(json.decode(fromDb) as Map<String, dynamic>);
}
@override
String? mapToSql(Preferences? value) {
if (value == null) {
return null;
}
return json.encode(value.toJson());
}
}
```
{% include "blocks/snippet" snippets = dart name = 'converter' %}
Finally, we can use that converter in a table declaration:
```dart
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
TextColumn get preferences =>
text().map(const PreferenceConverter()).nullable()();
}
```
{% include "blocks/snippet" snippets = dart name = 'table' %}
The generated `User` class will then have a `preferences` column of type
`Preferences`. Drift will automatically take care of storing and loading
@ -158,3 +113,16 @@ CREATE TABLE tasks (
```
Of course, the warning about automatic enum converters also applies to drift files.
## Type converters and json serialization
By default, type converters only apply to the conversion from Dart to the database. They don't impact how
values are serialized to and from JSON.
If you want to apply the same conversion to JSON as well, make your type converter mix-in the
`JsonTypeConverter` class.
You can also override the `toJson` and `fromJson` methods to customize serialization as long as the types
stay the compatible.
If you want to serialize to a different JSON type (e.g. you have a type converter `<MyObject, int>` in SQL but
want to map to a string in JSON), you'll have to write a custom [`ValueSerializer`](https://drift.simonbinder.eu/api/drift/valueserializer-class)
and pass it to the serialization methods.

View File

@ -18,6 +18,8 @@ dev_dependencies:
hosted: https://simonbinder.eu
version: ^0.2.5
linkcheck: ^2.0.19
json_annotation: ^4.4.0
json_serializable: ^6.1.6
shelf: ^1.2.0
shelf_static: ^1.1.0
code_snippets:

View File

@ -6,6 +6,10 @@ part of 'sql_types.dart';
/// Dart currently supports [DateTime], [double], [int], [Uint8List], [bool]
/// and [String] for [S].
///
/// Using a type converter does impact the way drift serializes data classes to
/// JSON by default. To control that, use a [JsonTypeConverter] or a custom
/// [ValueSerializer].
///
/// Also see [BuildGeneralColumn.map] for details.
abstract class TypeConverter<D, S> {
/// Empty constant constructor so that subclasses can have a constant
@ -22,6 +26,12 @@ abstract class TypeConverter<D, S> {
/// A mixin for [TypeConverter]s that should also apply to drift's builtin
/// JSON serialization of data classes.
///
/// By default, a [TypeConverter] only applies to the serialization from Dart
/// to SQL (and vice-versa).
/// When a [BuildGeneralColumn.map] column (or a `MAPPED BY` constraint in
/// `.drift` files) refers to a type converter that inherits from
/// [JsonTypeConverter], it will also be used to conversion from and to json.
mixin JsonTypeConverter<D, S> on TypeConverter<D, S> {
/// Map a value from the Data class to json.
///