Skip to content

Introduced ignore attribute to JsonKey. When set to true the generato… #118

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
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
89b1bf5
Introduced ignore attribute to JsonKey. When set to true the generato…
AlexanderJohr Mar 21, 2018
2aa72d7
Corrected dartdoc comments of JsonKey.ignore.
AlexanderJohr Mar 25, 2018
6cfdfe6
Added ignored field to integration test.
AlexanderJohr Mar 25, 2018
37e6d64
Added tests 'fails if ignored field is referenced by ctor' and 'fails…
AlexanderJohr Mar 25, 2018
a09d338
Updated changelog with ignore and private field exclusion.
AlexanderJohr Mar 25, 2018
4e5c847
Increased minor version to 0.2.3 and updated changelog to include Jso…
AlexanderJohr Mar 25, 2018
effb034
Made dartanalyzer ignore the private unused field _privateField of te…
AlexanderJohr Mar 25, 2018
e7d2463
Fixed dartanalyzer issues.
AlexanderJohr Mar 25, 2018
ce47871
Explicitly exclude alpha releases of pkg:analyzer with deprecations
kevmoo Mar 25, 2018
bdcab6b
Introduced ignore attribute to JsonKey. When set to true the generato…
AlexanderJohr Mar 21, 2018
b8da965
Corrected dartdoc comments of JsonKey.ignore.
AlexanderJohr Mar 25, 2018
7b47f13
Added ignored field to integration test.
AlexanderJohr Mar 25, 2018
8c3a952
Added tests 'fails if ignored field is referenced by ctor' and 'fails…
AlexanderJohr Mar 25, 2018
4f55496
Updated changelog with ignore and private field exclusion.
AlexanderJohr Mar 25, 2018
dd31734
Increased minor version to 0.2.3 and updated changelog to include Jso…
AlexanderJohr Mar 25, 2018
d155fcb
Made dartanalyzer ignore the private unused field _privateField of te…
AlexanderJohr Mar 25, 2018
d056be3
Fixed dartanalyzer issues.
AlexanderJohr Mar 25, 2018
c1cadbf
Merge branch 'ignore-private-and-ignore-annotated-fields' of https://…
AlexanderJohr Mar 26, 2018
c99786d
Tweak to analyzer dep
kevmoo Mar 27, 2018
f4ec841
Update constraint on pkg:json_annotation
kevmoo Mar 27, 2018
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.3

* Added `ignore` field to `JsonKey` class annotation

## 0.2.2

* Added a helper class – `$JsonMapWrapper` – and helper functions – `$wrapMap`,
Expand Down
8 changes: 7 additions & 1 deletion json_annotation/lib/src/json_serializable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,16 @@ class JsonKey {
/// enclosing class.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also: bump the minor version of json_annotation in the pubspec to 0.2.3-dev.

Add an entry to the changelog (under 0.2.3) that includes this change.

final bool includeIfNull;

/// `true` if the generator should ignore this field completely.
///
/// If `null` (the default) or `false`, the field will be considered for
/// serialization.
final bool ignore;

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

// TODO(kevmoo): Add documentation
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.2
version: 0.2.3-dev
description: Annotations for the json_serializable package
homepage: https://github.com/dart-lang/json_serializable
author: Dart Team <misc@dartlang.org>
Expand Down
4 changes: 4 additions & 0 deletions json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
`DeserializeContext` is now readonly. This would potentially break code that
extends these classes – which is not expected.

* Private and ignored fields are now excluded when generating serialization and deserialization code.

* Throw an exception if a private field, or an ignored field is referenced by a required constructor argument.

## 0.4.0

* **Potentially Breaking** Inherited fields are now processed and used
Expand Down
73 changes: 47 additions & 26 deletions json_serializable/lib/src/json_serializable_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:collection';

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

// ignore: implementation_imports
import 'package:analyzer/src/dart/resolver/inheritance_manager.dart'
show InheritanceManager;
Expand Down Expand Up @@ -81,7 +82,10 @@ class JsonSerializableGenerator

// Get all of the fields that need to be assigned
// TODO: support overriding the field set with an annotation option
var fieldsList = _listFields(classElement);
var fieldList = _listFields(classElement);
var fieldsList = fieldList
.where((field) => field.isPublic && _jsonKeyFor(field).ignore != true)
.toList();

var undefinedFields =
fieldsList.where((fe) => fe.type.isUndefined).toList();
Expand Down Expand Up @@ -112,14 +116,17 @@ class JsonSerializableGenerator
final classAnnotation = _valueForAnnotation(annotation);

if (classAnnotation.createFactory) {
var toSkip = _writeFactory(
buffer, classElement, fields, prefix, classAnnotation.nullable);
var ignoredFields = new Map<String, FieldElement>.fromIterable(
fieldList.where(
(field) => !field.isPublic || _jsonKeyFor(field).ignore == true),
key: (f) => (f as FieldElement).name);

var fieldsSetByFactory = _writeFactory(buffer, classElement, fields,
ignoredFields, prefix, classAnnotation.nullable);

// If there are fields that are final – that are not set via the generated
// constructor, then don't output them when generating the `toJson` call.
for (var field in toSkip) {
fields.remove(field.name);
}
fields.removeWhere((key, field) => !fieldsSetByFactory.contains(field));
}

// Now we check for duplicate JSON keys due to colliding annotations.
Expand Down Expand Up @@ -231,7 +238,8 @@ class JsonSerializableGenerator
for (var field in fields.values) {
var valueAccess = '_v.${field.name}';
buffer.write('''case ${_safeNameAccess(field)}:
return ${_serializeField(field, classAnnotation.nullable, accessOverride: valueAccess)};''');
return ${_serializeField(
field, classAnnotation.nullable, accessOverride: valueAccess)};''');
}

buffer.writeln('''
Expand Down Expand Up @@ -306,23 +314,26 @@ void $toJsonMapHelperName(String key, dynamic value) {

var pairs = <String>[];
for (var field in fields) {
pairs.add(
'${_safeNameAccess(field)}: ${_serializeField(field, classSupportNullable )}');
pairs.add('${_safeNameAccess(field)}: ${_serializeField(
field, classSupportNullable)}');
}
buffer.writeAll(pairs, ',\n');

buffer.writeln(' };');
}

/// Returns the set of fields that are not written to via constructors.
/// Returns the set of fields that are written to via factory.
/// Includes final fields that are written to in the constructor parameter list
/// Excludes remaining final fields, as they can't be set in the factory body
/// and shoudn't generated with toJson
Set<FieldElement> _writeFactory(
StringBuffer buffer,
ClassElement classElement,
Map<String, FieldElement> fields,
Map<String, FieldElement> ignoredFields,
String prefix,
bool classSupportNullable) {
// creating a copy so it can be mutated
var fieldsToSet = new Map<String, FieldElement>.from(fields);
var fieldsSetByFactory = new Set<FieldElement>();
var className = classElement.displayName;
// Create the factory method

Expand All @@ -342,9 +353,20 @@ void $toJsonMapHelperName(String key, dynamic value) {

if (field == null) {
if (arg.parameterKind == ParameterKind.REQUIRED) {
var additionalInfo = '';
var ignoredField = ignoredFields[arg.name];
if (ignoredField != null) {
if (_jsonKeyFor(ignoredField).ignore == true) {
additionalInfo = ' It it assigns to an ignored field.';
} else if (!ignoredField.isPublic) {
additionalInfo = ' It it assigns to a non public field.';
}
}

throw new UnsupportedError('Cannot populate the required constructor '
'argument: ${arg.displayName}.');
'argument: ${arg.displayName}.$additionalInfo');
}

continue;
}

Expand All @@ -354,7 +376,7 @@ void $toJsonMapHelperName(String key, dynamic value) {
} else {
ctorArguments.add(arg);
}
fieldsToSet.remove(arg.name);
fieldsSetByFactory.add(field);
}

var undefinedArgs = [ctorArguments, ctorNamedArguments]
Expand All @@ -370,14 +392,11 @@ void $toJsonMapHelperName(String key, dynamic value) {
todo: 'Check names and imports.');
}

// these are fields to skip – now to find them
var finalFields =
fieldsToSet.values.where((field) => field.isFinal).toSet();

for (var finalField in finalFields) {
var value = fieldsToSet.remove(finalField.name);
assert(value == finalField);
}
// find fields that aren't already set by the constructor and that aren't final
var remainingFieldsForFactoryBody = fields.values
.where((field) => !field.isFinal)
.toSet()
.difference(fieldsSetByFactory);

//
// Generate the static factory method
Expand All @@ -402,19 +421,20 @@ void $toJsonMapHelperName(String key, dynamic value) {
}), ', ');

buffer.write(')');
if (fieldsToSet.isEmpty) {
if (remainingFieldsForFactoryBody.isEmpty) {
buffer.writeln(';');
} else {
for (var field in fieldsToSet.values) {
for (var field in remainingFieldsForFactoryBody) {
buffer.writeln();
buffer.write(' ..${field.name} = ');
buffer.write(_deserializeForField(field, classSupportNullable));
fieldsSetByFactory.add(field);
}
buffer.writeln(';');
}
buffer.writeln();

return finalFields;
return fieldsSetByFactory;
}

Iterable<TypeHelper> get _allHelpers =>
Expand Down Expand Up @@ -519,7 +539,8 @@ JsonKey _jsonKeyFor(FieldElement element) {
: new JsonKey(
name: obj.getField('name').toStringValue(),
nullable: obj.getField('nullable').toBoolValue(),
includeIfNull: obj.getField('includeIfNull').toBoolValue());
includeIfNull: obj.getField('includeIfNull').toBoolValue(),
ignore: obj.getField('ignore').toBoolValue());
}

return key;
Expand Down
6 changes: 5 additions & 1 deletion json_serializable/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,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.2 <0.2.3'
json_annotation: '>=0.2.3 <0.2.4'
path: ^1.3.2
source_gen: ^0.7.5
dev_dependencies:
Expand All @@ -22,3 +22,7 @@ dev_dependencies:
collection: ^1.14.0
dart_style: ^1.0.0
test: ^0.12.3

dependency_overrides:
json_annotation:
path: ../json_annotation
50 changes: 50 additions & 0 deletions json_serializable/test/json_serializable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,23 @@ void _registerTests(JsonSerializableGenerator generator) {
expect(generateResult, contains('Map<String, dynamic> toJson()'));
});

if (!generator.useWrappers) {
test('includes final field in toJson when set in ctor', () async {
var generateResult = await runForElementNamed('FinalFields');
expect(generateResult, contains('new FinalFields(json[\'a\'] as int);'));
expect(
generateResult, contains('toJson() => <String, dynamic>{\'a\': a};'));
});

test('excludes final field in toJson when not set in ctor', () async {
var generateResult = await runForElementNamed('FinalFieldsNotSetInCtor');
expect(generateResult,
isNot(contains('new FinalFields(json[\'a\'] as int);')));
expect(generateResult,
isNot(contains('toJson() => <String, dynamic>{\'a\': a};')));
});
}

group('valid inputs', () {
if (!generator.useWrappers) {
test('class with no ctor params', () async {
Expand Down Expand Up @@ -233,6 +250,39 @@ abstract class _$OrderSerializerMixin {
});
}

if (!generator.useWrappers) {
test('works to ignore a field', () async {
var output = await runForElementNamed('IgnoredFieldClass');

expect(output, contains("'ignoredFalseField': ignoredFalseField,"));
expect(output, contains("'ignoredNullField': ignoredNullField"));
expect(output, isNot(contains("'ignoredTrueField': ignoredTrueField")));
});
}

if (!generator.useWrappers) {
test('fails if ignored field is referenced by ctor', () async {
expect(
() => runForElementNamed('IgnoredFieldCtorClass'),
throwsA(new FeatureMatcher<UnsupportedError>(
'message',
(e) => e.message,
'Cannot populate the required constructor argument: '
'ignoredTrueField. It it assigns to an ignored field.')));
});
}
if (!generator.useWrappers) {
test('fails if private field is referenced by ctor', () async {
expect(
() => runForElementNamed('PrivateFieldCtorClass'),
throwsA(new FeatureMatcher<UnsupportedError>(
'message',
(e) => e.message,
'Cannot populate the required constructor argument: '
'_privateField. It it assigns to a non public field.')));
});
}

test('fails if name duplicates existing field', () async {
expect(
() => runForElementNamed('KeyDupesField'),
Expand Down
32 changes: 32 additions & 0 deletions json_serializable/test/src/json_serializable_test_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ class FinalFields {
FinalFields(this.a);
}

@JsonSerializable()
class FinalFieldsNotSetInCtor {
final int a = 1;

FinalFieldsNotSetInCtor();
}

@JsonSerializable()
class FromJsonOptionalParameters {
final ChildWithFromJson child;
Expand Down Expand Up @@ -154,6 +161,31 @@ class DupeKeys {
String str;
}

@JsonSerializable(createFactory: false)
class IgnoredFieldClass {
@JsonKey(ignore: true)
int ignoredTrueField;

@JsonKey(ignore: false)
int ignoredFalseField;

int ignoredNullField;
}

@JsonSerializable()
class IgnoredFieldCtorClass {
@JsonKey(ignore: true)
int ignoredTrueField;
IgnoredFieldCtorClass(this.ignoredTrueField);
}

@JsonSerializable()
class PrivateFieldCtorClass {
// ignore: unused_field
int _privateField;
PrivateFieldCtorClass(this._privateField);
}

@JsonSerializable()
class SubType extends SuperType {
final int subTypeViaCtor;
Expand Down
7 changes: 7 additions & 0 deletions json_serializable/test/test_files/json_test_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ class Order extends Object with _$OrderSerializerMixin {
Platform platform;
Map<String, Platform> altPlatforms;

@JsonKey(ignore: true)
String get platformValue => platform?.description;

set platformValue(String value) {
throw new UnimplementedError('not impld');
}

int get price => items.fold(0, (total, item) => item.price + total);

Order(this.category, [Iterable<Item> items])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ class Order extends Object with _$OrderSerializerMixin {
Platform platform;
Map<String, Platform> altPlatforms;

@JsonKey(ignore: true)
String get platformValue => platform?.description;

set platformValue(String value) {
throw new UnimplementedError('not impld');
}

int get price => items.fold(0, (total, item) => item.price + total);

Order(this.category, [Iterable<Item> items])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ class Order extends Object with _$OrderSerializerMixin {
Platform platform;
Map<String, Platform> altPlatforms;

@JsonKey(ignore: true)
String get platformValue => platform?.description;

set platformValue(String value) {
throw new UnimplementedError('not impld');
}

int get price => items.fold(0, (total, item) => item.price + total);

Order(this.category, [Iterable<Item> items])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ class Order extends Object with _$OrderSerializerMixin {
Platform platform;
Map<String, Platform> altPlatforms;

@JsonKey(ignore: true)
String get platformValue => platform?.description;

set platformValue(String value) {
throw new UnimplementedError('not impld');
}

int get price => items.fold(0, (total, item) => item.price + total);

Order(this.category, [Iterable<Item> items])
Expand Down