Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JsonEnum.valueField for encoding enhanced enum values #1203

Merged
merged 3 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}
kevmoo marked this conversation as resolved.
Show resolved Hide resolved
} 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')
kevmoo marked this conversation as resolved.
Show resolved Hide resolved
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 {}