From a0f3f3d8016846cec0784bbeede3474e59fc9ac9 Mon Sep 17 00:00:00 2001 From: Isaac Adeni Adariku Date: Tue, 11 Oct 2022 12:14:52 +0100 Subject: [PATCH 1/6] Update web.md Following this depreciated change [https://github.com/simolus3/drift/commit/1af6bb78d98ee7b57f099624619a323eafd67576#:~:text=%40Deprecated(%27Use%20the,this(executor)%3B], the documentation for the web.md needs to be updated. This PR updates the documentation to reflect the changes. --- docs/pages/docs/Other engines/web.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/docs/Other engines/web.md b/docs/pages/docs/Other engines/web.md index 34bf1d77..7842e96c 100644 --- a/docs/pages/docs/Other engines/web.md +++ b/docs/pages/docs/Other engines/web.md @@ -167,7 +167,7 @@ void main() { final db = WebDatabase.withStorage(DriftWebStorage.indexedDb('worker', migrateFromLocalStorage: false, inWebWorker: true)); - final server = DriftServer(DatabaseConnection.fromExecutor(db)); + final server = DriftServer(DatabaseConnection(db)); self.onConnect.listen((event) { final msg = event as MessageEvent; @@ -294,4 +294,4 @@ With this setup, sqlite3 can be used on the web without an external library: This snippet also works in a service worker. -If you're running into any issues with the new backend, please post them [here](https://github.com/simolus3/sqlite3.dart/issues). \ No newline at end of file +If you're running into any issues with the new backend, please post them [here](https://github.com/simolus3/sqlite3.dart/issues). From ba28b51125a02504ae05074534378559f0a27742 Mon Sep 17 00:00:00 2001 From: Alexander Wilde Date: Fri, 14 Oct 2022 19:07:32 +0100 Subject: [PATCH 2/6] Decouple JsonTypeConverter from TypeConverter --- docs/lib/snippets/expressions.dart | 1 - drift/lib/src/runtime/types/converters.dart | 36 ++++++++++++++----- drift/test/generated/todos.dart | 8 ++++- drift/test/generated/todos.g.dart | 6 ++-- drift_dev/lib/src/analyzer/dart_types.dart | 4 +++ drift_dev/lib/src/analyzer/helper.dart | 13 ++++++- drift_dev/lib/src/model/types.dart | 14 ++++++++ .../lib/src/model/used_type_converter.dart | 16 ++++++--- .../src/writer/tables/data_class_writer.dart | 4 +-- .../analyzer/dart/type_converter_test.dart | 2 +- 10 files changed, 83 insertions(+), 21 deletions(-) diff --git a/docs/lib/snippets/expressions.dart b/docs/lib/snippets/expressions.dart index 43927164..6c979900 100644 --- a/docs/lib/snippets/expressions.dart +++ b/docs/lib/snippets/expressions.dart @@ -10,7 +10,6 @@ extension Expressions on MyDatabase { return (select(categories)..where((row) => hasNoTodo)).get(); } // #enddocregion emptyCategories - } // #docregion bitwise diff --git a/drift/lib/src/runtime/types/converters.dart b/drift/lib/src/runtime/types/converters.dart index 8171458b..4a362bb1 100644 --- a/drift/lib/src/runtime/types/converters.dart +++ b/drift/lib/src/runtime/types/converters.dart @@ -35,16 +35,20 @@ abstract class TypeConverter { /// `.drift` files) refers to a type converter that inherits from /// [JsonTypeConverter], it will also be used for the conversion from and to /// JSON. -mixin JsonTypeConverter on TypeConverter { +abstract class JsonTypeConverter extends TypeConverter { + /// Empty constant constructor so that subclasses can have a constant + /// constructor. + const JsonTypeConverter(); + /// Map a value from the Data class to json. /// /// Defaults to doing the same conversion as for Dart -> SQL, [toSql]. - S toJson(D value) => toSql(value); + J toJson(D value); /// Map a value from json to something understood by the data class. /// /// Defaults to doing the same conversion as for SQL -> Dart, [toSql]. - D fromJson(S json) => fromSql(json); + D fromJson(J json); /// Wraps an [inner] type converter that only considers non-nullable values /// as a type converter that handles null values too. @@ -52,8 +56,9 @@ mixin JsonTypeConverter on TypeConverter { /// The returned type converter will use the [inner] type converter for non- /// null values. Further, `null` is mapped to `null` in both directions (from /// Dart to SQL and vice-versa). - static JsonTypeConverter asNullable( - TypeConverter inner) { + static JsonTypeConverter + asNullable( + JsonTypeConverter inner) { return _NullWrappingTypeConverterWithJson(inner); } } @@ -150,9 +155,10 @@ class _NullWrappingTypeConverter S requireToSql(D value) => _inner.toSql(value); } -class _NullWrappingTypeConverterWithJson - extends NullAwareTypeConverter with JsonTypeConverter { - final TypeConverter _inner; +class _NullWrappingTypeConverterWithJson + extends NullAwareTypeConverter + implements JsonTypeConverter { + final JsonTypeConverter _inner; const _NullWrappingTypeConverterWithJson(this._inner); @@ -161,4 +167,18 @@ class _NullWrappingTypeConverterWithJson @override S requireToSql(D value) => _inner.toSql(value); + + D requireFromJson(J json) => _inner.fromJson(json); + + @override + D? fromJson(J? json) { + return json == null ? null : requireFromJson(json); + } + + J? requireToJson(D? value) => _inner.toJson(value as D); + + @override + J? toJson(D? value) { + return value == null ? null : requireToJson(value); + } } diff --git a/drift/test/generated/todos.dart b/drift/test/generated/todos.dart index 0168dbf6..dd5fdff5 100644 --- a/drift/test/generated/todos.dart +++ b/drift/test/generated/todos.dart @@ -135,8 +135,14 @@ class CustomConverter extends TypeConverter { } class CustomJsonConverter extends CustomConverter - with JsonTypeConverter { + implements JsonTypeConverter { const CustomJsonConverter(); + + @override + MyCustomObject fromJson(String json) => fromSql(json); + + @override + String toJson(MyCustomObject value) => toSql(value); } abstract class CategoryTodoCountView extends View { diff --git a/drift/test/generated/todos.g.dart b/drift/test/generated/todos.g.dart index 9bc1c990..a58388c1 100644 --- a/drift/test/generated/todos.g.dart +++ b/drift/test/generated/todos.g.dart @@ -1252,7 +1252,7 @@ class PureDefault extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return PureDefault( txt: $PureDefaultsTable.$converter0n - .fromJson(serializer.fromJson(json['txt'])), + .fromJson(serializer.fromJson(json['txt'])), ); } factory PureDefault.fromJsonString(String encodedJson, @@ -1373,9 +1373,9 @@ class $PureDefaultsTable extends PureDefaults return $PureDefaultsTable(attachedDatabase, alias); } - static JsonTypeConverter $converter0 = + static JsonTypeConverter $converter0 = const CustomJsonConverter(); - static JsonTypeConverter $converter0n = + static JsonTypeConverter $converter0n = JsonTypeConverter.asNullable($converter0); } diff --git a/drift_dev/lib/src/analyzer/dart_types.dart b/drift_dev/lib/src/analyzer/dart_types.dart index f3647dfb..1d33cba8 100644 --- a/drift_dev/lib/src/analyzer/dart_types.dart +++ b/drift_dev/lib/src/analyzer/dart_types.dart @@ -152,6 +152,8 @@ UsedTypeConverter? readTypeConverter( final staticType = dartExpression.staticType; final asTypeConverter = staticType != null ? helper.asTypeConverter(staticType) : null; + final asJsonTypeConverter = + staticType != null ? helper.asJsonTypeConverter(staticType) : null; if (asTypeConverter == null) { reportError('Not a type converter'); @@ -160,6 +162,7 @@ UsedTypeConverter? readTypeConverter( final dartType = asTypeConverter.typeArguments[0]; final sqlType = asTypeConverter.typeArguments[1]; + final jsonType = asJsonTypeConverter?.typeArguments[2] ?? sqlType; final typeSystem = library.typeSystem; final dartTypeNullable = typeSystem.isNullable(dartType); @@ -195,6 +198,7 @@ UsedTypeConverter? readTypeConverter( expression: dartExpression.toSource(), dartType: resolvedDartType ?? DriftDartType.of(dartType), sqlType: sqlType, + jsonType: jsonType, dartTypeIsNullable: dartTypeNullable, sqlTypeIsNullable: sqlTypeNullable, alsoAppliesToJsonConversion: appliesToJsonToo, diff --git a/drift_dev/lib/src/analyzer/helper.dart b/drift_dev/lib/src/analyzer/helper.dart index 5b563dd7..8a5e343e 100644 --- a/drift_dev/lib/src/analyzer/helper.dart +++ b/drift_dev/lib/src/analyzer/helper.dart @@ -26,13 +26,24 @@ class HelperLibrary { return type.asInstanceOf(converter); } + /// Converts the given Dart [type] into an instantiation of the + /// `TypeConverter` class from drift. + /// + /// Returns `null` if [type] is not a subtype of `TypeConverter`. + InterfaceType? asJsonTypeConverter(DartType type) { + final converter = helperLibrary.exportNamespace.get('JsonTypeConverter') + as InterfaceElement; + return type.asInstanceOf(converter); + } + bool isJsonAwareTypeConverter(DartType? type, LibraryElement context) { final jsonMixin = helperLibrary.exportNamespace.get('JsonTypeConverter') as InterfaceElement; final jsonConverterType = jsonMixin.instantiate( typeArguments: [ context.typeProvider.dynamicType, - context.typeProvider.dynamicType + context.typeProvider.dynamicType, + context.typeProvider.dynamicType, ], nullabilitySuffix: NullabilitySuffix.none, ); diff --git a/drift_dev/lib/src/model/types.dart b/drift_dev/lib/src/model/types.dart index b9672154..03ff81df 100644 --- a/drift_dev/lib/src/model/types.dart +++ b/drift_dev/lib/src/model/types.dart @@ -136,6 +136,20 @@ extension OperationOnTypes on HasType { return variableTypeCode(); } + + /// The dart type that matches the values of this column. For instance, if a + /// table has declared an `IntColumn`, the matching dart type name would be + /// [int]. + String jsonTypeCode() { + final converter = typeConverter; + if (converter != null) { + var inner = converter.jsonType.codeString(); + if (converter.canBeSkippedForNulls && nullable) inner += '?'; + return isArray ? 'List<$inner>' : inner; + } + + return variableTypeCode(); + } } const Map dartTypeNames = { diff --git a/drift_dev/lib/src/model/used_type_converter.dart b/drift_dev/lib/src/model/used_type_converter.dart index ea91a24a..f5525dbb 100644 --- a/drift_dev/lib/src/model/used_type_converter.dart +++ b/drift_dev/lib/src/model/used_type_converter.dart @@ -31,6 +31,10 @@ class UsedTypeConverter { /// mapped values in the database. final DartType sqlType; + /// The "JSON" type of this type converter. This is the type used to represent + /// mapped values in the database. + final DartType jsonType; + /// Whether the Dart-value output of this type converter is nullable. /// /// In other words, [dartType] is potentially nullable. @@ -80,6 +84,7 @@ class UsedTypeConverter { required this.expression, required this.dartType, required this.sqlType, + required this.jsonType, required this.dartTypeIsNullable, required this.sqlTypeIsNullable, this.alsoAppliesToJsonConversion = false, @@ -115,6 +120,7 @@ class UsedTypeConverter { sqlTypeIsNullable: false, dartTypeIsNullable: false, sqlType: typeProvider.intType, + jsonType: typeProvider.intType, ); } @@ -133,11 +139,13 @@ class UsedTypeConverter { String converterNameInCode({bool makeNullable = false}) { var sqlDartType = sqlType.getDisplayString(withNullability: true); if (makeNullable) sqlDartType += '?'; + var jsonDartType = jsonType.getDisplayString(withNullability: true); + if (makeNullable) jsonDartType += '?'; - final className = - alsoAppliesToJsonConversion ? 'JsonTypeConverter' : 'TypeConverter'; - - return '$className<${dartTypeCode(makeNullable)}, $sqlDartType>'; + if (alsoAppliesToJsonConversion) { + return 'JsonTypeConverter<${dartTypeCode(makeNullable)}, $sqlDartType, $jsonDartType>'; + } + return 'TypeConverter<${dartTypeCode(makeNullable)}, $sqlDartType>'; } } diff --git a/drift_dev/lib/src/writer/tables/data_class_writer.dart b/drift_dev/lib/src/writer/tables/data_class_writer.dart index b4183229..e511799c 100644 --- a/drift_dev/lib/src/writer/tables/data_class_writer.dart +++ b/drift_dev/lib/src/writer/tables/data_class_writer.dart @@ -107,7 +107,7 @@ class DataClassWriter { final typeConverter = column.typeConverter; if (typeConverter != null && typeConverter.alsoAppliesToJsonConversion) { - final type = column.innerColumnType(nullable: column.nullable); + final type = typeConverter.jsonType; final fromConverter = "serializer.fromJson<$type>(json['$jsonKey'])"; final converterField = typeConverter.tableAndField(forNullableColumn: column.nullable); @@ -151,7 +151,7 @@ class DataClassWriter { final converterField = typeConverter.tableAndField(forNullableColumn: column.nullable); value = '$converterField.toJson($value)'; - dartType = column.innerColumnType(nullable: true); + dartType = column.jsonTypeCode(); } _buffer.write("'$name': serializer.toJson<$dartType>($value),"); diff --git a/drift_dev/test/analyzer/dart/type_converter_test.dart b/drift_dev/test/analyzer/dart/type_converter_test.dart index 297568c5..62fa5e7c 100644 --- a/drift_dev/test/analyzer/dart/type_converter_test.dart +++ b/drift_dev/test/analyzer/dart/type_converter_test.dart @@ -15,7 +15,7 @@ void main() { import 'package:drift/drift.dart'; TypeConverter withoutJson() => throw 'stub'; -JsonTypeConverter withJson() => throw 'stub'; +JsonTypeConverter withJson() => throw 'stub'; class Users extends Table { TextColumn get foo => text().map(withoutJson())(); From ff9d419559fccc5c2dfa74a474697be263b51377 Mon Sep 17 00:00:00 2001 From: Alexander Wilde Date: Sat, 15 Oct 2022 23:47:58 +0100 Subject: [PATCH 3/6] Backwards compatible attempt --- drift/lib/src/drift_dev_helper.dart | 3 +- drift/lib/src/runtime/types/converters.dart | 46 +++++++++++++++---- drift/test/generated/todos.dart | 8 +--- drift/test/generated/todos.g.dart | 9 ++-- drift_dev/lib/src/analyzer/dart_types.dart | 2 +- drift_dev/lib/src/analyzer/helper.dart | 10 ++-- .../lib/src/model/used_type_converter.dart | 2 +- .../lib/src/writer/tables/table_writer.dart | 2 +- .../analyzer/dart/type_converter_test.dart | 2 +- 9 files changed, 53 insertions(+), 31 deletions(-) diff --git a/drift/lib/src/drift_dev_helper.dart b/drift/lib/src/drift_dev_helper.dart index cd21be52..85e31942 100644 --- a/drift/lib/src/drift_dev_helper.dart +++ b/drift/lib/src/drift_dev_helper.dart @@ -1,2 +1,3 @@ // This field is analyzed by drift_dev to easily obtain common types. -export 'runtime/types/converters.dart' show TypeConverter, JsonTypeConverter; +export 'runtime/types/converters.dart' + show TypeConverter, JsonTypeConverter, JsonTypeConverterWithDifferentTypes; diff --git a/drift/lib/src/runtime/types/converters.dart b/drift/lib/src/runtime/types/converters.dart index 4a362bb1..0c140df6 100644 --- a/drift/lib/src/runtime/types/converters.dart +++ b/drift/lib/src/runtime/types/converters.dart @@ -33,13 +33,9 @@ abstract class TypeConverter { /// 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 for the conversion from and to +/// [JsonTypeConverterWithDifferentTypes], it will also be used for the conversion from and to /// JSON. -abstract class JsonTypeConverter extends TypeConverter { - /// Empty constant constructor so that subclasses can have a constant - /// constructor. - const JsonTypeConverter(); - +mixin JsonTypeConverterWithDifferentTypes on TypeConverter { /// Map a value from the Data class to json. /// /// Defaults to doing the same conversion as for Dart -> SQL, [toSql]. @@ -56,9 +52,39 @@ abstract class JsonTypeConverter extends TypeConverter { /// The returned type converter will use the [inner] type converter for non- /// null values. Further, `null` is mapped to `null` in both directions (from /// Dart to SQL and vice-versa). - static JsonTypeConverter + static JsonTypeConverterWithDifferentTypes asNullable( - JsonTypeConverter inner) { + JsonTypeConverterWithDifferentTypes inner) { + return _NullWrappingTypeConverterWithJson(inner); + } +} + +/// 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 for the conversion from and to +/// JSON. +mixin JsonTypeConverter + implements JsonTypeConverterWithDifferentTypes { + @override + S toJson(D value) => toSql(value); + + @override + D fromJson(S json) => fromSql(json); + + /// Wraps an [inner] type converter that only considers non-nullable values + /// as a type converter that handles null values too. + /// + /// The returned type converter will use the [inner] type converter for non- + /// null values. Further, `null` is mapped to `null` in both directions (from + /// Dart to SQL and vice-versa). + static JsonTypeConverterWithDifferentTypes + asNullable( + JsonTypeConverterWithDifferentTypes inner) { return _NullWrappingTypeConverterWithJson(inner); } } @@ -157,8 +183,8 @@ class _NullWrappingTypeConverter class _NullWrappingTypeConverterWithJson extends NullAwareTypeConverter - implements JsonTypeConverter { - final JsonTypeConverter _inner; + implements JsonTypeConverterWithDifferentTypes { + final JsonTypeConverterWithDifferentTypes _inner; const _NullWrappingTypeConverterWithJson(this._inner); diff --git a/drift/test/generated/todos.dart b/drift/test/generated/todos.dart index dd5fdff5..0168dbf6 100644 --- a/drift/test/generated/todos.dart +++ b/drift/test/generated/todos.dart @@ -135,14 +135,8 @@ class CustomConverter extends TypeConverter { } class CustomJsonConverter extends CustomConverter - implements JsonTypeConverter { + with JsonTypeConverter { const CustomJsonConverter(); - - @override - MyCustomObject fromJson(String json) => fromSql(json); - - @override - String toJson(MyCustomObject value) => toSql(value); } abstract class CategoryTodoCountView extends View { diff --git a/drift/test/generated/todos.g.dart b/drift/test/generated/todos.g.dart index a58388c1..b3c0ee71 100644 --- a/drift/test/generated/todos.g.dart +++ b/drift/test/generated/todos.g.dart @@ -1373,10 +1373,11 @@ class $PureDefaultsTable extends PureDefaults return $PureDefaultsTable(attachedDatabase, alias); } - static JsonTypeConverter $converter0 = - const CustomJsonConverter(); - static JsonTypeConverter $converter0n = - JsonTypeConverter.asNullable($converter0); + static JsonTypeConverterWithDifferentTypes + $converter0 = const CustomJsonConverter(); + static JsonTypeConverterWithDifferentTypes + $converter0n = + JsonTypeConverterWithDifferentTypes.asNullable($converter0); } class CategoryTodoCountViewData extends DataClass { diff --git a/drift_dev/lib/src/analyzer/dart_types.dart b/drift_dev/lib/src/analyzer/dart_types.dart index 1d33cba8..24a0df30 100644 --- a/drift_dev/lib/src/analyzer/dart_types.dart +++ b/drift_dev/lib/src/analyzer/dart_types.dart @@ -181,7 +181,7 @@ UsedTypeConverter? readTypeConverter( "potentially map to `null` which can't be stored in the database."); } else if (!canBeSkippedForNulls) { final alternative = appliesToJsonToo - ? 'JsonTypeConverter.asNullable' + ? 'JsonTypeConverterWithDifferentTypes.asNullable' : 'NullAwareTypeConverter.wrap'; reportError('This column is nullable, but the type converter has a non-' diff --git a/drift_dev/lib/src/analyzer/helper.dart b/drift_dev/lib/src/analyzer/helper.dart index 8a5e343e..f6053153 100644 --- a/drift_dev/lib/src/analyzer/helper.dart +++ b/drift_dev/lib/src/analyzer/helper.dart @@ -27,18 +27,18 @@ class HelperLibrary { } /// Converts the given Dart [type] into an instantiation of the - /// `TypeConverter` class from drift. + /// `JsonTypeConverter` class from drift. /// /// Returns `null` if [type] is not a subtype of `TypeConverter`. InterfaceType? asJsonTypeConverter(DartType type) { - final converter = helperLibrary.exportNamespace.get('JsonTypeConverter') - as InterfaceElement; + final converter = helperLibrary.exportNamespace + .get('JsonTypeConverterWithDifferentTypes') as InterfaceElement; return type.asInstanceOf(converter); } bool isJsonAwareTypeConverter(DartType? type, LibraryElement context) { - final jsonMixin = helperLibrary.exportNamespace.get('JsonTypeConverter') - as InterfaceElement; + final jsonMixin = helperLibrary.exportNamespace + .get('JsonTypeConverterWithDifferentTypes') as InterfaceElement; final jsonConverterType = jsonMixin.instantiate( typeArguments: [ context.typeProvider.dynamicType, diff --git a/drift_dev/lib/src/model/used_type_converter.dart b/drift_dev/lib/src/model/used_type_converter.dart index f5525dbb..e4846181 100644 --- a/drift_dev/lib/src/model/used_type_converter.dart +++ b/drift_dev/lib/src/model/used_type_converter.dart @@ -143,7 +143,7 @@ class UsedTypeConverter { if (makeNullable) jsonDartType += '?'; if (alsoAppliesToJsonConversion) { - return 'JsonTypeConverter<${dartTypeCode(makeNullable)}, $sqlDartType, $jsonDartType>'; + return 'JsonTypeConverterWithDifferentTypes<${dartTypeCode(makeNullable)}, $sqlDartType, $jsonDartType>'; } return 'TypeConverter<${dartTypeCode(makeNullable)}, $sqlDartType>'; } diff --git a/drift_dev/lib/src/writer/tables/table_writer.dart b/drift_dev/lib/src/writer/tables/table_writer.dart index db5550f6..fbaf6993 100644 --- a/drift_dev/lib/src/writer/tables/table_writer.dart +++ b/drift_dev/lib/src/writer/tables/table_writer.dart @@ -336,7 +336,7 @@ class TableWriter extends TableOrViewWriter { converter.converterNameInCode(makeNullable: true); final wrap = converter.alsoAppliesToJsonConversion - ? 'JsonTypeConverter.asNullable' + ? 'JsonTypeConverterWithDifferentTypes.asNullable' : 'NullAwareTypeConverter.wrap'; final code = '$wrap(${converter.fieldName})'; diff --git a/drift_dev/test/analyzer/dart/type_converter_test.dart b/drift_dev/test/analyzer/dart/type_converter_test.dart index 62fa5e7c..297568c5 100644 --- a/drift_dev/test/analyzer/dart/type_converter_test.dart +++ b/drift_dev/test/analyzer/dart/type_converter_test.dart @@ -15,7 +15,7 @@ void main() { import 'package:drift/drift.dart'; TypeConverter withoutJson() => throw 'stub'; -JsonTypeConverter withJson() => throw 'stub'; +JsonTypeConverter withJson() => throw 'stub'; class Users extends Table { TextColumn get foo => text().map(withoutJson())(); From 09951adda6c2f88e5e060dab5f092741cd5dd907 Mon Sep 17 00:00:00 2001 From: Alexander Wilde Date: Tue, 18 Oct 2022 01:06:22 +0100 Subject: [PATCH 4/6] JsonTypeConverterWithDifferentTypes -> JsonTypeConverter2 --- drift/lib/src/drift_dev_helper.dart | 2 +- drift/lib/src/runtime/types/converters.dart | 19 +++++++++---------- drift/test/generated/todos.g.dart | 9 ++++----- drift_dev/lib/src/analyzer/dart_types.dart | 2 +- drift_dev/lib/src/analyzer/helper.dart | 8 ++++---- .../lib/src/model/used_type_converter.dart | 2 +- .../lib/src/writer/tables/table_writer.dart | 2 +- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/drift/lib/src/drift_dev_helper.dart b/drift/lib/src/drift_dev_helper.dart index 85e31942..d667d296 100644 --- a/drift/lib/src/drift_dev_helper.dart +++ b/drift/lib/src/drift_dev_helper.dart @@ -1,3 +1,3 @@ // This field is analyzed by drift_dev to easily obtain common types. export 'runtime/types/converters.dart' - show TypeConverter, JsonTypeConverter, JsonTypeConverterWithDifferentTypes; + show TypeConverter, JsonTypeConverter, JsonTypeConverter2; diff --git a/drift/lib/src/runtime/types/converters.dart b/drift/lib/src/runtime/types/converters.dart index 0c140df6..6e0ab070 100644 --- a/drift/lib/src/runtime/types/converters.dart +++ b/drift/lib/src/runtime/types/converters.dart @@ -33,9 +33,9 @@ abstract class TypeConverter { /// 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 -/// [JsonTypeConverterWithDifferentTypes], it will also be used for the conversion from and to +/// [JsonTypeConverter2], it will also be used for the conversion from and to /// JSON. -mixin JsonTypeConverterWithDifferentTypes on TypeConverter { +mixin JsonTypeConverter2 on TypeConverter { /// Map a value from the Data class to json. /// /// Defaults to doing the same conversion as for Dart -> SQL, [toSql]. @@ -52,9 +52,9 @@ mixin JsonTypeConverterWithDifferentTypes on TypeConverter { /// The returned type converter will use the [inner] type converter for non- /// null values. Further, `null` is mapped to `null` in both directions (from /// Dart to SQL and vice-versa). - static JsonTypeConverterWithDifferentTypes + static JsonTypeConverter2 asNullable( - JsonTypeConverterWithDifferentTypes inner) { + JsonTypeConverter2 inner) { return _NullWrappingTypeConverterWithJson(inner); } } @@ -68,8 +68,7 @@ mixin JsonTypeConverterWithDifferentTypes on TypeConverter { /// `.drift` files) refers to a type converter that inherits from /// [JsonTypeConverter], it will also be used for the conversion from and to /// JSON. -mixin JsonTypeConverter - implements JsonTypeConverterWithDifferentTypes { +mixin JsonTypeConverter implements JsonTypeConverter2 { @override S toJson(D value) => toSql(value); @@ -82,9 +81,9 @@ mixin JsonTypeConverter /// The returned type converter will use the [inner] type converter for non- /// null values. Further, `null` is mapped to `null` in both directions (from /// Dart to SQL and vice-versa). - static JsonTypeConverterWithDifferentTypes + static JsonTypeConverter2 asNullable( - JsonTypeConverterWithDifferentTypes inner) { + JsonTypeConverter2 inner) { return _NullWrappingTypeConverterWithJson(inner); } } @@ -183,8 +182,8 @@ class _NullWrappingTypeConverter class _NullWrappingTypeConverterWithJson extends NullAwareTypeConverter - implements JsonTypeConverterWithDifferentTypes { - final JsonTypeConverterWithDifferentTypes _inner; + implements JsonTypeConverter2 { + final JsonTypeConverter2 _inner; const _NullWrappingTypeConverterWithJson(this._inner); diff --git a/drift/test/generated/todos.g.dart b/drift/test/generated/todos.g.dart index b3c0ee71..16c66f2d 100644 --- a/drift/test/generated/todos.g.dart +++ b/drift/test/generated/todos.g.dart @@ -1373,11 +1373,10 @@ class $PureDefaultsTable extends PureDefaults return $PureDefaultsTable(attachedDatabase, alias); } - static JsonTypeConverterWithDifferentTypes - $converter0 = const CustomJsonConverter(); - static JsonTypeConverterWithDifferentTypes - $converter0n = - JsonTypeConverterWithDifferentTypes.asNullable($converter0); + static JsonTypeConverter2 $converter0 = + const CustomJsonConverter(); + static JsonTypeConverter2 $converter0n = + JsonTypeConverter2.asNullable($converter0); } class CategoryTodoCountViewData extends DataClass { diff --git a/drift_dev/lib/src/analyzer/dart_types.dart b/drift_dev/lib/src/analyzer/dart_types.dart index 24a0df30..bff4315d 100644 --- a/drift_dev/lib/src/analyzer/dart_types.dart +++ b/drift_dev/lib/src/analyzer/dart_types.dart @@ -181,7 +181,7 @@ UsedTypeConverter? readTypeConverter( "potentially map to `null` which can't be stored in the database."); } else if (!canBeSkippedForNulls) { final alternative = appliesToJsonToo - ? 'JsonTypeConverterWithDifferentTypes.asNullable' + ? 'JsonTypeConverter2.asNullable' : 'NullAwareTypeConverter.wrap'; reportError('This column is nullable, but the type converter has a non-' diff --git a/drift_dev/lib/src/analyzer/helper.dart b/drift_dev/lib/src/analyzer/helper.dart index f6053153..fc956746 100644 --- a/drift_dev/lib/src/analyzer/helper.dart +++ b/drift_dev/lib/src/analyzer/helper.dart @@ -31,14 +31,14 @@ class HelperLibrary { /// /// Returns `null` if [type] is not a subtype of `TypeConverter`. InterfaceType? asJsonTypeConverter(DartType type) { - final converter = helperLibrary.exportNamespace - .get('JsonTypeConverterWithDifferentTypes') as InterfaceElement; + final converter = helperLibrary.exportNamespace.get('JsonTypeConverter2') + as InterfaceElement; return type.asInstanceOf(converter); } bool isJsonAwareTypeConverter(DartType? type, LibraryElement context) { - final jsonMixin = helperLibrary.exportNamespace - .get('JsonTypeConverterWithDifferentTypes') as InterfaceElement; + final jsonMixin = helperLibrary.exportNamespace.get('JsonTypeConverter2') + as InterfaceElement; final jsonConverterType = jsonMixin.instantiate( typeArguments: [ context.typeProvider.dynamicType, diff --git a/drift_dev/lib/src/model/used_type_converter.dart b/drift_dev/lib/src/model/used_type_converter.dart index e4846181..6c1573fb 100644 --- a/drift_dev/lib/src/model/used_type_converter.dart +++ b/drift_dev/lib/src/model/used_type_converter.dart @@ -143,7 +143,7 @@ class UsedTypeConverter { if (makeNullable) jsonDartType += '?'; if (alsoAppliesToJsonConversion) { - return 'JsonTypeConverterWithDifferentTypes<${dartTypeCode(makeNullable)}, $sqlDartType, $jsonDartType>'; + return 'JsonTypeConverter2<${dartTypeCode(makeNullable)}, $sqlDartType, $jsonDartType>'; } return 'TypeConverter<${dartTypeCode(makeNullable)}, $sqlDartType>'; } diff --git a/drift_dev/lib/src/writer/tables/table_writer.dart b/drift_dev/lib/src/writer/tables/table_writer.dart index fbaf6993..2ad46c58 100644 --- a/drift_dev/lib/src/writer/tables/table_writer.dart +++ b/drift_dev/lib/src/writer/tables/table_writer.dart @@ -336,7 +336,7 @@ class TableWriter extends TableOrViewWriter { converter.converterNameInCode(makeNullable: true); final wrap = converter.alsoAppliesToJsonConversion - ? 'JsonTypeConverterWithDifferentTypes.asNullable' + ? 'JsonTypeConverter2.asNullable' : 'NullAwareTypeConverter.wrap'; final code = '$wrap(${converter.fieldName})'; From 92bb5a7fa1adad579075be80ab199935430f5992 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 18 Oct 2022 20:36:09 +0200 Subject: [PATCH 5/6] Add test and changelog --- drift/CHANGELOG.md | 5 ++++ drift/lib/src/runtime/types/converters.dart | 28 ++++++++------------- drift/pubspec.yaml | 2 +- drift/test/generated/todos.dart | 12 ++++++++- drift/test/generated/todos.g.dart | 14 +++++------ drift/test/serialization_test.dart | 8 ++++-- drift_dev/lib/src/model/types.dart | 5 ++-- drift_dev/pubspec.yaml | 4 +-- 8 files changed, 44 insertions(+), 34 deletions(-) diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index 60d68b5d..66591d38 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.3.0-dev + +- Add the `JsonTypeConverter2` mixin. It behaves similar to the existing json + type converters, but can use a different SQL and JSON type. + ## 2.2.0 - Always escape column names, avoiding the costs of using a regular expression diff --git a/drift/lib/src/runtime/types/converters.dart b/drift/lib/src/runtime/types/converters.dart index 6e0ab070..a6b3011b 100644 --- a/drift/lib/src/runtime/types/converters.dart +++ b/drift/lib/src/runtime/types/converters.dart @@ -29,12 +29,11 @@ abstract class TypeConverter { /// 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 -/// [JsonTypeConverter2], it will also be used for the conversion from and to -/// JSON. +/// Unlike the old [JsonTypeConverter] mixin, this more general mixin allows +/// using a different type when serializing to JSON ([J]) than the type used in +/// SQL ([S]). +/// For the cases where the JSON serialization and the mapping to SQL use the +/// same types, it may be more convenient to mix-in [JsonTypeConverter] instead. mixin JsonTypeConverter2 on TypeConverter { /// Map a value from the Data class to json. /// @@ -68,24 +67,17 @@ mixin JsonTypeConverter2 on TypeConverter { /// `.drift` files) refers to a type converter that inherits from /// [JsonTypeConverter], it will also be used for the conversion from and to /// JSON. +/// +/// If the serialized JSON has a different type than the type in SQL ([S]), use +/// a [JsonTypeConverter2]. For instance, this could be useful if your type +/// converter between Dart and SQL maps to a string in SQL, but to a `Map` in +/// JSON. mixin JsonTypeConverter implements JsonTypeConverter2 { @override S toJson(D value) => toSql(value); @override D fromJson(S json) => fromSql(json); - - /// Wraps an [inner] type converter that only considers non-nullable values - /// as a type converter that handles null values too. - /// - /// The returned type converter will use the [inner] type converter for non- - /// null values. Further, `null` is mapped to `null` in both directions (from - /// Dart to SQL and vice-versa). - static JsonTypeConverter2 - asNullable( - JsonTypeConverter2 inner) { - return _NullWrappingTypeConverterWithJson(inner); - } } /// Implementation for an enum to int converter that uses the index of the enum diff --git a/drift/pubspec.yaml b/drift/pubspec.yaml index 4f2df168..a96cc96f 100644 --- a/drift/pubspec.yaml +++ b/drift/pubspec.yaml @@ -1,6 +1,6 @@ name: drift description: Drift is a reactive library to store relational data in Dart and Flutter applications. -version: 2.2.0 +version: 2.3.0-dev repository: https://github.com/simolus3/drift homepage: https://drift.simonbinder.eu/ issue_tracker: https://github.com/simolus3/drift/issues diff --git a/drift/test/generated/todos.dart b/drift/test/generated/todos.dart index 0168dbf6..02d46fc7 100644 --- a/drift/test/generated/todos.dart +++ b/drift/test/generated/todos.dart @@ -135,8 +135,18 @@ class CustomConverter extends TypeConverter { } class CustomJsonConverter extends CustomConverter - with JsonTypeConverter { + with JsonTypeConverter2 { const CustomJsonConverter(); + + @override + MyCustomObject fromJson(Map json) { + return MyCustomObject(json['data'] as String); + } + + @override + Map toJson(MyCustomObject value) { + return {'data': value.data}; + } } abstract class CategoryTodoCountView extends View { diff --git a/drift/test/generated/todos.g.dart b/drift/test/generated/todos.g.dart index 16c66f2d..c5bf0896 100644 --- a/drift/test/generated/todos.g.dart +++ b/drift/test/generated/todos.g.dart @@ -1252,7 +1252,7 @@ class PureDefault extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return PureDefault( txt: $PureDefaultsTable.$converter0n - .fromJson(serializer.fromJson(json['txt'])), + .fromJson(serializer.fromJson>(json['txt'])), ); } factory PureDefault.fromJsonString(String encodedJson, @@ -1264,8 +1264,8 @@ class PureDefault extends DataClass implements Insertable { Map toJson({ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return { - 'txt': serializer - .toJson($PureDefaultsTable.$converter0n.toJson(txt)), + 'txt': serializer.toJson?>( + $PureDefaultsTable.$converter0n.toJson(txt)), }; } @@ -1373,10 +1373,10 @@ class $PureDefaultsTable extends PureDefaults return $PureDefaultsTable(attachedDatabase, alias); } - static JsonTypeConverter2 $converter0 = - const CustomJsonConverter(); - static JsonTypeConverter2 $converter0n = - JsonTypeConverter2.asNullable($converter0); + static JsonTypeConverter2> + $converter0 = const CustomJsonConverter(); + static JsonTypeConverter2?> + $converter0n = JsonTypeConverter2.asNullable($converter0); } class CategoryTodoCountViewData extends DataClass { diff --git a/drift/test/serialization_test.dart b/drift/test/serialization_test.dart index c04b00b9..f3b8f18d 100644 --- a/drift/test/serialization_test.dart +++ b/drift/test/serialization_test.dart @@ -82,8 +82,12 @@ void main() { }); test('applies json type converter', () { - expect(PureDefault(txt: MyCustomObject('foo')).toJson(), {'txt': 'foo'}); - expect(PureDefault.fromJson({'txt': 'foo'}), + const serialized = { + 'txt': {'data': 'foo'} + }; + + expect(PureDefault(txt: MyCustomObject('foo')).toJson(), serialized); + expect(PureDefault.fromJson(serialized), PureDefault(txt: MyCustomObject('foo'))); }); diff --git a/drift_dev/lib/src/model/types.dart b/drift_dev/lib/src/model/types.dart index 03ff81df..097ed43d 100644 --- a/drift_dev/lib/src/model/types.dart +++ b/drift_dev/lib/src/model/types.dart @@ -137,9 +137,8 @@ extension OperationOnTypes on HasType { return variableTypeCode(); } - /// The dart type that matches the values of this column. For instance, if a - /// table has declared an `IntColumn`, the matching dart type name would be - /// [int]. + /// The Dart type that matches the values of this column when serialized to + /// JSON. String jsonTypeCode() { final converter = typeConverter; if (converter != null) { diff --git a/drift_dev/pubspec.yaml b/drift_dev/pubspec.yaml index 1d7d7b91..7d10c1dc 100644 --- a/drift_dev/pubspec.yaml +++ b/drift_dev/pubspec.yaml @@ -1,6 +1,6 @@ name: drift_dev description: Dev-dependency for users of drift. Contains a the generator and development tools. -version: 2.2.0+1 +version: 2.3.0-dev repository: https://github.com/simolus3/drift homepage: https://drift.simonbinder.eu/ issue_tracker: https://github.com/simolus3/drift/issues @@ -25,7 +25,7 @@ dependencies: io: ^1.0.3 # Drift-specific analysis and apis - drift: '>=2.0.0 <2.3.0' + drift: '>=2.3.0 <2.4.0' sqlite3: '>=0.1.6 <2.0.0' sqlparser: ^0.23.3 From 51f4c282832980f740677ebc014108ff20af3ba6 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 18 Oct 2022 20:39:50 +0200 Subject: [PATCH 6/6] Document new type converters --- docs/pages/docs/Advanced Features/type_converters.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/pages/docs/Advanced Features/type_converters.md b/docs/pages/docs/Advanced Features/type_converters.md index 08eca372..ba71d93f 100644 --- a/docs/pages/docs/Advanced Features/type_converters.md +++ b/docs/pages/docs/Advanced Features/type_converters.md @@ -123,6 +123,13 @@ If you want to apply the same conversion to JSON as well, make your type convert 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 `` 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) +If the JSON type you want to serialize to is different to the SQL type you're +mapping to, you can mix-in `JsonTypeConverter2` instead. +For instance, say you have a type converter mapping to a complex Dart type +`MyObject`. In SQL, you might want to store this as an `String`. But when +serializing to JSON, you may want to use a `Map`. Here, simply +add the `JsonTypeConverter2>` mixin to +your type converter. + +As an alternative to using JSON type converters, you can use a custom [`ValueSerializer`](https://drift.simonbinder.eu/api/drift/valueserializer-class) and pass it to the serialization methods.