diff --git a/json_annotation/lib/src/json_converter.dart b/json_annotation/lib/src/json_converter.dart index 8736d2c42..fefe88ece 100644 --- a/json_annotation/lib/src/json_converter.dart +++ b/json_annotation/lib/src/json_converter.dart @@ -26,16 +26,15 @@ /// /// ```dart /// @JsonSerializable() -/// @MyJsonConverter() /// class Example { /// @MyJsonConverter() /// final Value property; /// } /// ``` /// -/// Or finally, passed to the annotation: +/// Or finally, passed to the `@JsonSerializable` annotation: /// -///```dart +/// ```dart /// @JsonSerializable(converters: [MyJsonConverter()]) /// class Example {} /// ``` diff --git a/json_serializable/CHANGELOG.md b/json_serializable/CHANGELOG.md index d6366ae68..7270ca696 100644 --- a/json_serializable/CHANGELOG.md +++ b/json_serializable/CHANGELOG.md @@ -1,6 +1,7 @@ ## 6.4.2-dev -- Support `ConstructorElement` which allows using tear-off constructors. +- Allow constructors to be passed to `JsonKey` parameters that support + `Function` types. ## 6.4.1 diff --git a/json_serializable/README.md b/json_serializable/README.md index 2d7b4c66d..ac0eebdf7 100644 --- a/json_serializable/README.md +++ b/json_serializable/README.md @@ -171,15 +171,94 @@ customize the encoding/decoding of any type, you have a few options. `toJson()` function to the type. Note: while you can use `json_serializable` for these types, you don't have to! The generator code only looks for these methods. It doesn't care how they were created. + + ```dart + @JsonSerializable() + class Sample1 { + Sample1(this.value); + + factory Sample1.fromJson(Map json) => + _$Sample1FromJson(json); + + // Sample2 is NOT annotated with @JsonSerializable(), but that's okay + // The class has a `fromJson` constructor and a `toJson` method, which is + // all that is required. + final Sample2 value; + + Map toJson() => _$Sample1ToJson(this); + } + + class Sample2 { + Sample2(this.value); + + // The convention is for `fromJson` to take a single parameter of type + // `Map` but any JSON-compatible type is allowed. + factory Sample2.fromJson(int value) => Sample2(value); + final int value; + + // The convention is for `toJson` to take return a type of + // `Map` but any JSON-compatible type is allowed. + int toJson() => value; + } + ``` + 1. Use the [`JsonKey.toJson`] and [`JsonKey.fromJson`] properties to specify custom conversions on the annotated field. The functions specified must be top-level or static. See the documentation of these properties for details. + + ```dart + @JsonSerializable() + class Sample3 { + Sample3(this.value); + + factory Sample3.fromJson(Map json) => + _$Sample3FromJson(json); + + @JsonKey( + toJson: _toJson, + fromJson: _fromJson, + ) + final DateTime value; + + Map toJson() => _$Sample3ToJson(this); + + static int _toJson(DateTime value) => value.millisecondsSinceEpoch; + static DateTime _fromJson(int value) => + DateTime.fromMillisecondsSinceEpoch(value); + } + ``` + 1. Create an implementation of [`JsonConverter`] and annotate either the corresponding field or the containing class. [`JsonConverter`] is convenient if you want to use the same conversion logic on many fields. It also allows you to support a type within collections. Check out [these examples](https://github.com/google/json_serializable.dart/blob/master/example/lib/json_converter_example.dart). + ```dart + @JsonSerializable() + class Sample4 { + Sample4(this.value); + + factory Sample4.fromJson(Map json) => + _$Sample4FromJson(json); + + @EpochDateTimeConverter() + final DateTime value; + + Map toJson() => _$Sample4ToJson(this); + } + + class EpochDateTimeConverter implements JsonConverter { + const EpochDateTimeConverter(); + + @override + DateTime fromJson(int json) => DateTime.fromMillisecondsSinceEpoch(json); + + @override + int toJson(DateTime object) => object.millisecondsSinceEpoch; + } + ``` + # Build configuration Aside from setting arguments on the associated annotation classes, you can also diff --git a/json_serializable/build.yaml b/json_serializable/build.yaml index 03933fb6e..1b5d731c9 100644 --- a/json_serializable/build.yaml +++ b/json_serializable/build.yaml @@ -18,6 +18,7 @@ targets: - test/kitchen_sink/* - test/literal/* - test/supported_types/* + - tool/readme/* json_serializable|_test_builder: generate_for: diff --git a/json_serializable/tool/readme/enum_example.dart b/json_serializable/tool/readme/enum_example.dart deleted file mode 100644 index f3f48b017..000000000 --- a/json_serializable/tool/readme/enum_example.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -// # simple_example -enum StatusCode { - @JsonValue(200) - success, - @JsonValue(301) - movedPermanently, - @JsonValue(302) - found, - @JsonValue(500) - internalServerError, -} - -// # enhanced_example -@JsonEnum(valueField: 'code') -enum StatusCodeEnhanced { - success(200), - movedPermanently(301), - found(302), - internalServerError(500); - - const StatusCodeEnhanced(this.code); - final int code; -} diff --git a/json_serializable/tool/readme/readme_examples.dart b/json_serializable/tool/readme/readme_examples.dart new file mode 100644 index 000000000..0abc70fc5 --- /dev/null +++ b/json_serializable/tool/readme/readme_examples.dart @@ -0,0 +1,101 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'readme_examples.g.dart'; + +// # simple_example +enum StatusCode { + @JsonValue(200) + success, + @JsonValue(301) + movedPermanently, + @JsonValue(302) + found, + @JsonValue(500) + internalServerError, +} + +// # enhanced_example +@JsonEnum(valueField: 'code') +enum StatusCodeEnhanced { + success(200), + movedPermanently(301), + found(302), + internalServerError(500); + + const StatusCodeEnhanced(this.code); + final int code; +} + +// # to_from +@JsonSerializable() +class Sample1 { + Sample1(this.value); + + factory Sample1.fromJson(Map json) => + _$Sample1FromJson(json); + + // Sample2 is NOT annotated with @JsonSerializable(), but that's okay + // The class has a `fromJson` constructor and a `toJson` method, which is + // all that is required. + final Sample2 value; + + Map toJson() => _$Sample1ToJson(this); +} + +class Sample2 { + Sample2(this.value); + + // The convention is for `fromJson` to take a single parameter of type + // `Map` but any JSON-compatible type is allowed. + factory Sample2.fromJson(int value) => Sample2(value); + final int value; + + // The convention is for `toJson` to take return a type of + // `Map` but any JSON-compatible type is allowed. + int toJson() => value; +} + +// # json_key +@JsonSerializable() +class Sample3 { + Sample3(this.value); + + factory Sample3.fromJson(Map json) => + _$Sample3FromJson(json); + + @JsonKey( + toJson: _toJson, + fromJson: _fromJson, + ) + final DateTime value; + + Map toJson() => _$Sample3ToJson(this); + + static int _toJson(DateTime value) => value.millisecondsSinceEpoch; + static DateTime _fromJson(int value) => + DateTime.fromMillisecondsSinceEpoch(value); +} + +// # json_converter +@JsonSerializable() +class Sample4 { + Sample4(this.value); + + factory Sample4.fromJson(Map json) => + _$Sample4FromJson(json); + + @EpochDateTimeConverter() + final DateTime value; + + Map toJson() => _$Sample4ToJson(this); +} + +class EpochDateTimeConverter implements JsonConverter { + const EpochDateTimeConverter(); + + @override + DateTime fromJson(int json) => DateTime.fromMillisecondsSinceEpoch(json); + + @override + int toJson(DateTime object) => object.millisecondsSinceEpoch; +} diff --git a/json_serializable/tool/readme/readme_examples.g.dart b/json_serializable/tool/readme/readme_examples.g.dart new file mode 100644 index 000000000..be265dd6d --- /dev/null +++ b/json_serializable/tool/readme/readme_examples.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: lines_longer_than_80_chars, text_direction_code_point_in_literal + +part of 'readme_examples.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Sample1 _$Sample1FromJson(Map json) => Sample1( + Sample2.fromJson(json['value'] as int), + ); + +Map _$Sample1ToJson(Sample1 instance) => { + 'value': instance.value, + }; + +Sample3 _$Sample3FromJson(Map json) => Sample3( + Sample3._fromJson(json['value'] as int), + ); + +Map _$Sample3ToJson(Sample3 instance) => { + 'value': Sample3._toJson(instance.value), + }; + +Sample4 _$Sample4FromJson(Map json) => Sample4( + const EpochDateTimeConverter().fromJson(json['value'] as int), + ); + +Map _$Sample4ToJson(Sample4 instance) => { + 'value': const EpochDateTimeConverter().toJson(instance.value), + }; diff --git a/json_serializable/tool/readme/readme_template.md b/json_serializable/tool/readme/readme_template.md index 2f3c39551..8af2bac78 100644 --- a/json_serializable/tool/readme/readme_template.md +++ b/json_serializable/tool/readme/readme_template.md @@ -75,14 +75,14 @@ Annotate `enum` types with `ja:JsonEnum` (new in `json_annotation` 4.2.0) to: Annotate `enum` values with `ja:JsonValue` to specify the encoded value to map to target `enum` entries. Values can be of type `core:String` or `core:int`. - + If you are annotating an [enhanced enum](https://dart.dev/guides/language/language-tour#declaring-enhanced-enums), you can use `ja:JsonEnum.valueField` to specify the field to use for the serialized value. - + # Supported types @@ -107,15 +107,23 @@ customize the encoding/decoding of any type, you have a few options. `toJson()` function to the type. Note: while you can use `json_serializable` for these types, you don't have to! The generator code only looks for these methods. It doesn't care how they were created. + + + 1. Use the `ja:JsonKey.toJson` and `ja:JsonKey.fromJson` properties to specify custom conversions on the annotated field. The functions specified must be top-level or static. See the documentation of these properties for details. + + + 1. Create an implementation of `ja:JsonConverter` and annotate either the corresponding field or the containing class. `ja:JsonConverter` is convenient if you want to use the same conversion logic on many fields. It also allows you to support a type within collections. Check out [these examples](https://github.com/google/json_serializable.dart/blob/master/example/lib/json_converter_example.dart). + + # Build configuration Aside from setting arguments on the associated annotation classes, you can also diff --git a/json_serializable/tool/readme_builder.dart b/json_serializable/tool/readme_builder.dart index be97e7832..76156a219 100644 --- a/json_serializable/tool/readme_builder.dart +++ b/json_serializable/tool/readme_builder.dart @@ -24,7 +24,7 @@ class _ReadmeBuilder extends Builder { final replacements = { ...await buildStep.getExampleContent('example/example.dart'), ...await buildStep.getExampleContent('example/example.g.dart'), - ...await buildStep.getExampleContent('tool/readme/enum_example.dart'), + ...await buildStep.getExampleContent('tool/readme/readme_examples.dart'), 'supported_types': _classCleanAndSort(supportedTypes()), 'collection_types': _classCleanAndSort(collectionTypes()), 'map_key_types': _classCleanAndSort(mapKeyTypes), @@ -44,11 +44,14 @@ class _ReadmeBuilder extends Builder { final foundClasses = SplayTreeMap(compareAsciiLowerCase); final theMap = { - RegExp(r''): (match) { - final replacementKey = match.group(1)!; + RegExp(r'( *)'): (match) { + final replacementKey = match.group(2)!; availableKeys.remove(replacementKey); - return (replacements[replacementKey] ?? '*MISSING! `$replacementKey`*') - .trim(); + final replacement = + (replacements[replacementKey] ?? '*MISSING! `$replacementKey`*') + .trim(); + + return replacement.indent(match.group(1)!); }, RegExp(r'`(\w+):(\w+)(\.\w+)?`'): (match) { final context = match.group(1)!; @@ -278,4 +281,7 @@ ${trim()} ```'''; static const _blockComment = r'// # '; + + String indent(String indent) => + LineSplitter.split(this).map((e) => '$indent$e'.trimRight()).join('\n'); }