How to convert List<dynamic> to List<T> without getting warning from linter in Dart? - dart

I wrote this code to convert dynamic list to Word list but linter says:
Omit type annotations for local variables. on 2nd line.
However if I omit type annotations, I get an error A value of type 'List<dynamic>' can't be returned from method 'convert' because it has a return type of 'List<Word>'.
It there any smarter way to convert?
static List<Word> convert(List<dynamic> words) {
final List<Word> wordsList = [];
words.forEach((v) {
final map = Map<String, dynamic>.from(v as Map<dynamic, dynamic>);
wordsList.add(Word.fromMap(map));
});
return wordsList;
}
Word.fromMap is:
Word.fromMap(Map<String, dynamic> map)
: text = map['text'] as String,
count = map['count'] as int;

To avoid the warning, and put the type on the right-hand side as the lint wants, just write:
final wordsList = <Word>[];
I assume words is JSON data, so the maps are already Map<String, dynamic>. Then you can also do everything in one line:
static List<Word> convert(List<dynamic> words) =>
[for (var v in words) Word.fromMap(v)];

Use the cast() method like this:
class Word {
final String text;
final int count;
static List<Word> convert(List<dynamic> words) {
final List<Word> wordsList = [];
words.cast<Map<dynamic, dynamic>>().forEach((v) { // <-- look here
final map = Map<String, dynamic>.from(v);
wordsList.add(Word.fromMap(map));
});
return wordsList;
}
Word.fromMap(Map<String, dynamic> map)
: text = map['text'] as String,
count = map['count'] as int;
}
It will ensure the casting are done on each element. Make sure the type are correct since it will else result in a type-cast error.

Related

Dart: Reference a variable name from the variable itself

I’m trying to get the variable name instead of the value to pass it to another method
eg.
String myString = "xyz";
String getVariableName(String s){
// What i want is if the above string is passed [getVariableName(myString)]
//i want it to return ['myString']
}
This is generally not possible. At best, if there are a finite number of String variables you care about, you could manually make a collection of them, and then search it:
final stringVariables = <String, String Function()>{
'myString': () => myString,
'myOtherString': () => myOtherString,
};
var myString = 'xyz';
var myOtherString = 'abc';
String getVariableName(String s) {
for (var entry in stringVariables.entries) {
if (entry.value() == s) {
return entry.key;
}
}
return '';
}
void main() {
print(getVariableName('xyz')); // Prints: myString
myString = 'foo';
print(getVariableName('foo')); // Prints: myString
}
Note that stringVariables's above must use a Function as a thunk to delay evaluation of the variable; otherwise the variable name would be associated with whatever value it happened to have when stringVariables is first accessed and wouldn't work if your variables are reassigned.
All that said, I don't really recommend doing any of this. This sounds like an XY problem. You should be asking about whatever it is you ultimately want to do, and there probably is a better way to accomplish that task.

Why a const list can't be used in a string literal when it can't be modified in any way?

void main() {
const list = [1, 2, 3];
const string = 'This is a $list'; // Error
}
When I can't assign list a new value and modify any of its elements, why can't I then use the list in my string literal?
Dart doesn't have a concept of saying that a method call can be evaluated at compilation time (in contrast to constexpr in C++). Therefore Dart cannot guarantee that calling a method on a const object returns another const object, and that includes the implicit call to .toString() when doing string interpolation.
For example, this is perfectly legal:
import 'dart:math';
final random = Random();
class Foo {
const Foo();
// Returns a string that is clearly not a compile-time constant.
#override
String toString() => random.nextInt(100).toString();
}
void main() {
const foo = Foo();
print('$foo');
const list = [foo, foo, foo];
print('$list');
}
Note that this doesn't apply to .toString() implementations for some built-in types (e.g. null, numeric, string, and boolean types) since they are known to produce constant values and because Dart does not allow creating derived classes from those types, so they cannot be overridden to do shenanigans like the above example.
It's an interesting question, because some const things can be interpolated into const strings. I checked out the Language Tour, and it gives this example, which is very close to your question:
// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';
// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];
const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';
The explanation given is that
Literal strings are compile-time constants, as long as any
interpolated expression is a compile-time constant that evaluates to
null or a numeric, string, or boolean value.
They don't explain why (what the technical reasons are, or the motivations for making the language work this way), but it's clear that a const list does not evaluate to "null or a numeric, string, or boolean value." So it just doesn't matter that your list is const—the string resulting from interpolation with that will not be a compile-time constant, so you can't use const with that string.

Using an 'is' expression when the right-hand operand is a variable?

I am trying to write a function that takes two arguments: givenType and targetType. If these two arguments match, I want givenType to be returned, otherwise null.
For this objective, I am trying to utilize Dart's is expression (maybe there is a better way to go about it, I am open to suggestions). Initially, I thought it would be as simple as writing this:
matchesTarget(givenType, targetType) {
if (givenType is targetType) {
return givenType;
}
return null;
}
But this produces an error:
The name 'targetType' isn't a type and can't be used in an 'is'
expression. Try correcting the name to match an existing
type.dart(type_test_with_non_type)
I tried looking up what satisfies an is expression but cannot seem to find it in the documentation. It seems like it needs its right-hand operand to be known at compile-time (hoping this is wrong, but it does not seem like I can use a variable), but if so, how else can I achieve the desired effect?
I cant guess the purpose of the function (or the scenario where it would be used, so if you can clarify it would be great). First of all, I don't know if you are passing "types" as arguments. And yes, you need to specify in compile time the right hand argument of the is function.
Meanwhile, if you are passing types, with one change, you can check if the types passed to your function at runtime.
matchesTarget(Type givenType, Type targetType) {
print('${givenType.runtimeType} ${targetType.runtimeType}');
if (givenType == targetType) {
return givenType;
}
return null;
}
main(){
var a = int; //this is a Type
var b = String; //this is also a Type
print(matchesTarget(a,b)); //You are passing different Types, so it will return null
var c = int; //this is also a Type
print(matchesTarget(a,c)); //You are passing same Types, so it will return int
}
But if you are passing variables, the solution is pretty similar:
matchesTarget(givenVar, targetVar) {
print('${givenVar.runtimeType} ${targetVar.runtimeType}');
if (givenVar.runtimeType == targetVar.runtimeType) {
return givenVar.runtimeType;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = "hello"; //this is also a variable (String)
print(matchesTarget(a,b)); //this will return null
var c = 12; //this is also a variable (int)
print(matchesTarget(a,c)); //this will return int
}
The Final Answer
matchesTarget(givenVar, targetType) {
print('${givenVar.runtimeType} ${targetType}');
if (givenVar.runtimeType == targetType) {
return givenVar;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = String; //this is a type (String)
print(matchesTarget(a,b)); //this will return null because 'a' isnt a String
var c = int; //this is also a type (int)
print(matchesTarget(a,c)); //this will return the value of 'a' (10)
}
The as, is, and is! operators are handy for checking types at runtime.
The is operator in Dart can be only used for type checking and not checking if two values are equal.
The result of obj is T is true if obj implements the interface specified by T. For example, obj is Object is always true.
See the below code for an example of how to use the is operator
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
Even the error message that you're getting says that
The name 'targetType' isn't a type and can't be used in an 'is'
expression.
So the bottomline is that you can use is only for checking if a variable or value belongs to a particular data type.
For checking equality, you can use the == operator if comparing primitive types, or write your own method for comparing the values. Hope this helps!

Convert JSON to map with string keys and List<String> values

I'm attempting to convert JSON that has strings for keys and string arrays for values.
From my understanding, this should work:
import 'dart:convert';
void main() {
var jsonString = '{"key": ["1", "2", "3"]}';
var data = json.decode(jsonString) as Map;
var result = data.cast<String, List<String>>();
print(result);
}
However I get the error that type 'List<dynamic>' is not a subtype of type 'List<String>' in type cast.
What's interesting, however, is that the following does correctly work:
import 'dart:convert';
void main() {
var jsonString = '{"key": "value"}';
var data = json.decode(jsonString) as Map;
var result = data.cast<String, String>();
print(result);
}
So, I assume that the .cast<> method introduced with Dart 2 doesn't know how to convert nested types that aren't simple types like String, int, or bool.
How would I convert this object to a Map<String, List<String>> without resorting to external libraries?
So, I assume that the .cast<> method introduced with Dart 2 doesn't know how to convert nested types that aren't simple types like String, int, or bool.
That's correct. It just does a one-level-deep shallow conversion. You can do the nested conversion yourself like:
void main() {
var jsonString = '{"key": ["1", "2", "3"]}';
var data = json.decode(jsonString) as Map;
var result = data.map((key, value) =>
MapEntry<String, List<String>>(key, List<String>.from(value)));
print(result.runtimeType);
}
This is calling Map.map(). Sometimes Map.fromIterable() or Map.fromIterables() is a better fit. The collection types have a handful of methods like this to convert between different types.

Dart: How to json decode '0' as double

I have a service that is sending me a list of values and after parsing with the 'dart:convert json.decode' I get a List < dynamic > json as such:
(values) [ 0.23, 0.2, 0, 0.43 ]
When I try to parse the values to 'double', I get a parsing error
double value = values[2] // error
Because if we check the .runtimeType of each element we can see that we have the following
[double, double, int, double]
So, how can I avoid '0' being interpreted as int?
I would like to avoid using a function like this:
double jsonToDouble(dynamic value) {
if(value.runtimeType == int) {
return (value as int).toDouble();
}
return value;
}
You can add some special logic to the JSON decoding by constructing your own codec with a reviver. This callback will be invoked for every property in a map or item in a List.
final myJsonCodec = new JsonCodec.withReviver((dynamic key, dynamic value) {
if (value is int) return value.toDouble();
return value;
});
However, you will always get a List<dynamic> from dart:covert, whether or not all of your number types are doubles or ints. Depending on what version of dart you are on you may be able to use List.cast to convert to a List<double>.
List<double> values = oldValues.cast<double>();
If that isn't available, you can create a new List<double> and add your values to it so that the runtime type is correctly set.
Doubles have a toDouble() method. So, you can use that in both cases (double and int).
double jsonToDouble(dynamic value) {
return value.toDouble();
}
This works only if the value is a double or an int, though. If the value is a string, an exception occurs.

Resources