Skip to content

Commit d6b6b4c

Browse files
committed
defaultValue: support serialization of single enum values
Also added $enumDecode[Nullable] helpers to json_annotation
1 parent 3d71780 commit d6b6b4c

21 files changed

+146
-75
lines changed

json_annotation/CHANGELOG.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
* Added `JsonKey.defaultValue`.
44

5+
* Added helpers for deserialization of `enum` values.
6+
These functions starting with `$` are referenced by generated code.
7+
They are not meant for direct use.
8+
59
## 0.2.5
610

711
* Added `CheckedFromJsonException` which is thrown by code generated when
812
`checked` is enabled in `json_serializable`.
913

10-
* Added functions to support the `checked` generation option. These
11-
functions start with `$` are referenced by generated code. They are not meant
12-
for direct use.
14+
* Added functions to support the `checked` generation option.
15+
These functions starting with `$` are referenced by generated code.
16+
They are not meant for direct use.
1317

1418
## 0.2.4
1519

json_annotation/lib/src/json_serializable.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,22 @@ class JsonKey {
133133
this.toJson,
134134
this.defaultValue});
135135
}
136+
137+
// Until enum supports parse: github.com/dart-lang/sdk/issues/33244
138+
/// Helper class used in generated code with `enum` values.
139+
///
140+
/// Should not be used directly.
141+
T $enumDecode<T>(String enumName, List<T> values, String enumValue) =>
142+
values.singleWhere((e) => e.toString() == '$enumName.$enumValue',
143+
orElse: () => throw new ArgumentError(
144+
'`$enumValue` is not one of the supported values: '
145+
'${values.map(_nameForEnumValue).join(', ')}'));
146+
147+
// Until enum has a name property: github.com/dart-lang/sdk/issues/21712
148+
String _nameForEnumValue(Object value) => value.toString().split('.')[1];
149+
150+
/// Helper class used in generated code with nullable `enum` values.
151+
///
152+
/// Should not be used directly.
153+
T $enumDecodeNullable<T>(String enumName, List<T> values, String enumValue) =>
154+
enumValue == null ? null : $enumDecode(enumName, values, enumValue);

json_serializable/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
* Added support for `JsonKey.defaultValue`.
44

5+
* `enum` deserialization now uses helpers provided by `json_annotation`.
6+
57
* Small change to how nullable `Map` values are deserialized.
68

79
* Small whitespace changes to `JsonLiteral` generation to align with `dartfmt`.

json_serializable/lib/src/json_key_helpers.dart

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:meta/meta.dart' show alwaysThrows;
1111
import 'package:source_gen/source_gen.dart';
1212

1313
import 'json_literal_generator.dart';
14+
import 'utils.dart';
1415

1516
@alwaysThrows
1617
void throwUnsupported(FieldElement element, String message) =>
@@ -45,7 +46,7 @@ JsonKeyWithConversion _from(FieldElement element) {
4546
var fromJsonName = _getFunctionName(obj, element, true);
4647
var toJsonName = _getFunctionName(obj, element, false);
4748

48-
Object _getLiteral(DartObject dartObject) {
49+
Object _getLiteral(DartObject dartObject, Iterable<String> things) {
4950
if (dartObject.isNull) {
5051
return null;
5152
}
@@ -65,27 +66,51 @@ JsonKeyWithConversion _from(FieldElement element) {
6566
}
6667

6768
if (badType != null) {
69+
badType = things.followedBy([badType]).join(' > ');
6870
throwUnsupported(
6971
element, '`defaultValue` is `$badType`, it must be a literal.');
7072
}
7173

7274
var literal = reader.literalValue;
75+
7376
if (literal is num || literal is String || literal is bool) {
7477
return literal;
7578
} else if (literal is List<DartObject>) {
76-
return literal.map(_getLiteral).toList();
77-
} else if (literal is Map<DartObject, DartObject>) {
7879
return literal
79-
.map((k, v) => new MapEntry(_getLiteral(k), _getLiteral(v)));
80+
.map((e) => _getLiteral(e, things.followedBy(['List'])))
81+
.toList();
82+
} else if (literal is Map<DartObject, DartObject>) {
83+
var mapThings = things.followedBy(['Map']);
84+
return literal.map((k, v) =>
85+
new MapEntry(_getLiteral(k, mapThings), _getLiteral(v, mapThings)));
8086
}
87+
88+
badType = things.followedBy(['$dartObject']).join(' > ');
89+
8190
throwUnsupported(
82-
element, 'The provided value is not supported: $dartObject');
91+
element,
92+
'The provided value is not supported: $badType. '
93+
'This may be an error in package:json_serializable. '
94+
'Please rerun your build with `--verbose` and file an issue.');
8395
}
8496

85-
var defaultValueLiteral = _getLiteral(obj.getField('defaultValue'));
86-
87-
if (defaultValueLiteral != null) {
88-
defaultValueLiteral = jsonLiteralAsDart(defaultValueLiteral, false);
97+
var defaultValueObject = obj.getField('defaultValue');
98+
99+
Object defaultValueLiteral;
100+
if (isEnum(defaultValueObject.type)) {
101+
var interfaceType = defaultValueObject.type as InterfaceType;
102+
var allowedValues = interfaceType.accessors
103+
.where((p) => p.returnType == interfaceType)
104+
.map((p) => p.name)
105+
.toList();
106+
var enumValueIndex = defaultValueObject.getField('index').toIntValue();
107+
defaultValueLiteral =
108+
'${interfaceType.name}.${allowedValues[enumValueIndex]}';
109+
} else {
110+
defaultValueLiteral = _getLiteral(defaultValueObject, []);
111+
if (defaultValueLiteral != null) {
112+
defaultValueLiteral = jsonLiteralAsDart(defaultValueLiteral, false);
113+
}
89114
}
90115

91116
return new JsonKeyWithConversion._(

json_serializable/lib/src/type_helpers/enum_helper.dart

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import 'package:analyzer/dart/element/type.dart';
66

7-
import '../constants.dart' as consts;
87
import '../type_helper.dart';
98
import '../utils.dart';
109

@@ -31,18 +30,9 @@ class EnumHelper extends TypeHelper {
3130
return null;
3231
}
3332

34-
var wrappedExpression =
35-
simpleExpression.hasMatch(expression) ? expression : '{$expression}';
36-
37-
var closureArg = consts.closureArg;
38-
if (closureArg == wrappedExpression) {
39-
closureArg = '${closureArg}2';
40-
}
41-
42-
return commonNullPrefix(
43-
context.nullable,
44-
expression,
45-
'$targetType.values.singleWhere(($closureArg) => $closureArg.toString()'
46-
" == '$targetType.\$$wrappedExpression')");
33+
var functionName =
34+
context.nullable ? r'$enumDecodeNullable' : r'$enumDecode';
35+
return "$functionName('$targetType', $targetType.values, "
36+
'$expression as String)';
4737
}
4838
}

json_serializable/test/default_value/default_value.checked.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212

1313
import 'package:json_annotation/json_annotation.dart';
1414

15-
import 'default_value_interface.dart' as dvi;
15+
import 'default_value_interface.dart' as dvi hide Greek;
16+
import 'default_value_interface.dart' show Greek;
1617

1718
part 'default_value.checked.g.dart';
1819

1920
const _intValue = 42;
2021

2122
dvi.DefaultValue fromJson(Map<String, dynamic> json) =>
22-
new DefaultValue.fromJson(json);
23+
_$DefaultValueFromJson(json);
2324

2425
@JsonSerializable()
2526
class DefaultValue extends Object
@@ -54,6 +55,9 @@ class DefaultValue extends Object
5455
})
5556
Map<String, List<String>> fieldMapListString;
5657

58+
@JsonKey(defaultValue: Greek.beta)
59+
Greek fieldEnum;
60+
5761
DefaultValue();
5862

5963
factory DefaultValue.fromJson(Map<String, dynamic> json) =>

json_serializable/test/default_value/default_value.checked.g.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ DefaultValue _$DefaultValueFromJson(Map json) =>
4545
{
4646
'root': ['child']
4747
});
48+
$checkedConvert(
49+
json,
50+
'fieldEnum',
51+
(v) => val.fieldEnum =
52+
$enumDecodeNullable('Greek', Greek.values, v as String) ??
53+
Greek.beta);
4854
return val;
4955
});
5056

@@ -58,6 +64,7 @@ abstract class _$DefaultValueSerializerMixin {
5864
List<int> get fieldListSimple;
5965
Map<String, int> get fieldMapSimple;
6066
Map<String, List<String>> get fieldMapListString;
67+
Greek get fieldEnum;
6168
Map<String, dynamic> toJson() => <String, dynamic>{
6269
'fieldBool': fieldBool,
6370
'fieldString': fieldString,
@@ -67,6 +74,8 @@ abstract class _$DefaultValueSerializerMixin {
6774
'fieldMapEmpty': fieldMapEmpty,
6875
'fieldListSimple': fieldListSimple,
6976
'fieldMapSimple': fieldMapSimple,
70-
'fieldMapListString': fieldMapListString
77+
'fieldMapListString': fieldMapListString,
78+
'fieldEnum':
79+
fieldEnum == null ? null : fieldEnum.toString().split('.')[1]
7180
};
7281
}

json_serializable/test/default_value/default_value.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
import 'package:json_annotation/json_annotation.dart';
88

9-
import 'default_value_interface.dart' as dvi;
9+
import 'default_value_interface.dart' as dvi hide Greek;
10+
import 'default_value_interface.dart' show Greek;
1011

1112
part 'default_value.g.dart';
1213

1314
const _intValue = 42;
1415

1516
dvi.DefaultValue fromJson(Map<String, dynamic> json) =>
16-
new DefaultValue.fromJson(json);
17+
_$DefaultValueFromJson(json);
1718

1819
@JsonSerializable()
1920
class DefaultValue extends Object
@@ -48,6 +49,9 @@ class DefaultValue extends Object
4849
})
4950
Map<String, List<String>> fieldMapListString;
5051

52+
@JsonKey(defaultValue: Greek.beta)
53+
Greek fieldEnum;
54+
5155
DefaultValue();
5256

5357
factory DefaultValue.fromJson(Map<String, dynamic> json) =>

json_serializable/test/default_value/default_value.g.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ DefaultValue _$DefaultValueFromJson(Map<String, dynamic> json) =>
3030
k, (e as List)?.map((e) => e as String)?.toList())) ??
3131
{
3232
'root': ['child']
33-
};
33+
}
34+
..fieldEnum = $enumDecodeNullable(
35+
'Greek', Greek.values, json['fieldEnum'] as String) ??
36+
Greek.beta;
3437

3538
abstract class _$DefaultValueSerializerMixin {
3639
bool get fieldBool;
@@ -42,6 +45,7 @@ abstract class _$DefaultValueSerializerMixin {
4245
List<int> get fieldListSimple;
4346
Map<String, int> get fieldMapSimple;
4447
Map<String, List<String>> get fieldMapListString;
48+
Greek get fieldEnum;
4549
Map<String, dynamic> toJson() => <String, dynamic>{
4650
'fieldBool': fieldBool,
4751
'fieldString': fieldString,
@@ -51,6 +55,8 @@ abstract class _$DefaultValueSerializerMixin {
5155
'fieldMapEmpty': fieldMapEmpty,
5256
'fieldListSimple': fieldListSimple,
5357
'fieldMapSimple': fieldMapSimple,
54-
'fieldMapListString': fieldMapListString
58+
'fieldMapListString': fieldMapListString,
59+
'fieldEnum':
60+
fieldEnum == null ? null : fieldEnum.toString().split('.')[1]
5561
};
5662
}

json_serializable/test/default_value/default_value_interface.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ abstract class DefaultValue {
1818
List<int> fieldListSimple;
1919
Map<String, int> fieldMapSimple;
2020
Map<String, List<String>> fieldMapListString;
21+
Greek fieldEnum;
2122
}
23+
24+
enum Greek { alpha, beta, gamma, delta }

json_serializable/test/default_value/default_value_test.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const _defaultInstance = const {
2121
'fieldMapListString': const {
2222
'root': const ['child']
2323
},
24+
'fieldEnum': 'beta'
2425
};
2526

2627
const _otherValues = const {
@@ -33,8 +34,9 @@ const _otherValues = const {
3334
'fieldListSimple': const [4, 5, 6],
3435
'fieldMapSimple': const <String, dynamic>{},
3536
'fieldMapListString': const {
36-
'root2': const ['child1', 'child2']
37+
'root2': const ['alpha']
3738
},
39+
'fieldEnum': 'delta'
3840
};
3941

4042
void main() {

json_serializable/test/json_serializable_integration_test.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,12 @@ void main() {
122122
});
123123

124124
test('required, but missing enum value fails', () {
125-
expect(() => new Order.fromJson({}), throwsStateError);
125+
expect(() => new Order.fromJson({}), throwsArgumentError);
126126
});
127127

128128
test('mismatched enum value fails', () {
129-
expect(() => new Order.fromJson({'category': 'weird'}), throwsStateError);
129+
expect(
130+
() => new Order.fromJson({'category': 'weird'}), throwsArgumentError);
130131
});
131132

132133
test('platform', () {

json_serializable/test/json_serializable_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,9 +632,9 @@ abstract class _$SubTypeSerializerMixin {
632632
});
633633
test('enum value', () {
634634
expectThrows(
635-
'DefaultWithEnum',
635+
'DefaultWithNestedEnum',
636636
'Error with `@JsonKey` on `field`. '
637-
'`defaultValue` is `Enum`, it must be a literal.');
637+
'`defaultValue` is `List > Enum`, it must be a literal.');
638638
});
639639
test('non-nullable field', () {
640640
expectThrows(

json_serializable/test/src/default_value_input.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ class DefaultWithConstObject {
4141
enum Enum { value }
4242

4343
@JsonSerializable()
44-
class DefaultWithEnum {
45-
@JsonKey(defaultValue: Enum.value)
44+
class DefaultWithNestedEnum {
45+
@JsonKey(defaultValue: [Enum.value])
4646
Object field;
4747

48-
DefaultWithEnum();
48+
DefaultWithNestedEnum();
4949
}
5050

5151
@JsonSerializable()

json_serializable/test/test_files/json_test_example.g.dart

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ part of 'json_test_example.dart';
1313
Person _$PersonFromJson(Map<String, dynamic> json) => new Person(
1414
json['firstName'] as String,
1515
json['lastName'] as String,
16-
json[r'$house'] == null
17-
? null
18-
: House.values
19-
.singleWhere((e) => e.toString() == 'House.${json[r'$house']}'),
16+
$enumDecodeNullable('House', House.values, json[r'$house'] as String),
2017
middleName: json['middleName'] as String,
2118
dateOfBirth: json['dateOfBirth'] == null
2219
? null
@@ -25,11 +22,7 @@ Person _$PersonFromJson(Map<String, dynamic> json) => new Person(
2522
? null
2623
: new Order.fromJson(json['order'] as Map<String, dynamic>)
2724
..houseMap = (json['houseMap'] as Map<String, dynamic>)?.map((k, e) =>
28-
new MapEntry(
29-
k,
30-
e == null
31-
? null
32-
: House.values.singleWhere((e2) => e2.toString() == 'House.$e')));
25+
new MapEntry(k, $enumDecodeNullable('House', House.values, e as String)));
3326

3427
abstract class _$PersonSerializerMixin {
3528
String get firstName;
@@ -52,8 +45,7 @@ abstract class _$PersonSerializerMixin {
5245
}
5346

5447
Order _$OrderFromJson(Map<String, dynamic> json) => new Order(
55-
Category.values
56-
.singleWhere((e) => e.toString() == 'Category.${json['category']}'),
48+
$enumDecode('Category', Category.values, json['category'] as String),
5749
(json['items'] as List)?.map(
5850
(e) => e == null ? null : new Item.fromJson(e as Map<String, dynamic>)))
5951
..count = json['count'] as int

0 commit comments

Comments
 (0)