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

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.

Related

Variable name in dot notation in Swift

I want to use variable names in dot notation in Swift.
I'm wrestling with trying to make sense of JSON in Swift, and having a really hard time doing things that are so easy in JS (like cycling through objects, etc).
I have variables of custom types:
var questionConfiguration: QuestionConfiguration
var priorityConfiguration: PriorityConfiguration
Each of these types has an attribute, displayOrder, I want to access this dynamically. I have tried various things, including the following (i is a variable of type Int):
var configs = [self.questionConfiguration, self.priorityConfiguration]
print([configs[i]].displayOrder)
var configs = ["questionConfiguration", "priorityConfiguration"]
print([configs[i]].displayOrder)
How can I achieve this?
EDIT:
struct QuestionConfiguration: Codable {
var displayOrder: Int
}
struct PriorityConfiguration: Codable {
var displayOrder: Int
}
You can create a protocol for same property name then you can access it easily
protocol SameProp {
var displayOrder: Int {get set}
}
class QuestionConfiguration : SameProp {
var displayOrder : Int = 4
}
class PriorityConfiguration : SameProp {
var displayOrder : Int = 6
}
var questionConfiguration: QuestionConfiguration = QuestionConfiguration()
var priorityConfiguration: PriorityConfiguration = PriorityConfiguration()
var configs = [questionConfiguration, priorityConfiguration] as [SameProp]
for elements in configs{
print(elements.displayOrder) // will print 4 and 6
}
Swift is very particular about data types and I suspect you are experiencing challenges because of it.
When you do this:
var configs = [self.questionConfiguration, self.priorityConfiguration]
In Swift, all the elements in an array have to have the same type. You've not given Swift any hints about what types this array should contain so the compiler has to infer a type. QuestionConfiguration and PriorityConfiguration are two different types. So the compiler is likely to infer that configs is of type [Any] also called Array<Any>.
Then you try something like this:
configs[i].displayOrder (not sure why your code has the extra brackets)
But configs[i] is inferred to be of type Any and that type doesn't have a displayOrder property so the compiler complains.
You need to tell the compiler that these types share some properties. One way to do that would be to add a protocol that describes what they share:
protocol DisplayOrderable {
var displayOrder: Int { get }
}
struct QuestionConfiguration: DisplayOrderable, Codable {
var displayOrder: Int
}
struct PriorityConfiguration: DisplayOrderable, Codable {
var displayOrder: Int
}
then you could use
var configs: [any DisplayOrderable] = [self.questionConfiguration, self.priorityConfiguration]
And configs[i].displayOrder should work.

How to convert a list inside of a list from json in dart

Imagine you have the following json
{
"list": [
["xa", "yc", "ze"],
["xb", "yd", "zf"]
]
}
how do we convert this to a List<List<String>> with json.decode() in dart?
I'm going to make the assumption that this is a string before you pass it to jsonDecode, which will return a Map<String, dynamic>.
// The original string
String jsonString = "{\"list\": [[\"xa\", \"yc\", \"ze\"], [\"xb\", \"yd\", \"zf\"]]}";
// The parsed map
Map<String, dynamic> json = jsonDecode(jsonString);
Now, it seems like you don't care about the containing Map.
Technically, those things that look like Lists are actually Maps at this point, so those type assertions would fail.
List<List<String>> output = json["list"].map((value) => value.toList()).toList();
That's the most straightforward method I can think of right now.

Access function arguments dynamically in Swift

What we want is to convert function parameters to Dictionary and save in other Dictionary.
Dictionary would look like this:
typealias FunctionName = String
typealias ArgumentName = String
typealias ArgumentValue = Any
var parameters: [FunctionName: [ArgumentName: ArgumentValue]] = [:]
Every function has different sign. It may look like this.
func functionA(arg1:String, arg2:Int) {
//Convert arg1 & arg2 in dictionary
//Save that dictionary in parameters Dictionary
//.....
}
func functionB(arg1:String, arg2:Int, arg3:String, arg4:[Double]) {
//Convert arg1, arg2, arg3, arg4 in dictionary
//Save that dictionary in parameters Dictionary
//.....
}
One solution is to create dictionary statically like this:
let args:[ArgumentName: ArgumentValue] = ["arg1": arg1, "arg2": arg2]
let args:[ArgumentName: ArgumentValue] = ["arg1": arg1, "arg2": arg2, "arg3": arg3, "arg4": arg4]
But we have to write this in every function and we also have to maintain it when function sign change. Thats why we want to make it dynamic.
We can get Function Name as string by using #function. But how to get function arguments name and value as dictionary OR array of key-pair OR array of tuple.

How to convert List<dynamic> to List<T> without getting warning from linter in 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.

Transform data when parsing a JSON string using Dart

I'm using the parse() function provided in dart:json. Is there a way to transform the parsed data using parse()? I'm thinking of something similar to the reviver argument when parsing JSON using JavaScript:
JSON.parse(text[, reviver])
The parse() function in dart:json takes a callback as an arg that you can use to transform the parsed data. For example, you may prefer to express a date field as a DateTime object, and not as a list of numbers representing the year, month and day. Specify a ‘reviver’ function as a second argument to parse.
This function is called once for each object or list property parsed, and the return value of the reviver function is used instead of the parsed value:
import 'dart:json' as json;
void main() {
var jsonPerson = '{"name" : "joe", "date" : [2013, 10, 3]}';
var person = json.parse(jsonPerson, (key, value) {
if (key == "date") {
return new DateTime(value[0], value[1], value[2]);
}
return value;
});
person['name']; // 'joe'
person['date'] is DateTime; // true
}

Resources