diff --git a/example/lib/example.dart b/example/lib/example.dart index 0cd0e650f..2605e603f 100644 --- a/example/lib/example.dart +++ b/example/lib/example.dart @@ -25,10 +25,6 @@ class Person { Person(this.firstName, this.lastName, this.dateOfBirth, {this.middleName, this.lastOrder, List orders}) : orders = orders ?? []; - - factory Person.fromJson(Map json) => _$PersonFromJson(json); - - Map toJson() => _$PersonToJson(this); } @JsonSerializable(includeIfNull: false) @@ -49,10 +45,6 @@ class Order { Order(this.date); - factory Order.fromJson(Map json) => _$OrderFromJson(json); - - Map toJson() => _$OrderToJson(this); - static Duration _durationFromMilliseconds(int milliseconds) => milliseconds == null ? null : Duration(milliseconds: milliseconds); @@ -73,10 +65,6 @@ class Item { bool isRushed; Item(); - - factory Item.fromJson(Map json) => _$ItemFromJson(json); - - Map toJson() => _$ItemToJson(this); } @JsonLiteral('data.json') diff --git a/example/lib/example.g.dart b/example/lib/example.g.dart index 44599902e..2565e6eac 100644 --- a/example/lib/example.g.dart +++ b/example/lib/example.g.dart @@ -6,6 +6,11 @@ part of 'example.dart'; // JsonSerializableGenerator // ************************************************************************** +extension PersonExt on Person { + static Person fromJson(Map json) => _$PersonFromJson(json); + Map toJson() => _$PersonToJson(this); +} + Person _$PersonFromJson(Map json) { return Person( json['firstName'] as String, @@ -16,7 +21,7 @@ Person _$PersonFromJson(Map json) { ? null : DateTime.parse(json['last-order'] as String), orders: (json['orders'] as List) - .map((e) => Order.fromJson(e as Map)) + .map((e) => OrderExt.fromJson(e as Map)) .toList(), ); } @@ -40,6 +45,11 @@ Map _$PersonToJson(Person instance) { return val; } +extension OrderExt on Order { + static Order fromJson(Map json) => _$OrderFromJson(json); + Map toJson() => _$OrderToJson(this); +} + Order _$OrderFromJson(Map json) { return Order( Order._dateTimeFromEpochUs(json['date'] as int), @@ -49,7 +59,7 @@ Order _$OrderFromJson(Map json) { ..isRushed = json['isRushed'] as bool ..item = json['item'] == null ? null - : Item.fromJson(json['item'] as Map) + : ItemExt.fromJson(json['item'] as Map) ..prepTime = Order._durationFromMilliseconds(json['prep-time'] as int); } @@ -71,6 +81,11 @@ Map _$OrderToJson(Order instance) { return val; } +extension ItemExt on Item { + static Item fromJson(Map json) => _$ItemFromJson(json); + Map toJson() => _$ItemToJson(this); +} + Item _$ItemFromJson(Map json) { return Item() ..count = json['count'] as int diff --git a/example/lib/json_converter_example.g.dart b/example/lib/json_converter_example.g.dart index 709e2e79b..acaf10163 100644 --- a/example/lib/json_converter_example.g.dart +++ b/example/lib/json_converter_example.g.dart @@ -6,6 +6,12 @@ part of 'json_converter_example.dart'; // JsonSerializableGenerator // ************************************************************************** +extension GenericCollectionExt on GenericCollection { + static GenericCollection fromJson(Map json) => + _$GenericCollectionFromJson(json); + Map toJson() => _$GenericCollectionToJson(this); +} + GenericCollection _$GenericCollectionFromJson(Map json) { return GenericCollection( page: json['page'] as int, @@ -24,6 +30,12 @@ Map _$GenericCollectionToJson( 'results': instance.results?.map(_Converter().toJson)?.toList(), }; +extension CustomResultExt on CustomResult { + static CustomResult fromJson(Map json) => + _$CustomResultFromJson(json); + Map toJson() => _$CustomResultToJson(this); +} + CustomResult _$CustomResultFromJson(Map json) { return CustomResult( json['name'] as String, diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 888586fe0..77a4dba8b 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -16,5 +16,7 @@ dev_dependencies: test: ^1.6.0 dependency_overrides: + json_annotation: + path: ../json_annotation json_serializable: path: ../json_serializable diff --git a/example/test/example_test.dart b/example/test/example_test.dart index 28a71c7a4..75182cd67 100644 --- a/example/test/example_test.dart +++ b/example/test/example_test.dart @@ -15,7 +15,7 @@ void main() { final personJson = _encode(person); final person2 = - Person.fromJson(json.decode(personJson) as Map); + PersonExt.fromJson(json.decode(personJson) as Map); expect(person.firstName, person2.firstName); expect(person.lastName, person2.lastName); diff --git a/json_annotation/CHANGELOG.md b/json_annotation/CHANGELOG.md index 3dedfef66..271d81c06 100644 --- a/json_annotation/CHANGELOG.md +++ b/json_annotation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.2 + +- Added extension to class to remove boilerplace code + ## 3.0.1 - Require at least Dart `2.6.0`. diff --git a/json_annotation/lib/src/json_serializable.dart b/json_annotation/lib/src/json_serializable.dart index 08f74940f..681990014 100644 --- a/json_annotation/lib/src/json_serializable.dart +++ b/json_annotation/lib/src/json_serializable.dart @@ -47,6 +47,28 @@ class JsonSerializable { /// [CheckedFromJsonException] is thrown. final bool checked; + /// If `Ext` (the default), a named extension `ExampleExt` is created + /// in the generated part file. + /// + /// ```dart + /// extension ExampleExt on Example { + /// } + /// ``` + final String extensionSuffix; + + /// If `true` (the default), a named extension `ExampleExt` is created + /// in the generated part file. + /// + /// ```dart + /// extension ExampleExt on Example { + /// Example static fromJson(Map json) => + /// _$ExampleFromJson(json); + /// + /// Map toJson() => _$ExampleToJson(this); + /// } + /// ``` + final bool createExtension; + /// If `true` (the default), a private, static `_$ExampleFromJson` method /// is created in the generated part file. /// @@ -143,6 +165,8 @@ class JsonSerializable { const JsonSerializable({ this.anyMap, this.checked, + this.createExtension, + this.extensionSuffix, this.createFactory, this.createToJson, this.disallowUnrecognizedKeys, @@ -161,6 +185,8 @@ class JsonSerializable { static const defaults = JsonSerializable( anyMap: false, checked: false, + createExtension: true, + extensionSuffix: 'Ext', createFactory: true, createToJson: true, disallowUnrecognizedKeys: false, @@ -179,6 +205,8 @@ class JsonSerializable { JsonSerializable withDefaults() => JsonSerializable( anyMap: anyMap ?? defaults.anyMap, checked: checked ?? defaults.checked, + createExtension: createExtension ?? defaults.createExtension, + extensionSuffix: extensionSuffix ?? defaults.extensionSuffix, createFactory: createFactory ?? defaults.createFactory, createToJson: createToJson ?? defaults.createToJson, disallowUnrecognizedKeys: diff --git a/json_annotation/lib/src/json_serializable.g.dart b/json_annotation/lib/src/json_serializable.g.dart index 68cec418b..61e19db2c 100644 --- a/json_annotation/lib/src/json_serializable.g.dart +++ b/json_annotation/lib/src/json_serializable.g.dart @@ -12,17 +12,20 @@ JsonSerializable _$JsonSerializableFromJson(Map json) { 'any_map', 'checked', 'create_factory', + 'create_extension', 'create_to_json', 'disallow_unrecognized_keys', 'explicit_to_json', 'field_rename', 'ignore_unannotated', 'include_if_null', - 'nullable', + 'nullable' ]); final val = JsonSerializable( anyMap: $checkedConvert(json, 'any_map', (v) => v as bool), checked: $checkedConvert(json, 'checked', (v) => v as bool), + createExtension: + $checkedConvert(json, 'create_extension', (v) => v as bool), createFactory: $checkedConvert(json, 'create_factory', (v) => v as bool), createToJson: $checkedConvert(json, 'create_to_json', (v) => v as bool), disallowUnrecognizedKeys: @@ -39,13 +42,14 @@ JsonSerializable _$JsonSerializableFromJson(Map json) { return val; }, fieldKeyMap: const { 'anyMap': 'any_map', + 'createExtension': 'create_extension', 'createFactory': 'create_factory', 'createToJson': 'create_to_json', 'disallowUnrecognizedKeys': 'disallow_unrecognized_keys', 'explicitToJson': 'explicit_to_json', 'fieldRename': 'field_rename', 'ignoreUnannotated': 'ignore_unannotated', - 'includeIfNull': 'include_if_null', + 'includeIfNull': 'include_if_null' }); } @@ -54,6 +58,7 @@ Map _$JsonSerializableToJson(JsonSerializable instance) => 'any_map': instance.anyMap, 'checked': instance.checked, 'create_factory': instance.createFactory, + 'create_extension': instance.createExtension, 'create_to_json': instance.createToJson, 'disallow_unrecognized_keys': instance.disallowUnrecognizedKeys, 'explicit_to_json': instance.explicitToJson, @@ -63,29 +68,41 @@ Map _$JsonSerializableToJson(JsonSerializable instance) => 'nullable': instance.nullable, }; -T _$enumDecode(Map enumValues, dynamic source) { +T _$enumDecode( + Map enumValues, + dynamic source, { + T unknownValue, +}) { if (source == null) { throw ArgumentError('A value must be provided. Supported values: ' '${enumValues.values.join(', ')}'); } - return enumValues.entries - .singleWhere((e) => e.value == source, - orElse: () => throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}')) - .key; + + final value = enumValues.entries + .singleWhere((e) => e.value == source, orElse: () => null) + ?.key; + + if (value == null && unknownValue == null) { + throw ArgumentError('`$source` is not one of the supported values: ' + '${enumValues.values.join(', ')}'); + } + return value ?? unknownValue; } -T _$enumDecodeNullable(Map enumValues, dynamic source) { +T _$enumDecodeNullable( + Map enumValues, + dynamic source, { + T unknownValue, +}) { if (source == null) { return null; } - return _$enumDecode(enumValues, source); + return _$enumDecode(enumValues, source, unknownValue: unknownValue); } -const _$FieldRenameEnumMap = { +const _$FieldRenameEnumMap = { FieldRename.none: 'none', FieldRename.kebab: 'kebab', FieldRename.snake: 'snake', - FieldRename.pascal: 'pascal' + FieldRename.pascal: 'pascal', }; diff --git a/json_annotation/pubspec.yaml b/json_annotation/pubspec.yaml index 3617b673b..7baedcde4 100644 --- a/json_annotation/pubspec.yaml +++ b/json_annotation/pubspec.yaml @@ -1,5 +1,5 @@ name: json_annotation -version: 3.0.1 +version: 3.0.2 description: >- Classes and helper functions that support JSON code generation via the `json_serializable` package. diff --git a/json_serializable/CHANGELOG.md b/json_serializable/CHANGELOG.md index a1142fda3..0ea84d1ec 100644 --- a/json_serializable/CHANGELOG.md +++ b/json_serializable/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.4.1-dev + +- Generated extension code to remove boilerplate code + ## 3.4.0-dev - Added support for `double` constants as default values. diff --git a/json_serializable/doc/doc.md b/json_serializable/doc/doc.md index 569a63d58..6594599a3 100644 --- a/json_serializable/doc/doc.md +++ b/json_serializable/doc/doc.md @@ -2,6 +2,7 @@ | -------------------------- | ------------------------------------------- | --------------------------- | | any_map | [JsonSerializable.anyMap] | | | checked | [JsonSerializable.checked] | | +| create_extension | [JsonSerializable.createExtension] | | | create_factory | [JsonSerializable.createFactory] | | | create_to_json | [JsonSerializable.createToJson] | | | disallow_unrecognized_keys | [JsonSerializable.disallowUnrecognizedKeys] | | @@ -19,23 +20,24 @@ | | | [JsonKey.toJson] | | | | [JsonKey.unknownEnumValue] | -[JsonSerializable.anyMap]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/anyMap.html -[JsonSerializable.checked]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/checked.html -[JsonSerializable.createFactory]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/createFactory.html -[JsonSerializable.createToJson]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/createToJson.html -[JsonSerializable.disallowUnrecognizedKeys]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/disallowUnrecognizedKeys.html -[JsonSerializable.explicitToJson]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/explicitToJson.html -[JsonSerializable.fieldRename]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/fieldRename.html -[JsonSerializable.ignoreUnannotated]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/ignoreUnannotated.html -[JsonSerializable.includeIfNull]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/includeIfNull.html -[JsonKey.includeIfNull]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/includeIfNull.html -[JsonSerializable.nullable]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonSerializable/nullable.html -[JsonKey.nullable]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/nullable.html -[JsonKey.defaultValue]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/defaultValue.html -[JsonKey.disallowNullValue]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/disallowNullValue.html -[JsonKey.fromJson]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/fromJson.html -[JsonKey.ignore]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/ignore.html -[JsonKey.name]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/name.html -[JsonKey.required]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/required.html -[JsonKey.toJson]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/toJson.html -[JsonKey.unknownEnumValue]: https://pub.dev/documentation/json_annotation/3.0.1/json_annotation/JsonKey/unknownEnumValue.html +[JsonSerializable.anyMap]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/anyMap.html +[JsonSerializable.checked]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/checked.html +[JsonSerializable.createExtension]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/createExtension.html +[JsonSerializable.createFactory]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/createFactory.html +[JsonSerializable.createToJson]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/createToJson.html +[JsonSerializable.disallowUnrecognizedKeys]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/disallowUnrecognizedKeys.html +[JsonSerializable.explicitToJson]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/explicitToJson.html +[JsonSerializable.fieldRename]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/fieldRename.html +[JsonSerializable.ignoreUnannotated]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/ignoreUnannotated.html +[JsonSerializable.includeIfNull]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/includeIfNull.html +[JsonKey.includeIfNull]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/includeIfNull.html +[JsonSerializable.nullable]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonSerializable/nullable.html +[JsonKey.nullable]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/nullable.html +[JsonKey.defaultValue]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/defaultValue.html +[JsonKey.disallowNullValue]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/disallowNullValue.html +[JsonKey.fromJson]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/fromJson.html +[JsonKey.ignore]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/ignore.html +[JsonKey.name]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/name.html +[JsonKey.required]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/required.html +[JsonKey.toJson]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/toJson.html +[JsonKey.unknownEnumValue]: https://pub.dev/documentation/json_annotation/3.0.2/json_annotation/JsonKey/unknownEnumValue.html diff --git a/json_serializable/example/example.dart b/json_serializable/example/example.dart index b62fd38f1..9197c0fb2 100644 --- a/json_serializable/example/example.dart +++ b/json_serializable/example/example.dart @@ -12,6 +12,4 @@ class Person { final String lastName; final DateTime dateOfBirth; Person({this.firstName, this.lastName, this.dateOfBirth}); - factory Person.fromJson(Map json) => _$PersonFromJson(json); - Map toJson() => _$PersonToJson(this); } diff --git a/json_serializable/example/example.g.dart b/json_serializable/example/example.g.dart index 4d790cf8c..fa3ed849e 100644 --- a/json_serializable/example/example.g.dart +++ b/json_serializable/example/example.g.dart @@ -6,6 +6,11 @@ part of 'example.dart'; // JsonSerializableGenerator // ************************************************************************** +extension PersonExt on Person { + static Person fromJson(Map json) => _$PersonFromJson(json); + Map toJson() => _$PersonToJson(this); +} + Person _$PersonFromJson(Map json) { return Person( firstName: json['firstName'] as String, diff --git a/json_serializable/lib/src/extension_helper.dart b/json_serializable/lib/src/extension_helper.dart new file mode 100644 index 000000000..f0957c1db --- /dev/null +++ b/json_serializable/lib/src/extension_helper.dart @@ -0,0 +1,18 @@ +import './helper_core.dart'; + +abstract class ExtensionHelper implements HelperCore { + Iterable createExtension() sync* { + assert(config.createExtension); + final buffer = StringBuffer() + ..writeln('extension $extensionName on $className { ' + 'static $className fromJson(Map json) => ${prefix}FromJson(json); ' + 'Map toJson() => ${prefix}ToJson(this); ' + '}'); + + yield buffer.toString(); + } + + String get className => element.name; + + String get extensionName => '$className${config.extensionSuffix}'; +} diff --git a/json_serializable/lib/src/json_serializable_generator.dart b/json_serializable/lib/src/json_serializable_generator.dart index 230457e72..d74ac30b3 100644 --- a/json_serializable/lib/src/json_serializable_generator.dart +++ b/json_serializable/lib/src/json_serializable_generator.dart @@ -9,6 +9,7 @@ import 'package:source_gen/source_gen.dart'; import 'decode_helper.dart'; import 'encoder_helper.dart'; +import 'extension_helper.dart'; import 'field_helpers.dart'; import 'helper_core.dart'; import 'type_helper.dart'; @@ -97,7 +98,8 @@ class JsonSerializableGenerator } } -class _GeneratorHelper extends HelperCore with EncodeHelper, DecodeHelper { +class _GeneratorHelper extends HelperCore + with EncodeHelper, DecodeHelper, ExtensionHelper { final JsonSerializableGenerator _generator; final _addedMembers = {}; @@ -146,6 +148,10 @@ class _GeneratorHelper extends HelperCore with EncodeHelper, DecodeHelper { }, ); + if(config.createExtension) { + yield* createExtension(); + } + var accessibleFieldSet = accessibleFields.values.toSet(); if (config.createFactory) { final createResult = createFactory(accessibleFields, unavailableReasons); diff --git a/json_serializable/lib/src/type_helpers/json_helper.dart b/json_serializable/lib/src/type_helpers/json_helper.dart index e106604f5..74c639497 100644 --- a/json_serializable/lib/src/type_helpers/json_helper.dart +++ b/json_serializable/lib/src/type_helpers/json_helper.dart @@ -70,7 +70,10 @@ class JsonHelper extends TypeHelper { // TODO: the type could be imported from a library with a prefix! // https://github.com/google/json_serializable.dart/issues/19 - output = '${targetType.element.name}.fromJson($output)'; + + final suffix = + context.config.createExtension ? context.config.extensionSuffix : ''; + output = '${targetType.element.name}$suffix.fromJson($output)'; return commonNullPrefix(context.nullable, expression, output).toString(); } diff --git a/json_serializable/lib/src/utils.dart b/json_serializable/lib/src/utils.dart index 40ac04a20..f619f29f1 100644 --- a/json_serializable/lib/src/utils.dart +++ b/json_serializable/lib/src/utils.dart @@ -78,6 +78,8 @@ T enumValueForDartObject( JsonSerializable _valueForAnnotation(ConstantReader reader) => JsonSerializable( anyMap: reader.read('anyMap').literalValue as bool, checked: reader.read('checked').literalValue as bool, + createExtension: reader.read('createExtension').literalValue as bool, + extensionSuffix: reader.read('extensionSuffix').literalValue as String, createFactory: reader.read('createFactory').literalValue as bool, createToJson: reader.read('createToJson').literalValue as bool, disallowUnrecognizedKeys: @@ -100,6 +102,8 @@ JsonSerializable mergeConfig(JsonSerializable config, ConstantReader reader) { return JsonSerializable( anyMap: annotation.anyMap ?? config.anyMap, checked: annotation.checked ?? config.checked, + createExtension: annotation.createExtension ?? config.createExtension, + extensionSuffix: annotation.extensionSuffix ?? config.extensionSuffix, createFactory: annotation.createFactory ?? config.createFactory, createToJson: annotation.createToJson ?? config.createToJson, disallowUnrecognizedKeys: diff --git a/json_serializable/pubspec.yaml b/json_serializable/pubspec.yaml index 03ecfd590..b1f9127a4 100644 --- a/json_serializable/pubspec.yaml +++ b/json_serializable/pubspec.yaml @@ -1,5 +1,5 @@ name: json_serializable -version: 3.4.0-dev +version: 3.4.1-dev description: >- Automatically generate code for converting to and from JSON by annotating Dart classes. @@ -32,3 +32,7 @@ dev_dependencies: source_gen_test: ^0.1.0 test: ^1.6.0 yaml: ^2.1.13 + +dependency_overrides: + json_annotation: + path: ../json_annotation