Skip to content

Commit

Permalink
Support Function values for JsonKey.default value
Browse files Browse the repository at this point in the history
Update docs in json_annotation
Prepare to release json_serializable v6.5.0

Closes #1185
  • Loading branch information
kevmoo committed Oct 5, 2022
1 parent 7d3bf20 commit c121c0d
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 26 deletions.
5 changes: 5 additions & 0 deletions json_annotation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 4.7.1-dev

- Update `JsonKey` documentation to align with new features in
`package:json_serializable`.

## 4.7.0

- Added `JsonEnum.valueField` which allows specifying a field in an
Expand Down
14 changes: 10 additions & 4 deletions json_annotation/lib/src/json_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import 'json_serializable.dart';
class JsonKey {
/// The value to use if the source JSON does not contain this key or if the
/// value is `null`.
///
/// Also supported: a top-level or static [Function] or a constructor with no
/// required parameters and a return type compatible with the field being
/// assigned.
final Object? defaultValue;

/// If `true`, generated code will throw a [DisallowedNullValueException] if
Expand All @@ -30,8 +34,9 @@ class JsonKey {
/// A [Function] to use when decoding the associated JSON value to the
/// annotated field.
///
/// Must be a top-level or static [Function] that takes one argument mapping
/// a JSON literal to a value compatible with the type of the annotated field.
/// Must be a top-level or static [Function] or a constructor that accepts one
/// positional argument mapping a JSON literal to a value compatible with the
/// type of the annotated field.
///
/// When creating a class that supports both `toJson` and `fromJson`
/// (the default), you should also set [toJson] if you set [fromJson].
Expand Down Expand Up @@ -94,8 +99,9 @@ class JsonKey {

/// A [Function] to use when encoding the annotated field to JSON.
///
/// Must be a top-level or static [Function] with one parameter compatible
/// with the field being serialized that returns a JSON-compatible value.
/// Must be a top-level or static [Function] or a constructor that accepts one
/// positional argument compatible with the field being serialized that
/// returns a JSON-compatible value.
///
/// When creating a class that supports both `toJson` and `fromJson`
/// (the default), you should also set [fromJson] if you set [toJson].
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: 4.7.0
version: 4.7.1-dev
description: >-
Classes and helper functions that support JSON code generation via the
`json_serializable` package.
Expand Down
5 changes: 4 additions & 1 deletion json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
## 6.4.2-dev
## 6.5.0

- Allow constructors to be passed to `JsonKey` parameters that support
`Function` types.
- Accept `Function` values for `JsonKey.defaultValue`. The provided
`Function` will be invoked for the default value if the target JSON element is
missing or `null`.

## 6.4.1

Expand Down
45 changes: 33 additions & 12 deletions json_serializable/lib/src/json_key_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ KeyConfig _from(FieldElement element, ClassConfig classAnnotation) {
} else if (reader.isType) {
badType = 'Type';
} else if (dartObject.type is FunctionType) {
// TODO: Support calling function for the default value?
// Function types at the "root" are already handled. If they occur
// here, it's because the function is nested instead of a collection
// literal, which is NOT supported!
badType = 'Function';
} else if (!reader.isLiteral) {
badType = dartObject.type!.element2!.name;
Expand Down Expand Up @@ -126,11 +128,33 @@ KeyConfig _from(FieldElement element, ClassConfig classAnnotation) {
/// [fieldName] is not an `enum` value.
String? createAnnotationValue(String fieldName, {bool mustBeEnum = false}) {
final annotationValue = obj.read(fieldName);
late final DartType annotationType;

final enumFields = annotationValue.isNull
? null
: iterateEnumFields(annotationType = annotationValue.objectValue.type!);
if (annotationValue.isNull) {
return null;
}

final objectValue = annotationValue.objectValue;
final annotationType = objectValue.type!;

if (annotationType is FunctionType) {
// TODO: we could be a LOT more careful here, checking the return type
// and the number of parameters. BUT! If any of those things are wrong
// the generated code will be invalid, so skipping until we're bored
// later

final functionValue = objectValue.toFunctionValue()!;

// Is it a const constructor?!?!
var invokeConst = '';
if (functionValue is ConstructorElement && functionValue.isConst) {
invokeConst = 'const ';
}

return '$invokeConst${functionValue.qualifiedName}()';
}

final enumFields = iterateEnumFields(annotationType);

if (enumFields != null) {
if (mustBeEnum) {
late DartType targetEnumType;
Expand Down Expand Up @@ -170,15 +194,12 @@ KeyConfig _from(FieldElement element, ClassConfig classAnnotation) {
final enumValueNames =
enumFields.map((p) => p.name).toList(growable: false);

final enumValueName = enumValueForDartObject<String>(
annotationValue.objectValue, enumValueNames, (n) => n);
final enumValueName =
enumValueForDartObject<String>(objectValue, enumValueNames, (n) => n);

return '${annotationType.element2!.name}'
'.$enumValueName';
return '${annotationType.element2!.name}.$enumValueName';
} else {
final defaultValueLiteral = annotationValue.isNull
? null
: literalForObject(fieldName, annotationValue.objectValue, []);
final defaultValueLiteral = literalForObject(fieldName, objectValue, []);
if (defaultValueLiteral == null) {
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion json_serializable/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_serializable
version: 6.4.2-dev
version: 6.5.0
description: >-
Automatically generate code for converting to and from JSON by annotating
Dart classes.
Expand Down
15 changes: 14 additions & 1 deletion json_serializable/test/default_value/default_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import 'default_value_interface.dart'
ConstClass,
ConstClassConverter,
constClassFromJson,
constClassToJson;
constClassToJson,
intDefaultValueFunction;

part 'default_value.g.dart';

Expand Down Expand Up @@ -72,6 +73,15 @@ class DefaultValue implements dvi.DefaultValue {
@JsonKey(fromJson: constClassFromJson, toJson: constClassToJson)
ConstClass valueFromFunction;

@JsonKey(defaultValue: intDefaultValueFunction)
int intDefaultValueFromFunction;

@JsonKey(defaultValue: ConstClass.new)
ConstClass valueFromDefaultValueDefaultConstructor;

@JsonKey(defaultValue: ConstClass.easy)
ConstClass valueFromDefaultValueNamedConstructor;

DefaultValue(
this.fieldBool,
this.fieldString,
Expand All @@ -89,6 +99,9 @@ class DefaultValue implements dvi.DefaultValue {
this.constClass = const ConstClass('value'),
this.valueFromConverter = const ConstClass('value'),
this.valueFromFunction = const ConstClass('value'),
required this.intDefaultValueFromFunction,
required this.valueFromDefaultValueDefaultConstructor,
required this.valueFromDefaultValueNamedConstructor,
});

factory DefaultValue.fromJson(Map<String, dynamic> json) =>
Expand Down
20 changes: 20 additions & 0 deletions json_serializable/test/default_value/default_value.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import 'default_value_interface.dart'
ConstClass,
ConstClassConverter,
constClassFromJson,
constClassToJson;
constClassToJson,
intDefaultValueFunction;

part 'default_value.g_any_map__checked.g.dart';

Expand Down Expand Up @@ -75,6 +76,15 @@ class DefaultValue implements dvi.DefaultValue {
@JsonKey(fromJson: constClassFromJson, toJson: constClassToJson)
ConstClass valueFromFunction;

@JsonKey(defaultValue: intDefaultValueFunction)
int intDefaultValueFromFunction;

@JsonKey(defaultValue: ConstClass.new)
ConstClass valueFromDefaultValueDefaultConstructor;

@JsonKey(defaultValue: ConstClass.easy)
ConstClass valueFromDefaultValueNamedConstructor;

DefaultValue(
this.fieldBool,
this.fieldString,
Expand All @@ -92,6 +102,9 @@ class DefaultValue implements dvi.DefaultValue {
this.constClass = const ConstClass('value'),
this.valueFromConverter = const ConstClass('value'),
this.valueFromFunction = const ConstClass('value'),
required this.intDefaultValueFromFunction,
required this.valueFromDefaultValueDefaultConstructor,
required this.valueFromDefaultValueNamedConstructor,
});

factory DefaultValue.fromJson(Map<String, dynamic> json) =>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ abstract class DefaultValue {
ConstClass get valueFromConverter;

ConstClass get valueFromFunction;

int get intDefaultValueFromFunction;

ConstClass get valueFromDefaultValueDefaultConstructor;

ConstClass get valueFromDefaultValueNamedConstructor;
}

enum Greek { alpha, beta, gamma, delta }
Expand All @@ -44,7 +50,9 @@ enum Greek { alpha, beta, gamma, delta }
class ConstClass {
final String field;

const ConstClass(this.field);
const ConstClass([this.field = 'default']);

ConstClass.easy() : field = 'easy';

factory ConstClass.fromJson(Map<String, dynamic> json) => ConstClass(
json['field'] as String,
Expand All @@ -68,3 +76,5 @@ class ConstClassConverter extends JsonConverter<ConstClass, String> {
@override
String toJson(ConstClass object) => object.field;
}

int intDefaultValueFunction() => 43;
6 changes: 6 additions & 0 deletions json_serializable/test/default_value/default_value_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const _defaultInstance = {
'constClass': {'field': 'value'},
'valueFromConverter': 'value',
'valueFromFunction': 'value',
'intDefaultValueFromFunction': 43,
'valueFromDefaultValueDefaultConstructor': {'field': 'default'},
'valueFromDefaultValueNamedConstructor': {'field': 'easy'},
};

const _otherValues = {
Expand All @@ -50,6 +53,9 @@ const _otherValues = {
'constClass': {'field': 'otherValue'},
'valueFromConverter': 'otherValue',
'valueFromFunction': 'otherValue',
'intDefaultValueFromFunction': 44,
'valueFromDefaultValueDefaultConstructor': {'field': 'other'},
'valueFromDefaultValueNamedConstructor': {'field': 'other'},
};

void main() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ class DefaultValueImplicit implements dvi.DefaultValue {
@JsonKey(fromJson: constClassFromJson, toJson: constClassToJson)
ConstClass valueFromFunction;

int intDefaultValueFromFunction;

ConstClass valueFromDefaultValueDefaultConstructor;

ConstClass valueFromDefaultValueNamedConstructor;

DefaultValueImplicit({
this.fieldBool = true,
this.fieldString = 'string',
Expand All @@ -65,6 +71,9 @@ class DefaultValueImplicit implements dvi.DefaultValue {
this.constClass = const ConstClass('value'),
this.valueFromConverter = const ConstClass('value'),
this.valueFromFunction = const ConstClass('value'),
this.intDefaultValueFromFunction = 43,
this.valueFromDefaultValueDefaultConstructor = const ConstClass(),
this.valueFromDefaultValueNamedConstructor = const ConstClass('easy'),
});

factory DefaultValueImplicit.fromJson(Map<String, dynamic> json) =>
Expand Down
19 changes: 19 additions & 0 deletions json_serializable/test/default_value/implicit_default_value.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions json_serializable/test/json_serializable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const _expectedAnnotatedTests = {
'DefaultWithConstObject',
'DefaultWithDisallowNullRequiredClass',
'DefaultWithFunction',
'DefaultWithFunctionInList',
'DefaultWithNestedEnum',
'DefaultWithSymbol',
'DefaultWithToJsonClass',
Expand Down

0 comments on commit c121c0d

Please sign in to comment.