Skip to content

Better output with fromJson methods with no Iterable/Map #155

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 3 commits into from
May 15, 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
8 changes: 8 additions & 0 deletions json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.5.3

* If `JsonKey.fromJson` function parameter is `Iterable` or `Map` with type
arguments of `dynamic` or `Object`, omit the arguments when generating a
cast.
`_myHelper(json['key'] as Map)` instead of
`_myHelper(json['key'] as Map<dynamic, dynamic>)`.

## 0.5.2

* If `fromJson`/`toJson` are set in `JsonKey`, apply them before any custom
Expand Down
8 changes: 5 additions & 3 deletions json_serializable/lib/src/generator_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,11 @@ void $toJsonMapHelperName(String key, dynamic value) {
}
}

TypeHelperContext _getHelperContext(FieldElement field) =>
new TypeHelperContext(
_generator, field.metadata, _nullable(field), jsonKeyFor(field));
TypeHelperContext _getHelperContext(FieldElement field) {
var key = jsonKeyFor(field);
return new TypeHelperContext(_generator, field.metadata, _nullable(field),
key.fromJsonData, key.toJsonData);
}

/// Returns `true` if the field can be written to JSON 'naively' – meaning
/// we can avoid checking for `null`.
Expand Down
56 changes: 56 additions & 0 deletions json_serializable/lib/src/shared_checkers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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.

import 'package:analyzer/dart/element/type.dart';
import 'package:source_gen/source_gen.dart' show TypeChecker;

/// A [TypeChecker] for [Iterable].
const coreIterableTypeChecker = const TypeChecker.fromUrl('dart:core#Iterable');

const coreStringTypeChecker = const TypeChecker.fromRuntime(String);

const coreMapTypeChecker = const TypeChecker.fromUrl('dart:core#Map');

/// Returns the generic type of the [Iterable] represented by [type].
///
/// If [type] does not extend [Iterable], an error is thrown.
DartType coreIterableGenericType(DartType type) =>
typeArgumentsOf(type, coreIterableTypeChecker).single;

/// If [type] is the [Type] or implements the [Type] represented by [checker],
/// returns the generic arguments to the [checker] [Type] if there are any.
///
/// If the [checker] [Type] doesn't have generic arguments, `null` is returned.
List<DartType> typeArgumentsOf(DartType type, TypeChecker checker) {
var implementation = _getImplementationType(type, checker) as InterfaceType;

return implementation?.typeArguments;
}

/// A [TypeChecker] for [String], [bool] and [num].
const simpleJsonTypeChecker = const TypeChecker.any(const [
coreStringTypeChecker,
const TypeChecker.fromRuntime(bool),
const TypeChecker.fromRuntime(num)
]);

DartType _getImplementationType(DartType type, TypeChecker checker) {
if (checker.isExactlyType(type)) return type;

if (type is InterfaceType) {
var match = [type.interfaces, type.mixins]
.expand((e) => e)
.map((type) => _getImplementationType(type, checker))
.firstWhere((value) => value != null, orElse: () => null);

if (match != null) {
return match;
}

if (type.superclass != null) {
return _getImplementationType(type.superclass, checker);
}
}
return null;
}
38 changes: 0 additions & 38 deletions json_serializable/lib/src/type_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,6 @@

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:source_gen/source_gen.dart' show TypeChecker;

/// If [type] is the [Type] or implements the [Type] represented by [checker],
/// returns the generic arguments to the [checker] [Type] if there are any.
///
/// If the [checker] [Type] doesn't have generic arguments, `null` is returned.
List<DartType> typeArgumentsOf(DartType type, TypeChecker checker) {
var implementation = _getImplementationType(type, checker) as InterfaceType;

return implementation?.typeArguments;
}

abstract class SerializeContext {
bool get nullable;
Expand Down Expand Up @@ -82,37 +71,10 @@ abstract class TypeHelper {
DartType targetType, String expression, DeserializeContext context);
}

/// A [TypeChecker] for [String], [bool] and [num].
const simpleJsonTypeChecker = const TypeChecker.any(const [
const TypeChecker.fromRuntime(String),
const TypeChecker.fromRuntime(bool),
const TypeChecker.fromRuntime(num)
]);

class UnsupportedTypeError extends Error {
final String expression;
final DartType type;
final String reason;

UnsupportedTypeError(this.type, this.expression, this.reason);
}

DartType _getImplementationType(DartType type, TypeChecker checker) {
if (checker.isExactlyType(type)) return type;

if (type is InterfaceType) {
var match = [type.interfaces, type.mixins]
.expand((e) => e)
.map((type) => _getImplementationType(type, checker))
.firstWhere((value) => value != null, orElse: () => null);

if (match != null) {
return match;
}

if (type.superclass != null) {
return _getImplementationType(type.superclass, checker);
}
}
return null;
}
7 changes: 4 additions & 3 deletions json_serializable/lib/src/type_helper_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ class TypeHelperContext implements SerializeContext, DeserializeContext {
@override
final bool nullable;

final JsonKeyWithConversion jsonKey;
final ConvertData fromJsonData;
final ConvertData toJsonData;

TypeHelperContext(
this._generator, this.metadata, this.nullable, this.jsonKey);
TypeHelperContext(this._generator, this.metadata, this.nullable,
this.fromJsonData, this.toJsonData);

@override
String serialize(DartType targetType, String expression) => _run(
Expand Down
45 changes: 33 additions & 12 deletions json_serializable/lib/src/type_helpers/convert_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

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

import '../shared_checkers.dart';
import '../type_helper.dart';
import '../type_helper_context.dart';
import '../utils.dart';
Expand All @@ -14,11 +15,11 @@ class ConvertHelper extends TypeHelper {
@override
String serialize(
DartType targetType, String expression, SerializeContext context) {
var jsonKey = (context as TypeHelperContext).jsonKey;
if (jsonKey.toJsonData != null) {
assert(targetType.isAssignableTo(jsonKey.toJsonData.paramType));
var toJsonData = (context as TypeHelperContext).toJsonData;
if (toJsonData != null) {
assert(targetType.isAssignableTo(toJsonData.paramType));

var result = '${jsonKey.toJsonData.name}($expression)';
var result = '${toJsonData.name}($expression)';
return commonNullPrefix(context.nullable, expression, result);
}
return null;
Expand All @@ -27,16 +28,36 @@ class ConvertHelper extends TypeHelper {
@override
String deserialize(
DartType targetType, String expression, DeserializeContext context) {
var jsonKey = (context as TypeHelperContext).jsonKey;
if (jsonKey.fromJsonData != null) {
var asContent = '';
var paramType = jsonKey.fromJsonData.paramType;
if (!(paramType.isDynamic || paramType.isObject)) {
asContent = ' as $paramType';
}
var result = '${jsonKey.fromJsonData.name}($expression$asContent)';
var fromJsonData = (context as TypeHelperContext).fromJsonData;
if (fromJsonData != null) {
var asContent = _asContent(fromJsonData.paramType);
var result = '${fromJsonData.name}($expression$asContent)';
return commonNullPrefix(context.nullable, expression, result);
}
return null;
}
}

String _asContent(DartType type) {
if (type.isDynamic || type.isObject) {
return '';
}

if (coreIterableTypeChecker.isAssignableFromType(type)) {
var itemType = coreIterableGenericType(type);
if (itemType.isDynamic || itemType.isObject) {
return ' as List';
}
}

if (coreMapTypeChecker.isAssignableFromType(type)) {
var args = typeArgumentsOf(type, coreMapTypeChecker);
assert(args.length == 2);

if (args.every((dt) => dt.isDynamic || dt.isObject)) {
return ' as Map';
}
}

return ' as $type';
}
29 changes: 11 additions & 18 deletions json_serializable/lib/src/type_helpers/iterable_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import 'package:analyzer/dart/element/type.dart';
import 'package:source_gen/source_gen.dart' show TypeChecker;

import '../shared_checkers.dart';
import '../type_helper.dart';

/// Name used for closure argument when generating calls to `map`.
Expand All @@ -15,40 +17,36 @@ class IterableHelper extends TypeHelper {
@override
String serialize(
DartType targetType, String expression, SerializeContext context) {
if (!_coreIterableChecker.isAssignableFromType(targetType)) {
if (!coreIterableTypeChecker.isAssignableFromType(targetType)) {
return null;
}

var args = typeArgumentsOf(targetType, _coreIterableChecker);
assert(args.length == 1);

var keyType = args[0];
var itemType = coreIterableGenericType(targetType);

// This block will yield a regular list, which works fine for JSON
// Although it's possible that child elements may be marked unsafe

var isList = _coreListChecker.isAssignableFromType(targetType);
var subFieldValue =
context.serialize(_getIterableGenericType(targetType), _closureArg);
var subField = context.serialize(itemType, _closureArg);

var optionalQuestion = context.nullable ? '?' : '';

// In the case of trivial JSON types (int, String, etc), `subFieldValue`
// In the case of trivial JSON types (int, String, etc), `subField`
// will be identical to `substitute` – so no explicit mapping is needed.
// If they are not equal, then we to write out the substitution.
if (subFieldValue != _closureArg) {
if (subField != _closureArg) {
if (context.useWrappers && isList) {
var method = '\$wrapList';
if (context.nullable) {
method = '${method}HandleNull';
}

return '$method<$keyType>($expression, ($_closureArg) => $subFieldValue)';
return '$method<$itemType>($expression, ($_closureArg) => $subField)';
}

// TODO: the type could be imported from a library with a prefix!
expression =
'$expression$optionalQuestion.map(($_closureArg) => $subFieldValue)';
'$expression$optionalQuestion.map(($_closureArg) => $subField)';

// expression now represents an Iterable (even if it started as a List
// ...resetting `isList` to `false`.
Expand All @@ -66,11 +64,11 @@ class IterableHelper extends TypeHelper {
@override
String deserialize(
DartType targetType, String expression, DeserializeContext context) {
if (!_coreIterableChecker.isAssignableFromType(targetType)) {
if (!coreIterableTypeChecker.isAssignableFromType(targetType)) {
return null;
}

var iterableGenericType = _getIterableGenericType(targetType);
var iterableGenericType = coreIterableGenericType(targetType);

var itemSubVal = context.deserialize(iterableGenericType, _closureArg);

Expand All @@ -92,9 +90,4 @@ class IterableHelper extends TypeHelper {
}
}

DartType _getIterableGenericType(DartType type) =>
typeArgumentsOf(type, _coreIterableChecker).single;

final _coreIterableChecker = const TypeChecker.fromUrl('dart:core#Iterable');

final _coreListChecker = const TypeChecker.fromUrl('dart:core#List');
16 changes: 7 additions & 9 deletions json_serializable/lib/src/type_helpers/map_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/dart/element/type.dart';
import 'package:source_gen/source_gen.dart' show TypeChecker;

import '../shared_checkers.dart';
import '../type_helper.dart';
import '../utils.dart';

Expand All @@ -16,10 +17,10 @@ class MapHelper extends TypeHelper {
@override
String serialize(
DartType targetType, String expression, SerializeContext context) {
if (!_coreMapChecker.isAssignableFromType(targetType)) {
if (!coreMapTypeChecker.isAssignableFromType(targetType)) {
return null;
}
var args = typeArgumentsOf(targetType, _coreMapChecker);
var args = typeArgumentsOf(targetType, coreMapTypeChecker);
assert(args.length == 2);

var keyType = args[0];
Expand Down Expand Up @@ -52,14 +53,14 @@ class MapHelper extends TypeHelper {
@override
String deserialize(
DartType targetType, String expression, DeserializeContext context) {
if (!_coreMapChecker.isAssignableFromType(targetType)) {
if (!coreMapTypeChecker.isAssignableFromType(targetType)) {
return null;
}

// Just pass through if
// key: dynamic, Object, String
// value: dynamic, Object
var typeArgs = typeArgumentsOf(targetType, _coreMapChecker);
var typeArgs = typeArgumentsOf(targetType, coreMapTypeChecker);
assert(typeArgs.length == 2);
var keyArg = typeArgs.first;
var valueArg = typeArgs.last;
Expand Down Expand Up @@ -96,14 +97,11 @@ class MapHelper extends TypeHelper {
// So the only safe types for key are dynamic/Object/String
var safeKey = keyArg.isDynamic ||
keyArg.isObject ||
_stringTypeChecker.isExactlyType(keyArg);
coreStringTypeChecker.isExactlyType(keyArg);

if (!safeKey) {
throw new UnsupportedTypeError(keyArg, expression,
'The type of the Map key must be `String`, `Object` or `dynamic`.');
}
}
}

final _coreMapChecker = const TypeChecker.fromUrl('dart:core#Map');
final _stringTypeChecker = const TypeChecker.fromRuntime(String);
2 changes: 2 additions & 0 deletions json_serializable/lib/src/type_helpers/value_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import 'package:analyzer/dart/element/type.dart';
import 'package:source_gen/source_gen.dart' show TypeChecker;

import '../shared_checkers.dart';
import '../type_helper.dart';

class ValueHelper extends TypeHelper {
Expand Down
1 change: 1 addition & 0 deletions json_serializable/lib/type_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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/shared_checkers.dart' show simpleJsonTypeChecker, typeArgumentsOf;
export 'src/type_helper.dart';
export 'src/type_helpers/date_time_helper.dart';
export 'src/type_helpers/json_helper.dart';
Loading