Skip to content

Commit 7a550cc

Browse files
authored
feature: allow customizing enum value serialization via annotations (#251)
* Require the latest Dart SDK Annotated enums fail without the CFE * Require the latest pkg:analyzer Enum metadata parsing was just recently added * Require the latest pkg:source_gen Uses multiple return values to allow generating shared helpers * Removed several helpers in json_annotation They can now be generated as part of the source, which minimizes versioning issues with helpers. * Small, but breaking changes to Generator API to return multiple values * Added `addMember` API to Context classes to allow helpers to be generated. * Updated integration test code to include an annotated enum * Updated yaml test code to remove now superfluous convert logic and use annotated enums instead Fixes #38
1 parent 29d3b51 commit 7a550cc

31 files changed

+627
-206
lines changed

json_annotation/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 0.3.0
2+
3+
* Added `JsonValue` class for annotating `enum` fields with a custom
4+
serialization value.
5+
6+
* Removed `$checkAllowedKeys`, `$enumDecode` and `$enumDecodeNullable` which are
7+
no longer needed by the latest release of `package:json_serializable`.
8+
19
## 0.2.9
210

311
* When `FormatException` is caught in "checked mode", use the `message`

json_annotation/lib/src/allowed_keys_helpers.dart

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,6 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
/// **DEPRECATED** Helper function used in generated code when
6-
/// `JsonSerializable.disallowUnrecognizedKeys` is `true`.
7-
///
8-
/// Should not be used directly.
9-
@Deprecated('Code generated with the latest `json_serializable` will use '
10-
'`\$checkKeys` instead. This function will be removed in the next major '
11-
'release.')
12-
void $checkAllowedKeys(Map map, Iterable<String> allowedKeys) {
13-
$checkKeys(map, allowedKeys: allowedKeys?.toList());
14-
}
15-
165
/// Helper function used in generated `fromJson` code when
176
/// `JsonSerializable.disallowUnrecognizedKeys` is true for an annotated type or
187
/// `JsonKey.required` is `true` for any annotated fields.

json_annotation/lib/src/json_serializable.dart

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -194,44 +194,12 @@ class JsonKey {
194194
});
195195
}
196196

197-
// Until enum supports parse: github.com/dart-lang/sdk/issues/33244
198-
/// *Helper function used in generated code with `enum` values – should not be
199-
/// used directly.*
200-
///
201-
/// Returns an enum instance corresponding to [enumValue] from the enum named
202-
/// [enumName] with [values].
203-
///
204-
/// If [enumValue] is null or no corresponding values exists, an `ArgumentError`
205-
/// is thrown.
206-
///
207-
/// Given an enum named `Example`, an invocation would look like
208-
///
209-
/// ```dart
210-
/// $enumDecode('Example', Example.values, 'desiredValue')
211-
/// ```
212-
T $enumDecode<T>(String enumName, List<T> values, String enumValue) =>
213-
values.singleWhere((e) => e.toString() == '$enumName.$enumValue',
214-
orElse: () => throw new ArgumentError(
215-
'`$enumValue` is not one of the supported values: '
216-
'${values.map(_nameForEnumValue).join(', ')}'));
217-
218-
/// *Helper function used in generated code with `enum` values – should not be
219-
/// used directly.*
220-
///
221-
/// Returns an enum instance corresponding to [enumValue] from the enum named
222-
/// [enumName] with [values].
223-
///
224-
/// If [enumValue] is `null`, `null` is returned.
225-
///
226-
/// If no corresponding values exists, an `ArgumentError` is thrown.
227-
///
228-
/// Given an enum named `Example`, an invocation would look like
229-
///
230-
/// ```dart
231-
/// $enumDecodeNullable('Example', Example.values, 'desiredValue')
232-
/// ```
233-
T $enumDecodeNullable<T>(String enumName, List<T> values, String enumValue) =>
234-
enumValue == null ? null : $enumDecode(enumName, values, enumValue);
235-
236-
// Until enum has a name property: github.com/dart-lang/sdk/issues/21712
237-
String _nameForEnumValue(Object value) => value.toString().split('.')[1];
197+
/// An annotation used to specify how a enum value is serialized.
198+
class JsonValue {
199+
/// The value to use when serializing and deserializing.
200+
///
201+
/// Can be a [String] or an [int].
202+
final dynamic value;
203+
204+
const JsonValue(this.value);
205+
}

json_annotation/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: json_annotation
2-
version: 0.2.9
2+
version: 0.3.0-dev
33
description: >-
44
Classes and helper functions that support JSON code generation via the
55
`json_serializable` package.

json_serializable/CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
## 0.6.0
2+
3+
* Now supports changing the serialized values of enums using `JsonValue`.
4+
5+
```dart
6+
enum AutoApply {
7+
none,
8+
dependents,
9+
@JsonValue('all_packages')
10+
allPackages,
11+
@JsonValue('root_package')
12+
rootPackage
13+
}
14+
```
15+
16+
* `JsonSerializableGenerator.generateForAnnotatedElement` now returns
17+
`Iterable<String>` instead of `String`.
18+
19+
* `SerializeContext` and `DeserializeContext` now have an `addMember` function
20+
which allows `TypeHelper` instances to add additional members when handling
21+
a field. This is useful for generating shared helpers, for instance.
22+
123
## 0.5.8
224

325
* Small fixes to support Dart 2 runtime semantics.

json_serializable/lib/src/helper_core.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ abstract class HelperCore {
2020

2121
HelperCore(this.generator, this.element, this.annotation);
2222

23+
void addMember(String memberContent);
24+
2325
String get targetClassReference =>
2426
'${element.name}${genericClassArgumentsImpl(false)}';
2527

@@ -41,7 +43,7 @@ abstract class HelperCore {
4143
new JsonKeyWithConversion(field, annotation);
4244

4345
TypeHelperContext getHelperContext(FieldElement field) =>
44-
new TypeHelperContext(generator, field.metadata, jsonKeyFor(field));
46+
new TypeHelperContext(this, field.metadata, jsonKeyFor(field));
4547
}
4648

4749
InvalidGenerationSourceError createInvalidGenerationError(

json_serializable/lib/src/json_key_with_conversion.dart

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,13 @@ JsonKeyWithConversion _from(
7979
var defaultValueObject = obj.getField('defaultValue');
8080

8181
Object defaultValueLiteral;
82-
if (isEnum(defaultValueObject.type)) {
83-
var interfaceType = defaultValueObject.type as InterfaceType;
84-
var allowedValues = interfaceType.element.fields
85-
.where((p) => !p.isSynthetic)
86-
.map((p) => p.name)
87-
.toList();
82+
83+
var enumFields = iterateEnumFields(defaultValueObject.type);
84+
if (enumFields != null) {
85+
var allowedValues = enumFields.map((p) => p.name).toList();
8886
var enumValueIndex = defaultValueObject.getField('index').toIntValue();
8987
defaultValueLiteral =
90-
'${interfaceType.name}.${allowedValues[enumValueIndex]}';
88+
'${defaultValueObject.type.name}.${allowedValues[enumValueIndex]}';
9189
} else {
9290
defaultValueLiteral = _getLiteral(defaultValueObject, []);
9391
if (defaultValueLiteral != null) {

json_serializable/lib/src/json_literal_generator.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ String jsonLiteralAsDart(dynamic value, bool asConst) {
5252

5353
if (value is List) {
5454
var listItems = value.map((v) => jsonLiteralAsDart(v, asConst)).join(', ');
55-
return '${asConst ? 'const' : ''}[$listItems]';
55+
return '${asConst ? 'const ' : ''}[$listItems]';
5656
}
5757

5858
if (value is Map) return jsonMapAsDart(value, asConst);

json_serializable/lib/src/json_serializable_generator.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class JsonSerializableGenerator
158158
new List.unmodifiable(typeHelpers.followedBy(_defaultHelpers)));
159159

160160
@override
161-
String generateForAnnotatedElement(
161+
Iterable<String> generateForAnnotatedElement(
162162
Element element, ConstantReader annotation, BuildStep buildStep) {
163163
if (element is! ClassElement) {
164164
var name = element.name;
@@ -170,7 +170,7 @@ class JsonSerializableGenerator
170170
var classElement = element as ClassElement;
171171
var classAnnotation = valueForAnnotation(annotation);
172172
var helper = new _GeneratorHelper(this, classElement, classAnnotation);
173-
return helper._generate().join('\n\n');
173+
return helper._generate();
174174
}
175175
}
176176

@@ -179,7 +179,15 @@ class _GeneratorHelper extends HelperCore with EncodeHelper, DecodeHelper {
179179
JsonSerializable annotation)
180180
: super(generator, element, annotation);
181181

182+
final _addedMembers = new Set<String>();
183+
184+
@override
185+
void addMember(String memberContent) {
186+
_addedMembers.add(memberContent);
187+
}
188+
182189
Iterable<String> _generate() sync* {
190+
assert(_addedMembers.isEmpty);
183191
var sortedFields = _createSortedFieldSet(element);
184192

185193
// Used to keep track of why a field is ignored. Useful for providing
@@ -223,6 +231,8 @@ class _GeneratorHelper extends HelperCore with EncodeHelper, DecodeHelper {
223231
if (annotation.createToJson) {
224232
yield* createToJson(accessibleFieldSet);
225233
}
234+
235+
yield* _addedMembers;
226236
}
227237
}
228238

json_serializable/lib/src/type_helper.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,27 @@ import 'package:analyzer/dart/element/element.dart';
66
import 'package:analyzer/dart/element/type.dart';
77

88
abstract class SerializeContext {
9+
/// `true` if [serialize] should handle the case of `expression` being null.
910
bool get nullable;
1011
bool get useWrappers;
1112

1213
/// [expression] may be just the name of the field or it may an expression
1314
/// representing the serialization of a value.
1415
String serialize(DartType fieldType, String expression);
1516
List<ElementAnnotation> get metadata;
17+
18+
/// Adds [memberContent] to the set of generated, top-level members.
19+
void addMember(String memberContent);
1620
}
1721

1822
abstract class DeserializeContext {
23+
/// `true` if [deserialize] should handle the case of `expression` being null.
1924
bool get nullable;
2025
String deserialize(DartType fieldType, String expression);
2126
List<ElementAnnotation> get metadata;
27+
28+
/// Adds [memberContent] to the set of generated, top-level members.
29+
void addMember(String memberContent);
2230
}
2331

2432
abstract class TypeHelper {

0 commit comments

Comments
 (0)