Skip to content

Commit d63205d

Browse files
authored
Add disallowUnrecognizedKeys option (#212)
1 parent 5d33655 commit d63205d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+941
-617
lines changed

json_annotation/CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.2.7
2+
3+
* Added `JsonSerializable.disallowUnrecognizedKeys`.
4+
* Added a helper function to support this option. This function starts with a
5+
`$` and should only be referenced by generated code. It is not meant for
6+
direct use.
7+
18
## 0.2.6
29

310
* `CheckedFromJsonException`
@@ -29,7 +36,7 @@
2936

3037
## 0.2.2
3138

32-
* Added a helper class – `$JsonMapWrapper` – and helper functions – `$wrapMap`,
39+
* Added a helper class – `$JsonMapWrapper` – and helper functions – `$wrapMap`,
3340
`$wrapMapHandleNull`, `$wrapList`, and `$wrapListHandleNull` – to support
3441
the `useWrappers` option added to `JsonSerializableGenerator` in `v0.3.0` of
3542
`package:json_serializable`.

json_annotation/lib/json_annotation.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
/// enabled.
1111
library json_annotation;
1212

13+
export 'src/allowed_keys_helpers.dart';
1314
export 'src/checked_helpers.dart';
1415
export 'src/json_literal.dart';
1516
export 'src/json_serializable.dart';
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// Helper function used in generated code when
6+
/// `JsonSerializable.disallowUnrecognizedKeys` is `true`.
7+
///
8+
/// Should not be used directly.
9+
void $checkAllowedKeys(Map map, Iterable<String> allowedKeys) {
10+
if (map == null) return;
11+
var invalidKeys = map.keys.where((k) => !allowedKeys.contains(k));
12+
if (invalidKeys.isNotEmpty) {
13+
throw new UnrecognizedKeysException(
14+
new List<String>.from(invalidKeys), map, allowedKeys.toList());
15+
}
16+
}
17+
18+
/// Exception thrown if there is an unrecognized key in a json map that was
19+
/// provided during deserialization.
20+
class UnrecognizedKeysException implements Exception {
21+
/// The allowed keys for [map].
22+
final List<String> allowedKeys;
23+
24+
/// The keys from [map] that were unrecognized.
25+
final List<String> unrecognizedKeys;
26+
27+
/// The source [Map] that the key was found in.
28+
final Map map;
29+
30+
/// A human-readable message corresponding to the error.
31+
String get message =>
32+
'Unrecognized keys [${unrecognizedKeys.join(', ')}], supported keys are '
33+
'[${allowedKeys.join(', ')}]';
34+
35+
UnrecognizedKeysException(this.unrecognizedKeys, this.map, this.allowedKeys);
36+
}

json_annotation/lib/src/checked_helpers.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
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+
import 'allowed_keys_helpers.dart';
6+
57
/// Helper function used in generated code when
68
/// `JsonSerializableGenerator.checked` is `true`.
79
///
@@ -85,6 +87,12 @@ class CheckedFromJsonException implements Exception {
8587
: _className = className,
8688
message = _getMessage(innerError);
8789

88-
static String _getMessage(Object error) =>
89-
(error is ArgumentError) ? error.message?.toString() : null;
90+
static String _getMessage(Object error) {
91+
if (error is ArgumentError) {
92+
return error.message?.toString();
93+
} else if (error is UnrecognizedKeysException) {
94+
return error.message;
95+
}
96+
return null;
97+
}
9098
}

json_annotation/lib/src/json_serializable.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
/// An annotation used to specify a class to generate code for.
66
class JsonSerializable {
7+
/// If `false` (the default), then any unrecognized keys passed to the
8+
/// generated FromJson factory will be ignored.
9+
///
10+
/// If `true`, any unrecognized keys will be treated as an error.
11+
final bool disallowUnrecognizedKeys;
12+
713
/// If `true` (the default), a private, static `_$ExampleFromJson` method
814
/// is created in the generated part file.
915
///
@@ -48,11 +54,13 @@ class JsonSerializable {
4854

4955
/// Creates a new [JsonSerializable] instance.
5056
const JsonSerializable(
51-
{bool createFactory: true,
57+
{bool disallowUnrecognizedKeys: false,
58+
bool createFactory: true,
5259
bool createToJson: true,
5360
bool includeIfNull: true,
5461
bool nullable: true})
55-
: this.createFactory = createFactory ?? true,
62+
: this.disallowUnrecognizedKeys = disallowUnrecognizedKeys ?? false,
63+
this.createFactory = createFactory ?? true,
5664
this.createToJson = createToJson ?? true,
5765
this.includeIfNull = includeIfNull ?? true,
5866
this.nullable = nullable ?? true;

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.6
2+
version: 0.2.7-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: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 0.5.6
2+
3+
* Added support for `JsonSerializable.disallowUnrecognizedKeys`.
4+
* Throws an `UnrecognizedKeysException` if it finds unrecognized keys in the
5+
JSON map used to create the annotated object.
6+
* Will be captured captured and wrapped in a `CheckedFromJsonException` if
7+
`checked` is enabled in `json_serializable`.
8+
* All `fromJson` constructors now use block syntax instead of fat arrows.
9+
110
## 0.5.5
211

312
* Added support for `JsonKey.defaultValue`.
@@ -35,9 +44,9 @@
3544

3645
* If `JsonKey.fromJson` function parameter is `Iterable` or `Map` with type
3746
arguments of `dynamic` or `Object`, omit the arguments when generating a
38-
cast.
47+
cast.
3948
`_myHelper(json['key'] as Map)` instead of
40-
`_myHelper(json['key'] as Map<dynamic, dynamic>)`.
49+
`_myHelper(json['key'] as Map<dynamic, dynamic>)`.
4150

4251
* `JsonKey.fromJson`/`.toJson` now support functions with optional arguments.
4352

json_serializable/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ Building creates the corresponding part `example.g.dart`:
4141
```dart
4242
part of 'example.dart';
4343
44-
Person _$PersonFromJson(Map<String, dynamic> json) => new Person(
45-
firstName: json['firstName'] as String,
46-
lastName: json['lastName'] as String,
47-
dateOfBirth: DateTime.parse(json['dateOfBirth'] as String));
44+
Person _$PersonFromJson(Map<String, dynamic> json) {
45+
return new Person(
46+
firstName: json['firstName'] as String,
47+
lastName: json['lastName'] as String,
48+
dateOfBirth: DateTime.parse(json['dateOfBirth'] as String));
49+
}
4850
4951
abstract class _$PersonSerializerMixin {
5052
String get firstName;

json_serializable/example/example.g.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ part of 'example.dart';
1010
// Generator: JsonSerializableGenerator
1111
// **************************************************************************
1212

13-
Person _$PersonFromJson(Map<String, dynamic> json) => new Person(
14-
firstName: json['firstName'] as String,
15-
lastName: json['lastName'] as String,
16-
dateOfBirth: DateTime.parse(json['dateOfBirth'] as String));
13+
Person _$PersonFromJson(Map<String, dynamic> json) {
14+
return new Person(
15+
firstName: json['firstName'] as String,
16+
lastName: json['lastName'] as String,
17+
dateOfBirth: DateTime.parse(json['dateOfBirth'] as String));
18+
}
1719

1820
abstract class _$PersonSerializerMixin {
1921
String get firstName;

json_serializable/lib/src/generator_helper.dart

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class _GeneratorHelper {
141141
var mapType = _generator.anyMap ? 'Map' : 'Map<String, dynamic>';
142142
_buffer.write('$_targetClassReference '
143143
'${_prefix}FromJson${_genericClassArguments(true)}'
144-
'($mapType json) =>');
144+
'($mapType json) {\n');
145145

146146
String deserializeFun(String paramOrFieldName,
147147
{ParameterElement ctorParam}) =>
@@ -152,10 +152,11 @@ class _GeneratorHelper {
152152
if (_generator.checked) {
153153
var classLiteral = escapeDartString(_element.name);
154154

155-
_buffer.write(''' \$checkedNew(
155+
_buffer.write('''
156+
return \$checkedNew(
156157
$classLiteral,
157158
json,
158-
()''');
159+
() {''');
159160

160161
var data = writeConstructorInvocation(
161162
_element,
@@ -169,31 +170,28 @@ class _GeneratorHelper {
169170

170171
fieldsSetByFactory = data.usedCtorParamsAndFields;
171172

172-
if (data.fieldsToSet.isEmpty) {
173-
// Use simple arrow syntax for the constructor invocation.
174-
// There are no fields to set
175-
_buffer.write(' => ${data.content}');
176-
} else {
177-
// If there are fields to set, create a full function body and
178-
// create a temporary variable to hold the instance so we can make
179-
// wrapped calls to all of the fields value assignments.
180-
_buffer.write(''' {
181-
var val = ''');
182-
_buffer.write(data.content);
183-
_buffer.writeln(';');
184-
185-
for (var field in data.fieldsToSet) {
186-
_buffer.writeln();
187-
var safeName = _safeNameAccess(accessibleFields[field]);
188-
_buffer.write('''
173+
if (_annotation.disallowUnrecognizedKeys) {
174+
var listLiteral = jsonLiteralAsDart(
175+
accessibleFields.values.map(_nameAccess).toList(), true);
176+
_buffer.write('''
177+
\$checkAllowedKeys(json, $listLiteral);''');
178+
}
179+
_buffer.write('''
180+
var val = ${data.content};''');
181+
182+
for (var field in data.fieldsToSet) {
183+
_buffer.writeln();
184+
var safeName = _safeNameAccess(accessibleFields[field]);
185+
_buffer.write('''
189186
\$checkedConvert(json, $safeName, (v) => ''');
190-
_buffer.write('val.$field = ');
191-
_buffer.write(_deserializeForField(accessibleFields[field],
192-
checkedProperty: true));
193-
_buffer.write(');');
194-
}
195-
_buffer.writeln('return val; }');
187+
_buffer.write('val.$field = ');
188+
_buffer.write(_deserializeForField(accessibleFields[field],
189+
checkedProperty: true));
190+
_buffer.write(');');
196191
}
192+
193+
_buffer.writeln('return val; }');
194+
197195
var fieldKeyMap = new Map.fromEntries(fieldsSetByFactory
198196
.map((k) => new MapEntry(k, _nameAccess(accessibleFields[k])))
199197
.where((me) => me.key != me.value));
@@ -206,7 +204,9 @@ class _GeneratorHelper {
206204
fieldKeyMapArg = ', fieldKeyMap: $mapLiteral';
207205
}
208206

209-
_buffer.write('$fieldKeyMapArg)');
207+
_buffer.write(fieldKeyMapArg);
208+
209+
_buffer.write(')');
210210
} else {
211211
var data = writeConstructorInvocation(
212212
_element,
@@ -220,14 +220,25 @@ class _GeneratorHelper {
220220

221221
fieldsSetByFactory = data.usedCtorParamsAndFields;
222222

223-
_buffer.write(' ${data.content}');
223+
if (_annotation.disallowUnrecognizedKeys) {
224+
var listLiteral = jsonLiteralAsDart(
225+
fieldsSetByFactory
226+
.map((k) => _nameAccess(accessibleFields[k]))
227+
.toList(),
228+
true);
229+
_buffer.write('''
230+
\$checkAllowedKeys(json, $listLiteral);''');
231+
}
232+
233+
_buffer.write('''
234+
return ${data.content}''');
224235
for (var field in data.fieldsToSet) {
225236
_buffer.writeln();
226-
_buffer.write(' ..$field = ');
237+
_buffer.write(' ..$field = ');
227238
_buffer.write(deserializeFun(field));
228239
}
229240
}
230-
_buffer.writeln(';');
241+
_buffer.writeln(';\n}');
231242
_buffer.writeln();
232243

233244
// If there are fields that are final – that are not set via the generated

json_serializable/lib/src/utils.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import 'package:source_gen/source_gen.dart';
1414

1515
JsonSerializable valueForAnnotation(ConstantReader annotation) =>
1616
new JsonSerializable(
17+
disallowUnrecognizedKeys:
18+
annotation.read('disallowUnrecognizedKeys').boolValue,
1719
createToJson: annotation.read('createToJson').boolValue,
1820
createFactory: annotation.read('createFactory').boolValue,
1921
nullable: annotation.read('nullable').boolValue,

json_serializable/pubspec.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: json_serializable
2-
version: 0.5.5
2+
version: 0.5.6-dev
33
author: Dart Team <misc@dartlang.org>
44
description: Generates utilities to aid in serializing to/from JSON.
55
homepage: https://github.com/dart-lang/json_serializable
@@ -12,7 +12,7 @@ dependencies:
1212

1313
# Use a tight version constraint to ensure that a constraint on
1414
# `json_annotation`. Properly constrains all features it provides.
15-
json_annotation: '>=0.2.6 <0.2.7'
15+
json_annotation: '>=0.2.7 <0.2.8'
1616
meta: ^1.1.0
1717
path: ^1.3.2
1818
source_gen: '>=0.8.1 <0.9.0'
@@ -25,3 +25,7 @@ dev_dependencies:
2525
logging: ^0.11.3+1
2626
test: ^0.12.3
2727
yaml: ^2.1.13
28+
29+
dependency_overrides:
30+
json_annotation:
31+
path: ../json_annotation

0 commit comments

Comments
 (0)