Skip to content

Add support for checked #151

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

Merged
merged 6 commits into from
May 17, 2018
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions json_annotation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 0.2.5

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

* Also added functions to support the `checked` generation option. These
functions start with `$` are referenced by generated code. They are not meant
for direct use.

## 0.2.4

* Added `fromJson` and `toJson` fields to `JsonKey` class.
Expand Down
1 change: 1 addition & 0 deletions json_annotation/lib/json_annotation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/// `json_serializable` when generating wrappers.
library json_annotation;

export 'src/checked_helpers.dart';
export 'src/json_literal.dart';
export 'src/json_serializable.dart';
export 'src/wrapper_helpers.dart';
58 changes: 58 additions & 0 deletions json_annotation/lib/src/checked_helpers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/// Helper function used in generated code when
/// `JsonSerializableGenerator.checked` is `true`.
///
/// Should not be used directly.
T $checkedNew<T>(String className, Map map, Map<String, String> fieldKeyMap,
T constructor()) {
try {
return constructor();
} on CheckedFromJsonException catch (e) {
e._className ??= className;
rethrow;
} catch (error, stack) {
throw new CheckedFromJsonException._(error, stack, map,
(error is ArgumentError) ? fieldKeyMap[error.name] : null, T,
className: className);
}
}

/// Helper function used in generated code when
/// `JsonSerializableGenerator.checked` is `true`.
///
/// Should not be used directly.
T $checkedConvert<T>(Map map, String key, T castFunc()) {
try {
return castFunc();
} on CheckedFromJsonException {
rethrow;
} catch (error, stack) {
throw new CheckedFromJsonException._(error, stack, map, key, T);
}
}

/// Exception thrown if there is a runtime exception in `fromJson`
/// code generated when `JsonSerializableGenerator.checked` is `true`
class CheckedFromJsonException implements Exception {
final Object innerError;
final StackTrace innerStack;
final String key;
final Map map;
final Type targetType;
final Object message;

String _className;
String get className => _className;

CheckedFromJsonException._(
this.innerError, this.innerStack, this.map, this.key, this.targetType,
{String className})
: _className = className,
message = _getMessage(innerError);

static Object _getMessage(Object error) =>
(error is ArgumentError) ? error.message : null;
}
2 changes: 1 addition & 1 deletion json_annotation/lib/src/wrapper_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import 'dart:collection';

/// Helper classes used in generated code when
/// Helper class used in generated code when
/// `JsonSerializableGenerator.useWrappers` is `true`.
///
/// Should not be used directly.
Expand Down
3 changes: 3 additions & 0 deletions json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 0.5.4

* Add `checked` configuration option. If `true`, generated `fromJson` functions
include extra checks to validate proper deserialization of types.

* Use `Map.map` for more map conversions. Simplifies generated code and fixes
a subtle issue when the `Map` key type is `dynamic` or `Object`.

Expand Down
1 change: 1 addition & 0 deletions json_serializable/lib/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Builder jsonSerializable(BuilderOptions options) {
var builder = jsonPartBuilder(
header: optionsMap.remove('header') as String,
useWrappers: optionsMap.remove('use_wrappers') as bool,
checked: optionsMap.remove('checked') as bool,
anyMap: optionsMap.remove('any_map') as bool);

if (optionsMap.isNotEmpty) {
Expand Down
2 changes: 1 addition & 1 deletion json_serializable/lib/json_serializable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

export 'src/json_literal_generator.dart';
export 'src/json_literal_generator.dart' show JsonLiteralGenerator;
export 'src/json_serializable_generator.dart' show JsonSerializableGenerator;
28 changes: 24 additions & 4 deletions json_serializable/lib/src/generator_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:source_gen/source_gen.dart';

import 'constants.dart';
import 'json_key_helpers.dart';
import 'json_literal_generator.dart';
import 'json_serializable_generator.dart';
import 'type_helper.dart';
import 'type_helper_context.dart';
Expand Down Expand Up @@ -126,16 +127,15 @@ class _GeneratorHelper {
var mapType = _generator.anyMap ? 'Map' : 'Map<String, dynamic>';

_buffer.writeln();
_buffer.writeln('${_element.name} '
'${_prefix}FromJson($mapType json) =>');

String deserializeFun(String paramOrFieldName,
{ParameterElement ctorParam}) =>
_deserializeForField(accessibleFields[paramOrFieldName],
ctorParam: ctorParam);

var tempBuffer = new StringBuffer();
var fieldsSetByFactory = writeConstructorInvocation(
_buffer,
tempBuffer,
_element,
accessibleFields.keys,
accessibleFields.values
Expand All @@ -144,6 +144,21 @@ class _GeneratorHelper {
.toList(),
unavailableReasons,
deserializeFun);

if (_generator.checked) {
var keyFieldMap = new Map<String, String>.fromIterable(
fieldsSetByFactory,
value: (k) => _nameAccess(accessibleFields[k]));
var mapLiteral = jsonMapAsDart(keyFieldMap, true);
var classLiteral = escapeDartString(_element.displayName);

_buffer.writeln(
'${_element.displayName} ${_prefix}FromJson($mapType json) =>'
'\$checkedNew($classLiteral, json, $mapLiteral, ()=>$tempBuffer)');
} else {
_buffer.writeln('${_element.name} '
'${_prefix}FromJson($mapType json) => $tempBuffer');
}
_buffer.writeln(';');

// If there are fields that are final – that are not set via the generated
Expand Down Expand Up @@ -297,7 +312,12 @@ void $toJsonMapHelperName(String key, dynamic value) {
var targetType = ctorParam?.type ?? field.type;

try {
return _getHelperContext(field).deserialize(targetType, 'json[$jsonKey]');
var value =
_getHelperContext(field).deserialize(targetType, 'json[$jsonKey]');
if (_generator.checked) {
return '\$checkedConvert(json, $jsonKey, () => $value)';
}
return value;
} on UnsupportedTypeError catch (e) {
throw _createInvalidGenerationError('fromJson', field, e);
}
Expand Down
4 changes: 2 additions & 2 deletions json_serializable/lib/src/json_literal_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ String _jsonLiteralAsDart(dynamic value, bool asConst) {
return '${asConst ? 'const' : ''}[$listItems]';
}

if (value is Map<String, dynamic>) return _jsonMapAsDart(value, asConst);
if (value is Map<String, dynamic>) return jsonMapAsDart(value, asConst);

throw new StateError(
'Should never get here – with ${value.runtimeType} - `$value`.');
}

String _jsonMapAsDart(Map<String, dynamic> value, bool asConst) {
String jsonMapAsDart(Map<String, dynamic> value, bool asConst) {
var buffer = new StringBuffer();
if (asConst) {
buffer.write('const ');
Expand Down
11 changes: 8 additions & 3 deletions json_serializable/lib/src/json_part_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ import 'json_serializable_generator.dart';
/// If `null`, the content of [defaultFileHeader] is used.
/// If [header] is an empty `String` no header is added.
///
/// For details on [useWrappers] and [anyMap], see [JsonSerializableGenerator].
/// For details on [useWrappers], [anyMap], and [checked] see
/// [JsonSerializableGenerator].
Builder jsonPartBuilder(
{String header, bool useWrappers: false, bool anyMap: false}) {
{String header,
bool useWrappers: false,
bool anyMap: false,
bool checked: false}) {
return new PartBuilder([
new JsonSerializableGenerator(useWrappers: useWrappers, anyMap: anyMap),
new JsonSerializableGenerator(
useWrappers: useWrappers, anyMap: anyMap, checked: checked),
const JsonLiteralGenerator()
], header: header);
}
35 changes: 24 additions & 11 deletions json_serializable/lib/src/json_serializable_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,41 @@ class JsonSerializableGenerator
].followedBy(_typeHelpers).followedBy(_coreHelpers);

/// If `true`, wrappers are used to minimize the number of
/// [Map] and [List] instances created during serialization. This will
/// increase the code size, but it may improve runtime performance, especially
/// for large object graphs.
/// [Map] and [List] instances created during serialization.
///
/// This will increase the code size, but it may improve runtime performance,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These line lengths look inconsistent. Would [Map] have fit within 80 characters on line 45 or is this line going over 80 characters?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I often wrap at the sentence start. It doesn't affect markdown rendering, but it doe make it easier to edit sentences without rewrapping the entire paragraph.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neither of these lines are wrapped at a sentence start...

/// especially for large object graphs.
final bool useWrappers;

/// If `true`, [Map] types are not assumed to be
/// [Map<String, dynamic>] – the default for JSON support in `dart:convert`.
/// This results in more code being generated, but allows [Map] types returned
/// If `true`, [Map] types are *not* assumed to be [Map<String, dynamic>]
/// – which is the default type of [Map] instances return by JSON decode in
/// `dart:convert`.
///
/// This will increase the code size, but allows [Map] types returned
/// from other sources, such as `package:yaml`.
///
/// *Note: in many cases the key values are still assumed to be [String]*.
final bool anyMap;

/// If `true`, generated `fromJson` functions include extra checks to validate
/// proper deserialization of types.
///
/// If an exception is thrown during deserialization, a
/// [CheckedFromJsonException] is thrown.
final bool checked;

/// Creates an instance of [JsonSerializableGenerator].
///
/// If [typeHelpers] is not provided, two built-in helpers are used:
/// [JsonHelper] and [DateTimeHelper].
const JsonSerializableGenerator(
{List<TypeHelper> typeHelpers,
bool useWrappers: false,
bool anyMap: false})
: this.useWrappers = useWrappers ?? false,
const JsonSerializableGenerator({
List<TypeHelper> typeHelpers,
bool useWrappers: false,
bool anyMap: false,
bool checked: false,
}) : this.useWrappers = useWrappers ?? false,
this.anyMap = anyMap ?? false,
this.checked = checked ?? false,
this._typeHelpers = typeHelpers ?? _defaultHelpers;

/// Creates an instance of [JsonSerializableGenerator].
Expand Down
4 changes: 4 additions & 0 deletions json_serializable/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ dev_dependencies:
logging: ^0.11.3+1
test: ^0.12.3
yaml: ^2.1.13

dependency_overrides:
json_annotation:
path: ../json_annotation
9 changes: 9 additions & 0 deletions json_serializable/test/config/angular_comp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
builders:
scss_builder:
target: "scss_builder"
import: "package:angular_components/builder.dart"
builder_factories: ["scssBuilder"]
build_to: cache
build_extensions:
.scss: [".scss.css"]
.sass: [".scss.css"]
21 changes: 21 additions & 0 deletions json_serializable/test/config/angular_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
builders:
angular:
import: "package:angular/builder.dart"
builder_factories:
- templatePlaceholder
- templateCompiler
- stylesheetCompiler
auto_apply: none
applies_builders:
- "angular|placeholder_cleanup"
- "angular|component_source_cleanup"
# See https://github.com/dart-lang/angular/issues/988.
is_optional: true
required_inputs:
- ".css"
build_extensions:
.css:
- ".css.dart"
- ".css.shim.dart"
.dart:
- ".template.dart"
Loading