I'm new to Dart and was wondering over how the .cast() method works with dynamic types and lists.
This is a working example from the Flutter documentation on how to parse JSON manually in Dart:
List<Photo> parsePhotos(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}
where responseBody is some JSON array previously fetched from a HTTP endpoint.
I don't understand why the result of json.decode(responseBody) is cast to Map<String, dynamic> when logically it should be List<Map<String, dynamic>>. I've debugged the code and in fact the variable parsed is a list subtype.
What am I getting wrong here?
Thanks in advance.
It looks like it is correct. cast is a method of Iterable. The type in angle brackets is the type of each element in the iterable.
https://api.dart.dev/stable/2.7.1/dart-core/Iterable/cast.html
Related
Given a Map variable, how can I determine the type of Key and Value from it?
For example:
void doSomething(Map m){
print('m: ${m.runtimeType}');
print('keys: ${m.keys.runtimeType}');
print('values: ${m.values.runtimeType}');
print('entries: ${m.entries.runtimeType}');
}
void main() async {
Map<String, int> m = {};
doSomething(m);
}
This will print
m: _InternalLinkedHashMap<String, int>
keys: _CompactIterable<String>
values: _CompactIterable<int>
entries: MappedIterable<String, MapEntry<String, int>>
But how can I get the actual type of Key and Value (i.e. String and int), so that I can use them in type checking code (i.e. if( KeyType == String ))?
You cannot extract the type parameters of a class if it doesn't provide them to you, and Map does not.
An example of a class which does provide them is something like:
class Example<T> {
Type get type => T;
R withType<R>(R Function<X>() callback) => callback<T>();
}
If you have an instance of Example, you can get to the type parameter, either as a Type (which is generally useless), or as a type argument which allows you to do anything with the type.
Alas, providing access to types variables that way is very rare in most classes.
You can possibly use reflection if you have access to dart:mirrors, but most code does not (it doesn't work with ahead-of-time compilation, which includes all web code, or in Flutter programs).
You can try to guess the type by trying types that you know (like map is Map<dynamic, num>, then map is Map<dynamic, int> and map is Map<dynamic, Never>. If the first two are true, and the last one is false, then the value type is definitely int. That only works if you know all the possible types.
It does work particularly well for platform types like int and String because you know for certain that their only subtype is Never.
If you can depend on the static type instead of the runtime type, you could use a generic function:
Type mapKeyType<K, V>(Map<K, V> map) => K;
Otherwise you would need to have a non-empty Map and inspect the runtime types of the actual elements.
I have nested comments. In order to fetch them from JSON, I have the following fromJson() function:
Comment.fromJson(Map<String, dynamic> json)
: id = json['id'],
...
children = json['children'] != null
? json['children'].map((c) => Comment.fromJson(c)).toList()
: null,
...
This checks if the current comment has any children comments, and if so, recursively parses them from JSON into a Comment. However when I run this, I get the following error:
[ERROR:flutter/shell/common/shell.cc(214)]
Dart Unhandled Exception:
type 'List<dynamic>' is not a subtype of type 'List<Comment>'
stack trace: #0 new Comment.fromJson (package:app/models/comment.dart:43:18)
Which points to the line where I assign children with children = json.... I'm new to dart, but I don't understand how mapping over a list, and returning a Comment doesn't let Dart infer the type of the list. How do I fix this? Adding as List<Comment> after toList() didn't work. If I add as Comment after Comment.fromJson(c), I get unnecessary cast.
Dart can't infer anything from json['children'].map(...).toList() because json['children'] is of type dynamic, and therefore it doesn't know what the .map call is going to resolve to at runtime.
At runtime, since .map is called without an explicit type, it ends up being a call to .map<dynamic>(...), and then .toList() returns List<dynamic>.
Try either:
Explicitly using .map<Comment>(...).
Casting json['children']: (json['children'] as List<Map<String, dynamic>>).map(...). It'd probably be a good idea to validate this anyway.
As you can see in https://api.dart.dev/stable/2.7.1/dart-convert/jsonDecode.html, it has no type and no documentation. I don't know which methods I can invoke on the result neither I don't know which type to but on a parameter that should be a json object.
Why is Dart like this? And what are the advantages?
It does have documentation, and you are linking to it.
If you want it to have more documentation, then that is reasonable. The returned value is admittedly not documented very well.
The function jsonDecode is a shorthand for json.decode, which again forwards to JsonDecoder.convert.
It returns a "JSON value" object which depends on the JSON source that it decodes.
A "JSON value" can be any of:
* null
* an int
* a double
* a String
* a bool (true or false)
* a List<dynamic> containing zero or more JSON values.
* a Map<String, dynamic> mapping keys to JSON values.
Those are also the same values that are accepted by the JsonEncoder which converts object structures to JSON strings.
Since these types have no common superclass other than Object, the function cannot have a return type which is more specific than dynamic or Object.
The chosen return type is dynamic because the dynamic type allows the receiver to optimistically call any member on the value. They might know that the value will always be a map, so they can just do jsonParse(jsonSource)["key"] to look up a value. Obviously, if the source was not a JSON object, that call will fail.
If you don't know which type the result is, you have to check:
var data = jsonDecode(jsonSource);
if (data is Map<String, dynamic>) {
something something data["key"] something
} else if (data is List<dynamic>) {
something something list[2] something
} else ... etc ...
A valid JSON file is actually a valid Dart expression too. The value returned by jsonDecode is similar to the value you would get if you wrote the JSON code directly as Dart code (in Dart 1 it was exactly the same, in Dart 2, the Dart code might infer a more precise type for maps and lists).
Can I pass array or dictionary object from dart to native extension by Dart_Handle? If it is possible, how to do that?
I don't know about the dictionary object but you can do it with array/list. You can see here that the Dart_CObject struct can be of the type Dart_CObject_kArray which you can get with call as_array on the Dart_Object:
struct {
intptr_t length;
struct _Dart_CObject** values;
} as_array;
https://github.com/dart-lang/sdk/blob/master/runtime/include/dart_native_api.h#L68
You can then access the values inside the list as normal array (e.g. get first element where "message" are of the type Dart_CObject*):
Dart_CObject* param0 = message->value.as_array.values[0];
I have made a project where I uses lists as argument to native code. The relevant part for you can be found here:
https://github.com/julemand101/lirc_client/blob/master/lib/src/lirc_extension.cc#L126
I wonder, if a dart or flutter method exists, which returns complete type information about the structure of a variable's value as a string.
E.g., if some print( someValue.toString() ); emits this
{"user":"userName","state":"valid"}
I don't know if it is a Map or a String.
But how do I get a string, which describes a variable's value / structure?
Something, that's like PHP's print_r(), which print stuff like this:
Array
(
[a] => Apfel
[b] => Banane
[c] => Array
(
[0] => x
[1] => y
[2] => z
)
)
There is nothing like print_r() available natively in Dart.
However, it would be possible to use the following functionalities to build e.g. a function like print_r() from PHP.
You can evaluate the type of someValue using runtimeType.
String someValueType = someValue.runtimeType.toString();
String someValueString = someValue.toString();
If you want to compare, you can also use is. More on that here.
Dart is a strongly typed language, which means that you could also enforce types (List a; String b), which makes a function like print_r() redundant for Dart.
If it is about server-side code you can use reflection to create a function that produces such an output for every value.
If it is about your own classes, you can implement toString() to make the objects render themselves this way when they are printed
class Person {
Foo(this.firstName, this.lastName);
String firstName;
String lastName;
#override
String toString() => '''
firstName: $firstName
lastName: $lastName
''';
}
print(new Person('John', 'Doe'));
Best way to do is
print(”$someValue“);
It will return a structured string which similar to JSON.