Skip to content
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

Add option to drop nullable values in "toJson" #391

Merged
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
6 changes: 6 additions & 0 deletions examples/file_generation_mode/data_class_plugin_options.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# default config
auto_delete_code_from_annotation: false

json:
to_json:
options_config:
drop_null_values:
default: true

data_class:
options_config:
# default config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class _$UserImpl extends User {
return <String, dynamic>{
'id': id,
'username': username,
'email': email,
if (email != null) 'email': email,
'isVerified': isVerified,
};
}
Expand Down
15 changes: 14 additions & 1 deletion package/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ To create a custom configuration you need to add a file named `data_class_plugin
```

When `auto_delete_code_from_annotation` is `true` => `toJson` will be removed because is set as `false` in the `@DataClass`.

When `auto_delete_code_from_annotation` is `false` => `toJson` will be kept as it is even when `toJson` is set as `false` in the `@DataClass`. This allows to create custom `fromJson/toJson` implementations.

1. `json`
Expand Down Expand Up @@ -333,6 +333,19 @@ json:
- "a/glob/here"
- "another/glob/here"

# Allows to configure "toJson" code generation
# If no config is provided, null values will be dropped by default
to_json:
options_config:
drop_null_values:
default: boolean # default value is there is no match in enabled or disabled lists
enabled: # list of globs
- "a/glob/here"
- "another/glob/here"
disabled: # list of globs
- "a/glob/here"
- "another/glob/here"

data_class:
options_config:
# For each of the provided methods you can provide a configuration
Expand Down
3 changes: 3 additions & 0 deletions package/lib/src/backend/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ class DataClassPluginGenerator extends TachyonPluginCodeGenerator {
jsonKeyNameConventionGetter: jsonKeyNameConventionGetter,
classDeclarationFinder: declarationFinder.findClassOrEnum,
logger: logger,
dropNullValues: pluginOptions.json.toJson.effectiveDropNullValues(targetFileRelativePath),
).execute();
}

Expand Down Expand Up @@ -420,6 +421,8 @@ class DataClassPluginGenerator extends TachyonPluginCodeGenerator {
toJsonUnionKey: unionAnnotationValueExtractor.getString('unionJsonKey'),
classDeclarationFinder: declarationFinder.findClassOrEnum,
logger: logger,
dropNullValues:
pluginOptions.json.toJson.effectiveDropNullValues(targetFileRelativePath),
).execute();
}

Expand Down
16 changes: 15 additions & 1 deletion package/lib/src/backend/core/generators/to_json.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ class ToJsonGenerator implements Generator {
required final List<DeclarationInfo> fields,
required final JsonKeyNameConventionGetter jsonKeyNameConventionGetter,
required final ClassOrEnumDeclarationFinder classDeclarationFinder,
required final bool dropNullValues,
final String? toJsonUnionKey,
required final Logger logger,
}) : _codeWriter = codeWriter,
_fields = fields,
_jsonKeyNameConventionGetter = jsonKeyNameConventionGetter,
_classDeclarationFinder = classDeclarationFinder,
_dropNullValues = dropNullValues,
_toJsonUnionKey = toJsonUnionKey,
_logger = logger;

Expand All @@ -24,6 +26,7 @@ class ToJsonGenerator implements Generator {
final JsonKeyNameConventionGetter _jsonKeyNameConventionGetter;
final ClassOrEnumDeclarationFinder _classDeclarationFinder;
final String? _toJsonUnionKey;
final bool _dropNullValues;
final Logger _logger;

@override
Expand Down Expand Up @@ -82,6 +85,10 @@ class ToJsonGenerator implements Generator {
jsonKeyNameConvention.transform(fieldName.escapeDollarSign());
final TachyonDartType dartType = field.type?.customDartType ?? TachyonDartType.dynamic;

if (_dropNullValues && dartType.isNullable) {
_codeWriter.writeln('if ($fieldName != null)');
}

_codeWriter.write("'$jsonFieldName': ");

if (customJsonConverter != null) {
Expand Down Expand Up @@ -112,6 +119,9 @@ class ToJsonGenerator implements Generator {
void _writeNullableParsingPrefix({
required final String parentVariableName,
}) {
if (_dropNullValues) {
return;
}
_codeWriter.write('$parentVariableName == null ? null : ');
}

Expand Down Expand Up @@ -174,7 +184,11 @@ class ToJsonGenerator implements Generator {

if (typeDeclarationNode is ClassDeclaration && typeDeclarationNode.hasMethod('toJson') ||
typeDeclarationNode is EnumDeclaration && typeDeclarationNode.hasMethod('toJson')) {
final String accessOperator = dartType.isNullable ? '?.' : '.';
final String accessOperator = switch (dartType.isNullable) {
true when _dropNullValues => '!.',
true => '?.',
false => '.',
};
_codeWriter.writeln('$parentVariableName${accessOperator}toJson(),');
return;
}
Expand Down
4 changes: 4 additions & 0 deletions package/lib/src/options/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ extension OptionsGlobMatch on Map<String, OptionConfig> {
return _effectiveValue('when', filePath, defaultValue);
}

bool effectiveToJsonDropNullValues({required String filePath, required bool defaultValue}) {
return _effectiveValue('drop_null_values', filePath, defaultValue);
}

bool _effectiveValue(
String method,
String filePath,
Expand Down
26 changes: 26 additions & 0 deletions package/lib/src/options/json_options.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import 'package:data_class_plugin/data_class_plugin.dart';
import 'package:data_class_plugin/src/options/data_class_plugin_options.dart';
import 'package:data_class_plugin/src/options/extensions.dart';
import 'package:data_class_plugin/src/options/options_config.dart';

part 'json_options.gen.dart';

Expand All @@ -10,6 +13,7 @@ abstract class JsonOptions {
const factory JsonOptions({
String? keyNameConvention,
Map<String, List<String>> nameConventionGlobs,
ToJsonOptions toJson,
}) = _$JsonOptionsImpl;

/// Creates an instance of [JsonOptions] from [json]
Expand All @@ -20,4 +24,26 @@ abstract class JsonOptions {
@JsonKey(name: 'key_name_conventions')
@DefaultValue(<String, List<String>>{})
Map<String, List<String>> get nameConventionGlobs;

@DefaultValue(ToJsonOptions())
ToJsonOptions get toJson;
}

@DataClass()
abstract class ToJsonOptions {
const ToJsonOptions.ctor();

/// Creates an instance of [ToJsonOptions] from [json]
factory ToJsonOptions.fromJson(Map<dynamic, dynamic> json) = _$ToJsonOptionsImpl.fromJson;

/// Default constructor
const factory ToJsonOptions({
Map<String, OptionConfig> optionsConfig,
}) = _$ToJsonOptionsImpl;

@DefaultValue(<String, OptionConfig>{})
Map<String, OptionConfig> get optionsConfig;

bool effectiveDropNullValues(String filePath) =>
optionsConfig.effectiveToJsonDropNullValues(filePath: filePath, defaultValue: true);
}
33 changes: 33 additions & 0 deletions package/lib/src/options/json_options.gen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class _$JsonOptionsImpl extends JsonOptions {
const _$JsonOptionsImpl({
this.keyNameConvention,
Map<String, List<String>> nameConventionGlobs = const <String, List<String>>{},
this.toJson = const ToJsonOptions(),
}) : _nameConventionGlobs = nameConventionGlobs,
super.ctor();

Expand All @@ -20,6 +21,9 @@ class _$JsonOptionsImpl extends JsonOptions {
Map<String, List<String>>.unmodifiable(_nameConventionGlobs);
final Map<String, List<String>> _nameConventionGlobs;

@override
final ToJsonOptions toJson;

factory _$JsonOptionsImpl.fromJson(Map<dynamic, dynamic> json) {
return _$JsonOptionsImpl(
keyNameConvention: json['key_name_convention'] as String?,
Expand All @@ -32,9 +36,38 @@ class _$JsonOptionsImpl extends JsonOptions {
for (final dynamic i1 in (e0.value as List<dynamic>)) i1 as String,
],
},
toJson:
json['to_json'] == null ? const ToJsonOptions() : ToJsonOptions.fromJson(json['to_json']),
);
}

@override
Type get runtimeType => JsonOptions;
}

class _$ToJsonOptionsImpl extends ToJsonOptions {
const _$ToJsonOptionsImpl({
Map<String, OptionConfig> optionsConfig = const <String, OptionConfig>{},
}) : _optionsConfig = optionsConfig,
super.ctor();

@override
Map<String, OptionConfig> get optionsConfig =>
Map<String, OptionConfig>.unmodifiable(_optionsConfig);
final Map<String, OptionConfig> _optionsConfig;

factory _$ToJsonOptionsImpl.fromJson(Map<dynamic, dynamic> json) {
return _$ToJsonOptionsImpl(
optionsConfig: json['options_config'] == null
? const <String, OptionConfig>{}
: <String, OptionConfig>{
for (final MapEntry<dynamic, dynamic> e0
in (json['options_config'] as Map<dynamic, dynamic>).entries)
e0.key as String: OptionConfig.fromJson(e0.value),
},
);
}

@override
Type get runtimeType => ToJsonOptions;
}
Loading