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

Bug report - @JsonKey(includeFromJson: false) malfunctions #1334

Open
APPXOTICA opened this issue Jun 30, 2023 · 4 comments
Open

Bug report - @JsonKey(includeFromJson: false) malfunctions #1334

APPXOTICA opened this issue Jun 30, 2023 · 4 comments
Assignees

Comments

@APPXOTICA
Copy link

APPXOTICA commented Jun 30, 2023

Here's a test class

@JsonSerializable()
class TestClass {
  TestClass({
    this.a,
  });

  @JsonKey(includeFromJson: false)
  String? a;

  factory TestClass.fromJson(Map<String, dynamic> json) => _$TestClassFromJson(json);
  Map<String, dynamic> toJson() => _$TestClassToJson(this);
}

Generated code.

TestClass _$TestClassFromJson(Map<String, dynamic> json) => TestClass();

Map<String, dynamic> _$TestClassToJson(TestClass instance) =>
    <String, dynamic>{};

TestClassToJson should include String a but it doesn't. It excludes String a from TestClassFromJson and TestClassToJson both.

json_serializable: ^6.7.0
json_annotation: ^4.8.1

@kevmoo kevmoo self-assigned this Jul 3, 2023
@CicadaCinema
Copy link
Contributor

Note the docstring:

/// `null` (the default) means the field will be handled with the default
/// semantics that take into account if it's private or if it can be cleanly
/// round-tripped to-from JSON.

The current behaviour can be summarised by these test cases: ToJsonNullFromJsonFalsePublic and ToJsonFalseFromJsonNullPublic

Summary:

  • for @JsonKey(includeFromJson: false) the field is included in neither toJson nor fromJson
  • for @JsonKey(includeToJson: false) the field is included in fromJson, but not in toJson

I take round-tripped to-from JSON to mean a round trip from TestClass to Map<String, dynamic> and back to TestClass, however the following docstrings seem to imply that we must implement toJson if and only if we implement fromJson:

/// When creating a class that supports both `toJson` and `fromJson`
/// (the default), you should also set [toJson] if you set [fromJson].
/// Values returned by [toJson] should "round-trip" through [fromJson].
final Function? fromJson;

/// When creating a class that supports both `toJson` and `fromJson`
/// (the default), you should also set [fromJson] if you set [toJson].
/// Values returned by [toJson] should "round-trip" through [fromJson].
final Function? toJson;

To me it sounds like we only care about the to-from round-trip and that line 42 is a mistake (it should be replaced by the contents of line 138).

Maybe this is an incorrect assumption, given that these docstrings have survived this long without modification.

In either case, it seems that the current behaviour can break if we attempt a to-from round trip, as described above:

@JsonSerializable()
class TestClass {
  TestClass({
    required this.a,
  });

  @JsonKey(includeToJson: false)
  String a;

  factory TestClass.fromJson(Map<String, dynamic> json) =>
      _$TestClassFromJson(json);
  Map<String, dynamic> toJson() => _$TestClassToJson(this);
}

void main() {
  final json = TestClass(a: 'aaa').toJson();
  final test = TestClass.fromJson(json);
}

Generated code:

TestClass _$TestClassFromJson(Map<String, dynamic> json) => TestClass(
      a: json['a'] as String,
    );

Map<String, dynamic> _$TestClassToJson(TestClass instance) =>
    <String, dynamic>{};
$ dart run lib/testme.dart 
Unhandled exception:
type 'Null' is not a subtype of type 'String' in type cast
#0      _$TestClassFromJson (package:testme/testme.g.dart:10:20)
#1      new TestClass.fromJson (package:testme/testme.dart:14:7)
#2      main (package:testme/testme.dart:20:26)
#3      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:296:19)
#4      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)

Dependency versions:

json_annotation 4.8.1
json_serializable 6.7.1

@CicadaCinema
Copy link
Contributor

CicadaCinema commented Jul 11, 2023

If my assumption is correct, I propose we correct the docs to only refer to a to-from round-trip, and fix the current behaviour when attempting this round trip.

Also, I see no reason to add to the docstrings for includeFromJson and includeToJson, adding specifically what will happen if the class is private or if it can't be round-tripped.

References:

#1256

includeToJson = includeFromJson = !ignore;

@gmaggio
Copy link

gmaggio commented Nov 9, 2023

You may have to specify explicitly includeToJson: true, too

  @JsonKey(
    includeFromJson: false,
    includeToJson: true,
  )
  String? a;

@edlman
Copy link

edlman commented May 11, 2024

I encountered the same (for me strange) behavior. Why is field excluded from "toJson" method when I set "includeFromJson: false"? I don't see any reason for this.

  • @JsonKey() String? a; // OK, generates code for "a" in both toJson a fromJson methods
  • @JsonKey(includeToJson: false) String? a; // OK, generates code for "a" in fromJson, and does not in toJson
  • @JsonKey(includeFromJson: false) String? a; // NOK, does not generate code for "a" in fromJson (ok), BUT even not in toJson, behaves just like @JsonKey(ignore: true) or @JsonKey(includeFromJson: false, includeToJson: false)
  • @JsonKey(includeFromJson: false, includeToJson: true) String? a; // OK, generates code for "a" in toJson, not in fromJson.

So "includeToJson: true" must be explicitly set when using "includeFromJson: false"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants