2

I'm learning Dart and was reading the article Using Dart with JSON Web Services, which told me that I could get help with type checking when converting my objects to and from JSON. I used their code snippet but ended up with compiler warnings. I found another Stack Overflow question which discussed the same problem, and the answer was to use the @proxy annotation and implement noSuchMethod. Here's my attempt:

abstract class Language {
  String language;
  List targets;
  Map website;
}

@proxy
class LanguageImpl extends JsonObject implements Language {
  LanguageImpl();

  factory LanguageImpl.fromJsonString(string) {
    return new JsonObject.fromJsonString(string, new LanguageImpl());
  }
  noSuchMethod(i) => super.noSuchMethod(i);
}

I don't know if the noSuchMethod implementation is correct, and @proxy seems redundant now. Regardless, the code doesn't do what I want. If I run

var lang1 = new LanguageImpl.fromJsonString('{"language":"Dart"}');
print(JSON.encode(lang1));
print(lang1.language);
print(lang1.language + "!");
var lang2 = new LanguageImpl.fromJsonString('{"language":13.37000}');
print(JSON.encode(lang2));
print(lang2.language);
print(lang2.language + "!");

I get the output

{"language":"Dart"}
Dart
Dart!
{"language":13.37}
13.37
type 'String' is not a subtype of type 'num' of 'other'.

and then a stacktrace. Hence, although the readability is a little bit better (one of the goals of the article), the strong typing promised by the article doesn't work and the code might or might not crash, depending on the input.

What am I doing wrong?

Community
  • 1
  • 1
Anders Sjöqvist
  • 3,372
  • 4
  • 21
  • 22

2 Answers2

0

The article mentions static types in one paragraph but JsonObject has nothing to do with static types.
What you get from JsonObject is that you don't need Map access syntax.
Instead of someMap['language'] = value; you can write someObj.language = value; and you get the fields in the autocomplete list, but Dart is not able to do any type checking neither when you assign a value to a field of the object (someObj.language = value;) nor when you use fromJsonString() (as mentioned because of noSuchMethod/@proxy).

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Yes, I am indeed assigning a `double` to a `String` and I am fully aware of the distinction between quotes and no quotes. I do this intentionally, since the article talked about "type checking" and "strong typing". What I had expected was to get `"13.37000"`, `""`, `null` or even `"0"`... Anything rather than a `double` disguising itself as a `String`, really. Are you saying that the article is wrong and that there's no way to achieve strong typing in this case, or at least that I can't do it in AngularDart and/or Dart Editor? – Anders Sjöqvist Mar 21 '14 at 16:45
  • I haven't read the article but strong typing is about type checking. When you assign a double to a String you get an exception. Duck typing is when you assign a double to a String and you get 'something'. It seems you want Duck typing. – Günter Zöchbauer Mar 21 '14 at 16:47
  • Why do you assume that? I'd be just as happy to get an error message, an exception, a `null` variable within an object or a `null` object. Liskov and Zilles described strong typing as "whenever an object is passed from a calling function to a called function, its type must be compatible with the type declared in the called function." My goal is to find out immediately whether the conversion was successful or not, and then not having to check every single variable to see if it matches the type is should have. I believe this is what most people find helpful with strong typing. – Anders Sjöqvist Mar 21 '14 at 17:00
  • Using `@proxy` and implementing `noSuchMethod` tells Dart 'don't bother me with type checking, I know what I am doing). I'm currently checking the article you referred to. I respond when I know more what this is about (I haven't used JsonObject yet). – Günter Zöchbauer Mar 21 '14 at 17:04
  • You wrote "When you assign a double to a String you get an exception." If you check my output, this is not what happened. The exception didn't occur until I concatenated what should have been two `String`s, but which turned out to be a `double` and a `String`. The assignment itself went completely fine, and I could even print the value. It's also possible to treat it as a number and use it in a calculation. – Anders Sjöqvist Mar 21 '14 at 17:04
  • You are right about the exception. The article mentions static types in one paragraph but JsonObject has nothing to do with static types. What you get from JsonObject is that you don't need Map access syntax. Instead of `someMap['language'] = value;` you can write `someObj.language = value;` and you get the fields in the autocomplete list, but Dart is not able to do any type checking neither when you assign a value to a field of the object (`someObj.language = value;`) nor when you use `fromJsonString()` (as mentioned because of noSuchMethod/@proxy). – Günter Zöchbauer Mar 21 '14 at 17:12
  • I'm sorry, but I don't follow. The article says "JsonObject uses the dart:convert decode() function to extract the JSON data into a map, and then it uses the noSuchMethod feature of Dart classes to provide a way to access values in the parsed map by using dot notation." It also shows examples of this. Are you saying that this statement is incorrect, unless you also create custom classes? – Anders Sjöqvist Mar 21 '14 at 17:17
  • No, this is what I explained in my previous comment (put this also in my answer) you get **just** dot notation and autocompletion but nothing else and especially no type checking (the use of `@proxy and `noSuchMethod` disables type checking, and this is necessary to make this approach work, because internally JsonObject probably just works with a Map). Sorry for the confusion caused by my first wrong response. – Günter Zöchbauer Mar 21 '14 at 17:18
  • Ok, I see your point, thanks. So the conclusion is that the article is wrong? It provides autocompletion but nothing else. And it's not because I implemented `noSuchMethod` incorrectly? (I was mostly guessing.) Also, it's not that it's working in another version of Dart than I'm using? And most importantly: Should I look for a solution elsewhere or is the problem itself unsolvable? I have a lot of JSON objects and there will potentially be huge amount of manual type-checking. – Anders Sjöqvist Mar 21 '14 at 17:38
  • Yes, this one paragraph invites false assumptions. I have no experience with serialization/deserialization in Dart. It is possible to use reflection when you have no private fields. I guess there are nice solutions out there for this approach (maybe even included in 'dart:convert') or you just implement your custom `fromJson` constructor/`toJson` method in your classes and write the code to convert from/to object/map manually. I guess you find good information seraching for `[dart] json` here on StackOverflow. The first one I found looks not so bad: http://stackoverflow.com/questions/20024298 – Günter Zöchbauer Mar 21 '14 at 17:51
0

I assume that you want an exception to be thrown on this line:

var lang2 = new LanguageImpl.fromJsonString('{"language":13.37000}');

because 13.37 is not a String. In order for JsonObject to do this it would need to use mirrors to determine the type of the field and manually do a type check. This is possible, but it would add to the dart2js output size.

So barring that, I think that throwing a type error when reading the field is reasonable, and you might have just found a bug-worthy issue here. Since noSuchMethod is being used to implement an abstract method, the runtime can actually do a type check on the arguments and return values. It appears from your example that it's not. Care to file a bug?

If this was addressed, then JsonObject could immediate read a field after setting it to cause a type check when decoding without mirrors, and it could do that check in an assert() so that it's only done in checked mode. I think that would be a nice solution.

Justin Fagnani
  • 10,483
  • 2
  • 27
  • 37
  • Thank you for your answer! You're absolutely right in your assumption. I'm still a Dart n00b (I started learning maybe one day before posting this question), and thus I don't feel confident that this behavior isn't a consequence of the ["optional and unsound" types](https://www.dartlang.org/articles/why-dart-types/) in Dart. Alternatively, it could be a result of current [unstable state of Dart reflection](https://www.dartlang.org/articles/reflection-with-mirrors/). Could you verify that these are not obstacles to implementing the type check you mentioned? If so, I'd be happy to file a bug. – Anders Sjöqvist Mar 24 '14 at 08:30
  • No, it doesn't seem to me like it's a result of the design of the type system. I think it's quite reasonable to ask if an abstract method can help define the type checks performed on a call. It might not work exactly because nSM can respond to variations of an abstract method that would be illegal to declare, but it's certainly worth bringing up. – Justin Fagnani Mar 25 '14 at 15:50