Skip to content

Commit

Permalink
Add JsonEnum.valueField for encoding enhanced enum values (#1203)
Browse files Browse the repository at this point in the history
Fixes #1147

Prepare to release json_annotation v4.7.0
  • Loading branch information
kevmoo committed Sep 20, 2022
1 parent 572e813 commit 9143b83
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 25 deletions.
2 changes: 2 additions & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@ dev_dependencies:
test: ^1.16.0

dependency_overrides:
json_annotation:
path: ../json_annotation
json_serializable:
path: ../json_serializable
5 changes: 4 additions & 1 deletion json_annotation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 4.6.1-dev
## 4.7.0

- Added `JsonEnum.valueField` which allows specifying a field in an
"enhanced enum" to use for serialization instead of specifying each value
individually with `JsonValue`.
- Require Dart SDK 2.17

## 4.6.0
Expand Down
8 changes: 8 additions & 0 deletions json_annotation/lib/src/json_enum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class JsonEnum {
const JsonEnum({
this.alwaysCreate = false,
this.fieldRename = FieldRename.none,
this.valueField,
});

/// If `true`, `_$[enum name]EnumMap` is generated for in library containing
Expand All @@ -34,4 +35,11 @@ class JsonEnum {
/// Note: the value for [JsonValue.value] takes precedence over this option
/// for entries annotated with [JsonValue].
final FieldRename fieldRename;

/// Specifies the field within an "enhanced enum" to use as the value
/// to use for serialization.
///
/// If an individual `enum` element is annotated with `@JsonValue`
/// that value still takes precedence.
final String? valueField;
}
2 changes: 1 addition & 1 deletion json_annotation/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_annotation
version: 4.6.1-dev
version: 4.7.0
description: >-
Classes and helper functions that support JSON code generation via the
`json_serializable` package.
Expand Down
7 changes: 7 additions & 0 deletions json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 6.4.0

- Add support for `JsonEnum.valueField` which allows specifying a field in an
"enhanced enum" to use for serialization instead of specifying each value
individually with `JsonValue
- Require `json_annotation: '>=4.7.0 <4.8.0'`

## 6.3.2

- Require `analyzer: '>=4.6.0 <6.0.0'`
Expand Down
16 changes: 8 additions & 8 deletions json_serializable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,14 @@ targets:
[`Enum`]: https://api.dart.dev/stable/dart-core/Enum-class.html
[`int`]: https://api.dart.dev/stable/dart-core/int-class.html
[`Iterable`]: https://api.dart.dev/stable/dart-core/Iterable-class.html
[`JsonConverter`]: https://pub.dev/documentation/json_annotation/4.6.0/json_annotation/JsonConverter-class.html
[`JsonEnum`]: https://pub.dev/documentation/json_annotation/4.6.0/json_annotation/JsonEnum-class.html
[`JsonKey.fromJson`]: https://pub.dev/documentation/json_annotation/4.6.0/json_annotation/JsonKey/fromJson.html
[`JsonKey.toJson`]: https://pub.dev/documentation/json_annotation/4.6.0/json_annotation/JsonKey/toJson.html
[`JsonKey`]: https://pub.dev/documentation/json_annotation/4.6.0/json_annotation/JsonKey-class.html
[`JsonLiteral`]: https://pub.dev/documentation/json_annotation/4.6.0/json_annotation/JsonLiteral-class.html
[`JsonSerializable`]: https://pub.dev/documentation/json_annotation/4.6.0/json_annotation/JsonSerializable-class.html
[`JsonValue`]: https://pub.dev/documentation/json_annotation/4.6.0/json_annotation/JsonValue-class.html
[`JsonConverter`]: https://pub.dev/documentation/json_annotation/4.7.0/json_annotation/JsonConverter-class.html
[`JsonEnum`]: https://pub.dev/documentation/json_annotation/4.7.0/json_annotation/JsonEnum-class.html
[`JsonKey.fromJson`]: https://pub.dev/documentation/json_annotation/4.7.0/json_annotation/JsonKey/fromJson.html
[`JsonKey.toJson`]: https://pub.dev/documentation/json_annotation/4.7.0/json_annotation/JsonKey/toJson.html
[`JsonKey`]: https://pub.dev/documentation/json_annotation/4.7.0/json_annotation/JsonKey-class.html
[`JsonLiteral`]: https://pub.dev/documentation/json_annotation/4.7.0/json_annotation/JsonLiteral-class.html
[`JsonSerializable`]: https://pub.dev/documentation/json_annotation/4.7.0/json_annotation/JsonSerializable-class.html
[`JsonValue`]: https://pub.dev/documentation/json_annotation/4.7.0/json_annotation/JsonValue-class.html
[`List`]: https://api.dart.dev/stable/dart-core/List-class.html
[`Map`]: https://api.dart.dev/stable/dart-core/Map-class.html
[`num`]: https://api.dart.dev/stable/dart-core/num-class.html
Expand Down
2 changes: 1 addition & 1 deletion json_serializable/lib/src/check_dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:pubspec_parse/pubspec_parse.dart';

const _productionDirectories = {'lib', 'bin'};
const _annotationPkgName = 'json_annotation';
final requiredJsonAnnotationMinVersion = Version.parse('4.6.0');
final requiredJsonAnnotationMinVersion = Version.parse('4.7.0');

Future<void> pubspecHasRightVersion(BuildStep buildStep) async {
final segments = buildStep.inputId.pathSegments;
Expand Down
58 changes: 47 additions & 11 deletions json_serializable/lib/src/enum_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,50 @@ String? enumValueMapFromType(

dynamic fieldValue;
if (annotation == null) {
fieldValue = encodedFieldName(jsonEnum.fieldRename, fe.name);
if (jsonEnum.valueField != null) {
// TODO: fieldRename is pointless here!!! At least log a warning!

final fieldElementType = fe.type.element2 as EnumElement;

final e = fieldElementType.getField(jsonEnum.valueField!);

if (e == null || e.isStatic) {
throw InvalidGenerationSourceError(
'`JsonEnum.valueField` was set to "${jsonEnum.valueField}", but '
'that is not a valid, instance field on '
'`${typeToCode(targetType)}`.',
element: targetType.element2,
);
}

final reader = ConstantReader(fe.computeConstantValue());
final valueReader = reader.read(jsonEnum.valueField!);
if (valueReader.validValueType) {
fieldValue = valueReader.literalValue;
} else {
throw InvalidGenerationSourceError(
'`JsonEnum.valueField` was set to "${jsonEnum.valueField}", but '
'that field does not have a type of String, int, or null.',
element: targetType.element2,
);
}
} else {
fieldValue = encodedFieldName(jsonEnum.fieldRename, fe.name);
}
} else {
final reader = ConstantReader(annotation);

final valueReader = reader.read('value');

if (valueReader.isString || valueReader.isNull || valueReader.isInt) {
if (valueReader.validValueType) {
fieldValue = valueReader.literalValue;
} else {
final targetTypeCode = typeToCode(targetType);
throw InvalidGenerationSourceError(
'The `JsonValue` annotation on `$targetTypeCode.${fe.name}` does '
'not have a value of type String, int, or null.',
element: fe);
'The `JsonValue` annotation on `$targetTypeCode.${fe.name}` does '
'not have a value of type String, int, or null.',
element: fe,
);
}
}

Expand Down Expand Up @@ -74,10 +104,16 @@ JsonEnum _fromAnnotation(DartObject? dartObject) {
}
final reader = ConstantReader(dartObject);
return JsonEnum(
alwaysCreate: reader.read('alwaysCreate').literalValue as bool,
fieldRename: enumValueForDartObject(
reader.read('fieldRename').objectValue,
FieldRename.values,
(f) => f.toString().split('.')[1],
));
alwaysCreate: reader.read('alwaysCreate').literalValue as bool,
fieldRename: enumValueForDartObject(
reader.read('fieldRename').objectValue,
FieldRename.values,
(f) => f.toString().split('.')[1],
),
valueField: reader.read('valueField').literalValue as String?,
);
}

extension on ConstantReader {
bool get validValueType => isString || isNull || isInt;
}
8 changes: 6 additions & 2 deletions json_serializable/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_serializable
version: 6.3.2
version: 6.4.0-dev
description: >-
Automatically generate code for converting to and from JSON by annotating
Dart classes.
Expand All @@ -16,7 +16,7 @@ dependencies:

# Use a tight version constraint to ensure that a constraint on
# `json_annotation` properly constrains all features it provides.
json_annotation: '>=4.6.0 <4.7.0'
json_annotation: '>=4.7.0 <4.8.0'
meta: ^1.3.0
path: ^1.8.0
pub_semver: ^2.0.0
Expand All @@ -37,3 +37,7 @@ dev_dependencies:
test_descriptor: ^2.0.0
test_process: ^2.0.0
yaml: ^3.0.0

dependency_overrides:
json_annotation:
path: ../json_annotation
13 changes: 13 additions & 0 deletions json_serializable/test/integration/json_enum_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ enum DayType {

Iterable<String> get dayTypeEnumValues => _$DayTypeEnumMap.values;

@JsonEnum(alwaysCreate: true, valueField: 'value')
enum MyStatusCode {
success(200),
@JsonValue(701) // explicit value always takes precedence
weird(601);

const MyStatusCode(this.value);

final int value;
}

Iterable<int> get myStatusCodeEnumValues => _$MyStatusCodeEnumMap.values;

@JsonSerializable(
createToJson: false,
)
Expand Down
5 changes: 5 additions & 0 deletions json_serializable/test/integration/json_enum_example.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion json_serializable/test/json_serializable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ Future<void> main() async {
testAnnotatedElements(
jsonEnumTestReader,
const JsonEnumGenerator(),
expectedAnnotatedTests: {'UnsupportedClass'},
expectedAnnotatedTests: {
'EnumValueIssue1147',
'EnumValueNotAField',
'EnumValueNotSupportType',
'EnumValueWeirdField',
'UnsupportedClass',
},
);
}

Expand Down
61 changes: 61 additions & 0 deletions json_serializable/test/src/_json_enum_test_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,67 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:source_gen_test/annotations.dart';

@ShouldGenerate(r'''
const _$EnumValueIssue1147EnumMap = {
EnumValueIssue1147.success: 200,
EnumValueIssue1147.weird: 601,
};
''')
@JsonEnum(alwaysCreate: true, valueField: 'statusCodeNumber')
enum EnumValueIssue1147 {
success(200),
@JsonValue(601)
weird(701);

const EnumValueIssue1147(this.statusCodeNumber);

final int statusCodeNumber;
}

@ShouldThrow(
'`JsonEnum.valueField` was set to "notAField", but that is not a valid, '
'instance field on `EnumValueNotAField`.',
)
@JsonEnum(alwaysCreate: true, valueField: 'notAField')
enum EnumValueNotAField {
success(200),
@JsonValue(601)
weird(701);

const EnumValueNotAField(this.statusCodeNumber);

final int statusCodeNumber;
}

@ShouldThrow(
'`JsonEnum.valueField` was set to "symbolWeird", but that field does not '
'have a type of String, int, or null.',
)
@JsonEnum(alwaysCreate: true, valueField: 'symbolWeird')
enum EnumValueNotSupportType {
success(#success),
@JsonValue(601)
weird(#weird);

const EnumValueNotSupportType(this.symbolWeird);

final Symbol symbolWeird;
}

@ShouldThrow(
'`JsonEnum.valueField` was set to "values", but that is not a valid, '
'instance field on `EnumValueWeirdField`.',
)
@JsonEnum(alwaysCreate: true, valueField: 'values')
enum EnumValueWeirdField {
success(200),
weird(701);

const EnumValueWeirdField(this.something);

final int something;
}

@ShouldThrow('`@JsonEnum` can only be used on enum elements.')
@JsonEnum() // ignore: invalid_annotation_target
class UnsupportedClass {}

0 comments on commit 9143b83

Please sign in to comment.