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

dart:convert jsonEncode() throws an exception if the method toJson() is defined in an extension. #42742

Closed
simphotonics opened this issue Jul 17, 2020 · 3 comments

Comments

@simphotonics
Copy link

simphotonics commented Jul 17, 2020

My SDK version is:

Dart VM version: 2.9.0-14.1.beta (beta) (Tue Jun 9 10:52:57 2020 +0200) on "linux_x64"

When attempting to move the method toJson() to an extension I noticed that jsonEncode() does not behave as expected.
If the method toJson() is defined within the class A (see below) the program produces the expected output:

{"index":0}

However, if toJson() is defined within the extension JsonA dart:convert does not seem to have access to toJson and a call to jsonEncode results in this unhandled exception:

Unhandled exception:
Converting object to an encodable object failed: Instance of 'A'
#0      _JsonStringifier.writeObject (dart:convert/json.dart:687:7)
#1      _JsonStringStringifier.printOn (dart:convert/json.dart:876:17)
#2      _JsonStringStringifier.stringify (dart:convert/json.dart:861:5)
#3      JsonEncoder.convert (dart:convert/json.dart:261:30)
#4      JsonCodec.encode (dart:convert/json.dart:171:45)
#5      jsonEncode (dart:convert/json.dart:81:10)
#6      main (file:///home/dan/WORK/RemoteFlutterProjects/generic_enum/generic_enum/example/bin/example.dart:20:9)
#7      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#8      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
import 'dart:convert';

class A {
  const A(this.a);
  final int a;
  static const values = [A(1), A(2)];

  // Map<String, dynamic> toJson() => {
  //       'index': A.values.indexOf(this),
  //     };
}

extension JsonA on A {
  Map<String, dynamic> toJson() => {
        'index': A.values.indexOf(this),
      };
}

main() {
  print(jsonEncode(A.values[0]));
}
@julemand101
Copy link
Contributor

Extensions are dependent on static types and does therefore not work on dynamic:
https://dart.dev/guides/language/extension-methods#static-types-and-dynamic

If using the debugger we can see the next part of the stacktrace:

#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1      _defaultToEncodable (dart:convert/json.dart:521:55)
#2      _JsonStringifier.writeObject (dart:convert/json.dart:653:36)
#3      _JsonStringStringifier.printOn (dart:convert/json.dart:846:17)
#4      _JsonStringStringifier.stringify (dart:convert/json.dart:831:5)
#5      JsonEncoder.convert (dart:convert/json.dart:260:30)
#6      JsonCodec.encode (dart:convert/json.dart:168:45)
#7      jsonEncode (dart:convert/json.dart:82:10)
#8      main (file:///C:/Users/jacob/Documents/GitHub/AdventOfCode2019/bin/stackoverflow.dart:22:9)
#9      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#10     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

The _defaultToEncodable method is defined as:

dynamic _defaultToEncodable(dynamic object) => object.toJson();

We can reproduce this problem fairly simple by:

dynamic _defaultToEncodable(dynamic object) => object.toJson();

class A { }

extension JsonA on A {
  void toJson() => print('Hey');
}

void main() {
  print(_defaultToEncodable(A()));
}

Which returns:

Unhandled exception:
NoSuchMethodError: Class 'A' has no instance method 'toJson'.
Receiver: Instance of 'A'
Tried calling: toJson()
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1      _defaultToEncodable (file:///C:/Users/jacob/Documents/GitHub/AdventOfCode2019/bin/stackoverflow.dart:1:55)
#2      main (file:///C:/Users/jacob/Documents/GitHub/AdventOfCode2019/bin/stackoverflow.dart:10:9)
#3      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#4      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

@simphotonics
Copy link
Author

@julemand101 Thank you for taking the time to write such a detailed explanation. It might be useful for others who come across the same issue.

The use of the data-type dynamic was not obvious to me since jsonEncode and the first few functions in the calling sequence use Object. But I guess they had to use dynamic at some point in order to call the method toJson() on an object of unknown type.

I'll close this issue now.

@chonghorizons
Copy link

chonghorizons commented Dec 8, 2021

Issued a pull request (#47875) to update the docs, because the docs are not clear on this issue.

My use case was extending an enum: see https://stackoverflow.com/questions/70272959/jsonencode-doesnt-work-with-enum-extensions-is-there-a-workaround

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

No branches or pull requests

3 participants