Skip to content

Add fromJson and toJson fields to JsonKey class #146

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 2 commits into from
May 9, 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
4 changes: 4 additions & 0 deletions json_annotation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.4

* Added `fromJson` and `toJson` fields to `JsonKey` class.

## 0.2.3

* Added `ignore` field to `JsonKey` class annotation
Expand Down
16 changes: 15 additions & 1 deletion json_annotation/lib/src/json_serializable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,24 @@ class JsonKey {
/// serialization.
final bool ignore;

/// A top-level [Function] to use when deserializing the associated JSON
/// value to the annotated field.
final Function fromJson;

/// A top-level [Function] to use when serializing the annotated field to
/// JSON.
final Function toJson;

/// Creates a new [JsonKey].
///
/// Only required when the default behavior is not desired.
const JsonKey({this.name, this.nullable, this.includeIfNull, this.ignore});
const JsonKey(
{this.name,
this.nullable,
this.includeIfNull,
this.ignore,
this.fromJson,
this.toJson});
}

/// Helper classes used in generated code when
Expand Down
2 changes: 1 addition & 1 deletion json_annotation/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_annotation
version: 0.2.3
version: 0.2.4-dev
description: Annotations for the json_serializable package
homepage: https://github.com/dart-lang/json_serializable
author: Dart Team <misc@dartlang.org>
Expand Down
3 changes: 3 additions & 0 deletions json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
## 0.5.1

* Support new `fromJson` and `toJson` fields on `JsonKey`.

* Use `log` exposed by `package:build`. This requires end-users to have at least
`package:build_runner` `^0.8.2`.

* Updated minimum `package:source_gen` dependency to `0.8.1` which includes
improved error messages.

Expand Down
3 changes: 2 additions & 1 deletion json_serializable/lib/src/generator_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,8 @@ void $toJsonMapHelperName(String key, dynamic value) {
}

TypeHelperContext _getHelperContext(FieldElement field) =>
new TypeHelperContext(_generator, field.metadata, _nullable(field));
new TypeHelperContext(
_generator, field.metadata, _nullable(field), jsonKeyFor(field));

/// Returns `true` if the field can be written to JSON 'naively' – meaning
/// we can avoid checking for `null`.
Expand Down
114 changes: 105 additions & 9 deletions json_serializable/lib/src/json_key_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@
// 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/analyzer.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/constant/value.dart';

import 'package:json_annotation/json_annotation.dart';
import 'package:meta/meta.dart';
import 'package:source_gen/source_gen.dart';

final _jsonKeyExpando = new Expando<JsonKey>();
@alwaysThrows
T _throwUnsupported<T>(FieldElement element, String message) =>
throw new InvalidGenerationSourceError(
'Error with `@JsonKey` on `${element.displayName}`. $message',
element: element);

final _jsonKeyExpando = new Expando<JsonKeyWithConversion>();

final _jsonKeyChecker = const TypeChecker.fromRuntime(JsonKey);

JsonKey jsonKeyFor(FieldElement element) {
JsonKeyWithConversion jsonKeyFor(FieldElement element) {
var key = _jsonKeyExpando[element];

if (key == null) {
Expand All @@ -21,14 +31,100 @@ JsonKey jsonKeyFor(FieldElement element) {
var obj = _jsonKeyChecker.firstAnnotationOfExact(element) ??
_jsonKeyChecker.firstAnnotationOfExact(element.getter);

_jsonKeyExpando[element] = key = obj == null
? const JsonKey()
: new JsonKey(
name: obj.getField('name').toStringValue(),
nullable: obj.getField('nullable').toBoolValue(),
includeIfNull: obj.getField('includeIfNull').toBoolValue(),
ignore: obj.getField('ignore').toBoolValue());
_jsonKeyExpando[element] = key = _from(element, obj);
}

return key;
}

JsonKeyWithConversion _from(FieldElement element, DartObject obj) {
if (obj == null) {
return const JsonKeyWithConversion._empty();
}
var fromJsonName = _getFunctionName(obj, element, true);
var toJsonName = _getFunctionName(obj, element, false);

return new JsonKeyWithConversion._(
obj.getField('name').toStringValue(),
obj.getField('nullable').toBoolValue(),
obj.getField('includeIfNull').toBoolValue(),
obj.getField('ignore').toBoolValue(),
fromJsonName,
toJsonName);
}

class ConvertData {
final String name;
final DartType paramType;

ConvertData._(this.name, this.paramType);
}

class JsonKeyWithConversion extends JsonKey {
final ConvertData fromJsonData;
final ConvertData toJsonData;

const JsonKeyWithConversion._empty()
: fromJsonData = null,
toJsonData = null,
super();

JsonKeyWithConversion._(String name, bool nullable, bool includeIfNull,
bool ignore, this.fromJsonData, this.toJsonData)
: super(
name: name,
nullable: nullable,
includeIfNull: includeIfNull,
ignore: ignore);
}

ConvertData _getFunctionName(
DartObject obj, FieldElement element, bool isFrom) {
var paramName = isFrom ? 'fromJson' : 'toJson';
var objectValue = obj.getField(paramName);

if (objectValue.isNull) {
return null;
}

var type = objectValue.type as FunctionType;

if (type.element is MethodElement) {
_throwUnsupported(
element,
'The function provided for `$paramName` must be top-level. '
'Static class methods (`${type.element.name}`) are not supported.');
}
var functionElement = type.element as FunctionElement;

var positionalParams = functionElement.parameters
.where((pe) => pe.parameterKind == ParameterKind.REQUIRED)
.toList();

if (positionalParams.length != 1) {
_throwUnsupported(
element,
'The `$paramName` function `${functionElement.name}` must have one '
'positional paramater.');
}

if (isFrom) {
var returnType = functionElement.returnType;
if (!returnType.isAssignableTo(element.type)) {
_throwUnsupported(
element,
'The `$paramName` function `${functionElement.name}` return type '
'`$returnType` is not compatible with field type `${element.type}`.');
}
} else {
var argType = positionalParams.single.type;
if (!element.type.isAssignableTo(argType)) {
_throwUnsupported(
element,
'The `$paramName` function `${functionElement.name}` argument type '
'`$argType` is not compatible with field type'
' `${element.type}`.');
}
}
return new ConvertData._(functionElement.name, positionalParams.single.type);
}
2 changes: 2 additions & 0 deletions json_serializable/lib/src/json_serializable_generator.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 'generator_helper.dart';
import 'type_helper.dart';
import 'type_helpers/convert_helper.dart';
import 'type_helpers/date_time_helper.dart';
import 'type_helpers/enum_helper.dart';
import 'type_helpers/iterable_helper.dart';
Expand All @@ -24,6 +25,7 @@ Iterable<TypeHelper> allHelpersImpl(JsonSerializableGenerator generator) =>
class JsonSerializableGenerator
extends GeneratorForAnnotation<JsonSerializable> {
static const _coreHelpers = const [
const ConvertHelper(),
const IterableHelper(),
const MapHelper(),
const EnumHelper(),
Expand Down
6 changes: 5 additions & 1 deletion json_serializable/lib/src/type_helper_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';

import 'json_key_helpers.dart';
import 'json_serializable_generator.dart';
import 'type_helper.dart';

Expand All @@ -20,7 +21,10 @@ class TypeHelperContext implements SerializeContext, DeserializeContext {
@override
final bool nullable;

TypeHelperContext(this._generator, this.metadata, this.nullable);
final JsonKeyWithConversion jsonKey;

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

@override
String serialize(DartType targetType, String expression) => _run(
Expand Down
42 changes: 42 additions & 0 deletions json_serializable/lib/src/type_helpers/convert_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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 '../type_helper.dart';
import '../type_helper_context.dart';
import '../utils.dart';

class ConvertHelper extends TypeHelper {
const ConvertHelper();

@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 result = '${jsonKey.toJsonData.name}($expression)';
return commonNullPrefix(context.nullable, expression, result);
}
return null;
}

@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)';
return commonNullPrefix(context.nullable, expression, result);
}
return null;
}
}
6 changes: 5 additions & 1 deletion json_serializable/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies:

# Use a tight version constraint to ensure that a constraint on
# `json_annotation`. Properly constrains all features it provides.
json_annotation: '>=0.2.3 <0.2.4'
json_annotation: '>=0.2.4 <0.2.5'
path: ^1.3.2
source_gen: '>=0.8.1 <0.9.0'
dev_dependencies:
Expand All @@ -21,3 +21,7 @@ dev_dependencies:
collection: ^1.14.0
dart_style: ^1.0.0
test: ^0.12.3

dependency_overrides:
json_annotation:
path: ../json_annotation
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ void main() {
..nums = [0, 0.0]
..doubles = [0.0]
..nnDoubles = [0.0]
..ints = [0]);
..ints = [0]
..duration = const Duration(seconds: 1));
});

test('support ints as doubles', () {
Expand Down
Loading