How to cast an Object to Map (without using 'as')? - dart

Variable 'valor' is of type 'Object'.
'valor' stores a 'Map<String, String>'.
I can 'convert' valor to a Map using:
var json = valor as Map<String, String>;
The question is: Can I do the same thing without using 'as' ?

You can cast without an explicit as, by going through dynamic.
dynamic dynamicValue = valor; // Valid up-cast.
Map<String, String> map = dynamicValue; // Implicit down-cast.
The implicit downcast is effectively an as Map<String, String> which is inserted by the compiler.
When compiling for the web, with "unsound optimizations" enabled, the implicit cast is optimized away and the compiler just blindly treats the value as a Map<String, String>, whether it is one or not.
If that's your goal, to avoid the cost of a cast at runtime, you can also just do:
Map<String, String> json = valor as dynamic;
That as is an up-cast, which is always sound and has no runtime overhead.
(Depending on your analyzer configuration, you may get warnings about implicit downcasts.)
Another approach, which doesn't use as, but does cost at runtime, is to use promotion:
Map<String, String> json;
if (valor is Map<String, String>) { // no `as`, but still a check.
json = valor;
} else {
throw UnsupportedError("But it should be a map!");
}
// json is assigned here.
No use of as, but just as many type checks as as Map<String, String> does, so maybe not what you want anyway.

Sorry but you can't, even method such as Iterable.cast() are using the as keyword under the hood.
You can check the Dart type system documentation for confirmation.

You can and should check if valor is a Map<String, String> first and allow it to be automatically type-promoted:
if (valor is Map<String, String>) {
// `valor` is now known to be a `Map<String, String>`.
var json = valor;
...
}
Note that type promotion can occur only for local variables, so if valor is not local, you must create a local reference first.

Related

Dart: Maps nested in maps

I want to store various data for my app in a single place, in a map. In JS, I'd store in a JSON file, and I want to use the same sort of approach, but struggling with Dart. I can't seem to work with nested lists or maps.
Here's essentially what I want to do:
var items = {
"item1": {
"message" : "aa",
"nested1": {
"message": "bb",
"nested2" : {
"message" : "cc"
},
}
},
};
void main() {
var message1 = items["item1"]?["message"];
print(message1);
print(message1.runtimeType);
var message2 = items["item1"]?["nested1"]?["message"];
print(message2);
print(message2.runtimeType);
var message3 = items["item1"]?["nested1"]?["nested2"]?["message"];
print(message3);
print(message3.runtimeType);
}
I've been struggling to make this work in Dartpad.
message1 works as expected, but then I can't seem to work my way down the tree...
Is this a shortcoming with map literals? Do I need to use constructors? Or am I missing something bigger?
Your problem is that items is inferred to be of type Map<String, Map<String, Object>>, but Object does not have an operator []. Therefore when you eventually extract that Object, you will not be able to do anything with it until you cast it a more specific type.
What you probably want instead is to explicitly declare items as Map<String, dynamic> to disable static type-checking on the Map's values:
var items = <String, dynamic>{
"item1": ...
};
Of course, when you disable static type-checking, you are responsible for ensuring that the values you get from the Map are what you expect, or you will get NoSuchMethod or TypeError exceptions at runtime. If you do want static type-checking, you should use define custom classes instead of using a blob of key-value properties.

Can I create a const object from a json string?

I want to create a const object from a fixed JSON string.
This json string is comming from a --dart-define parameter.
I'm getting it using const _APP_CONF = String.fromEnvironment('APP_CONF', defaultValue: '{}');
I've tried the code below, but it is not working. the compiler is complaining about the second constructor:
class AuthnProvider {
final String id;
final String clientId;
final List<String> scopes;
const AuthnProvider(
{this.id,
this.clientId,
this.scopes});
const AuthnProvider.fromJson(final Map<String, dynamic> json)
: id = json['id'],
clientId = json['clientId'],
scopes = json['scopes'].cast<String>();
The json parameter is coming from json.decode() method.
I also tried to create const and final var from from the json map and use the first constructor, but compiler gives error too.
This is expected. const create a compile-time constant. Dart does not execute code during compilation and therefore cannot create a const from a Map. This is the reason const constructors can't have a body and why there is no other way to work around this limitation.
You don't mention the reason for doing this in your question, but if it's for performance, the difference will be negligible. If it's for immutability, all the fields we see are already final, so making the object const makes no difference.

How to convert list to map in dart?

I'm new to dart. I'm trying to covert my Holiday class to Map to be used in my calendar. I tried using Map.fromIterable but it only convert it to <String, dynamic>?
class Occasion {
final List<Holiday> holidays;
Map<DateTime, List> toMap() {
var map = Map.fromIterable(holidays,
key: (e) => DateFormat('y-M-d').format(DateTime.parse(e.date)),
value: (e) => e.name);
print(map);
}
}
class Holiday {
final String date;
final String name;
Holiday({
this.date,
this.name,
});
factory Holiday.fromJson(Map<String, dynamic> parsedJson) {
return Holiday(date: parsedJson['date'], name: parsedJson['name']);
}
}
There are two things:
First: The type parameters of your returned map aren't right for the values you produce in fromIterable. You say it should be List, but in value: ... you are only producing a single String.
Secondly, as I said in my comment you need to help out the dart compiler here a little bit. The compiler isn't very smart. It doesn't see that you are only producing Strings in value. You need to tell him that.
To be fair. This might not be the problem of the compiler, but an overuse of the dynamic type in the collections library.
Map<String, String> toMap() {
var map = Map<String, String>.fromIterable(holidays,
key: (e) => e.date,
value: (e) => e.name );
return map;
}
Just remember: be precise with your types. If you run into type errors start putting additional type information everywhere you can. If you feel it's to cluttered after that, try removing them one spot at a time and see where it leads you.

Syntax error when trying to determine if a variable is of a certain type

Pretty much as the title says: If you have a Type stored in a variable, there's no way to compare your actual object to this type variable, as far as I can tell. I can probably accomplish what I'm trying to do with mirrors, but I'd prefer not to if at all possible.
void example() {
Type myType = String;
String myExample = "Example";
//Syntax error here: The name 'myType' is not a type and cannot be used in an 'is' expression
if (myExample is myType) {
}
}
You can't generally test if a value is of a type using the Type object.
Type objects are reflected types, not real types. They represent the real type, but you can't use them in the code where you need a type: as type assertions, as generic type parameters or with the is/as operators. You must use the name of a type in those places, and not the name of a normal variable that happens to hold a Type object.
Clever stuff using mirrors might get there, but it's likely overkill for most cases (and I understand that you don't want it).
What you might be able to do instead, is to not pass around raw Type objects. You could instead make your own type abstraction, something like:
class MyType<T> {
const MyType();
Type get type => T;
bool isA(Object object) => object is T;
}
Then you can use that to represent types, not a Type object, and do something like:
void main(List<String> args) {
MyType myType = const MyType<String>();
String myExample = "Example";
if(myType.isA(myExample)) {
print('is');
} else {
print('is not');
}
}
That does require that your entire program uses your type objects to pass around types, but it also gives you a lot of control over those objects, so you can implement the functionality that you need.
I tried
library x;
void main(List<String> args) {
Type myType = String;
String myExample = "Example";
if(myExample.runtimeType == myType) {
print('is');
} else {
print('is not');
}
}
and it worked.
I have not much experience with such code in Dart though. Maybe that is not a fail-safe approach.
import 'package:reflection/reflection.dart';
void main() {
var childType = typeInfo(Child);
var baseType = typeInfo(Base);
if(childType.isA(baseType)) {
print("Child is Base");
}
if(baseType.isAssignableFrom(childType)) {
print("Base is assignable from Child");
}
}
class Base {
}
class Child extends Base {
}
Child is Base
Base is assignable for Child
P.S.
The "reflection" package incompatible with dart2js. It work only when used in Dart language.

Dart - named parameters using a Map

I would like to know if I can call a function with name parameters using a map e.g.
void main()
{
Map a = {'m':'done'}; // Map with EXACTLY the same keys as slave named param.
slave(a);
}
void slave({String m:'not done'}) //Here I should have some type control
{
print(m); //should print done
}
the hack here is to not use kwargs but a Map or, if you care about types, some interfaced class (just like Json-obj), but wouldn't be more elegant to just have it accept map as kwars?
More, using this hack, optional kwargs would probably become a pain...
IMHO a possible implementation, if it does not exist yet, would be something like:
slave(kwargs = a)
e.g. Every function that accepts named param could silently accept a (Map) kwargs (or some other name) argument, if defined dart should, under the hood, take care of this logic: if the key in the Map are exactly the non optional ones, plus some of the optional ones, defined in the {} brackets, and of compatible types "go on".
You can use Function.apply to do something similar :
main() {
final a = new Map<Symbol, dynamic>();
a[const Symbol('m')] = 'done';
Function.apply(slave, [], a);
}
You can also extract an helper method to simplify the code :
main() {
final a = symbolizeKeys({'m':'done'});
Function.apply(slave, [], a);
}
Map<Symbol, dynamic> symbolizeKeys(Map<String, dynamic> map){
return map.map((k, v) => MapEntry(Symbol(k), v));
}
The answer of #alexandre-ardhuin is correct but is missing something : How to call a constructor as Function.
You have to use the property new after the Classname. Here's an example :
main() {
final a = new Map<Symbol, dynamic>();
Function.apply(MyClass.new, [], a);
}

Resources