Dart: Maps nested in maps - dart

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.

Related

Converting complex JSON string to Map

When I say complex, means : A lot of nested objects, arrays, etc...
I am actually stuck on this simple thing:
// Get the result from endpoint, store in a complex model object
// and then write to secure storage.
ServerResponse rd = ServerResponse.fromMap(response.data);
appUser = AppUser.fromMap(rd.data); // appData is a complex object
await storage.write(key: keyData, value: userData);
String keyData = "my_data";
const storage = FlutterSecureStorage();
String? userData = appUser?.toJson(); // Convert the data to json. This will produce a JSON with escapes on the nested JSON elements.bear this in mind.
// Now that I stored my data, sucessfully, here comes the challenge: read it back
String dataStored = await storage.read(key: keyData);
// Now What ?
If I decide to go to appUser = AppUser.fromJson(dataStored), will be very complicated because for each level of my Json, too many fromJson, fromMap, toJson, toMap...It's nuts.
Hovever I've a fromMap that's actually works good since Dio always receive the data as Map<String, dynamic>. And my question is: Is there some way to convert a huge and complex JSON stringified to a full Map<String, dynamic> ? json.decode(dataStored) only can convert the properties on root - Nested properties will still continue as JSON string inside a map.
Any clue ??? thanks !
This is the main problem since Dart is lacking data classes. Thus, you need to define fromJson/toJson methods by yourself. However, there are several useful packages that use code generation to cope with this problem:
json_serializable - basically a direct solution to your problem.
freezed - uses json_serializable under the hood, but also implement some extra useful methods, focusing on the immutability of your data classes.
Other than that, unfortunately, you would need to implement fromJson/toJson methods by yourself.
This website might help you.
Simply just paste your json in there and it will automatically generate fromJson/toJson methods for you, then you can custom it by yourself.
I've used it a lot for my company project and it's very helpful.
Here's the link to website : link
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
const userJson = {
'firstName': 'John',
'lastName': 'Smith',
};
class User {
final String firstName;
final String lastName;
const User({required this.firstName, required this.lastName});
Map<String, dynamic> toJson() => {
'firstName': firstName,
'lastName': lastName,
};
factory User.fromJson(dynamic json) {
return User(
firstName: json['firstName'] as String? ?? '',
lastName: json['lastName'] as String? ?? '',
);
}
}
void main() {
test('from and to json methods', () async {
final user = User.fromJson(userJson);
final _userJson = user.toJson();
debugPrint(userJson.toString());
debugPrint(_userJson.toString());
expect(const MapEquality().equals(userJson, _userJson), true);
});
}

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.

FLUTTER How to get variable based on passed string name?

I have stored variables in a class with their code names.
Suppose I want to get XVG from that class, I want to do
String getIconsURL(String symbol) {
var list = new URLsList();
//symbol = 'XVG'
return list.(symbol);
}
class URLsList{
var XVG = 'some url';
var BTC = 'some url';
}
Can someone help me achieve this or provide me with a better solution?
Dart when used in flutter doesn't support reflection.
If it's text that you want to have directly in your code for some reason, I'd advise using a text replace (using your favourite tool or using intellij's find + replace with regex) to change it into a map, i.e.
final Map<String, String> whee = {
'XVG': 'url 1',
'BTC': 'url 2',
};
Another alternative is saving it as a JSON file in your assets, and then loading it and reading it when the app opens, or even downloading it from a server on first run / when needed (in case the URLs need updating more often than you plan on updating the app). Hardcoding a bunch of data like that isn't necessarily always a good idea.
EDIT: how to use.
final Map<String, String> whee = .....
String getIconsURL(String symbol) {
//symbol = 'XVG'
return whee[symbol];
}
If you define it in a class make sure you set it to static as well so it doesn't make another each time the class is instantiated.
Also, if you want to iterate through them you have the option of using entries, keys, or values - see the Map Class documentation
I'd just implement a getProperty(String name) method or the [] operator like:
class URLsList{
var XVG = 'some url';
var BTC = 'some url';
String get operator [](String key) {
switch(key) {
case 'XVG': return XVG;
case 'BTC': return BTC;
}
}
}
String getIconsURL(String symbol) {
var list = new URLsList();
return list[symbol];
}
You can also use reflectable package that enables you to use reflection-like code by code generation.
Assuming that the class is being created from a JSON Object, you can always use objectName.toJSON() and then use the variable names are array indices to do your computations.

Dart Built Value Deserialize List of Objects

I have an API that's returning a list of objects...
[{}, {}, {}, ...]
I already have a defined and working built_value model for each object. However, now I need to deserialize the list.
I currently am trying something like this:
List<Map<String, dynamic>> json = JSON.decode(DEMO_TASK);
json.expand<Task>((Map<String, dynamic> map) => _serializers.deserializeWith<Task>(Task.serializer, map));
However, that causes issues since it says _serializers.deserializeWith return type Task isn't an Iterable<Task> as defined by the closure.
How do I go about deserializing the list. I'm sure I'm missing something super basic.
In case you want to have more general approach you can use this code snippet:
In case anybody needs this functionality I leave here code snippet of how to handle this situation (code should be placed in serializers.dart file):
Serializers standardSerializers = (serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
T deserialize<T>(dynamic value) =>
standardSerializers.deserializeWith<T>(standardSerializers.serializerForType(T), value);
BuiltList<T> deserializeListOf<T>(dynamic value) =>
BuiltList.from(value.map((value) => deserialize<T>(value)).toList(growable: false));
So if you have json file
[
{
"name": "test1",
"result": "success"
},
{
"name": "test2",
"result": "fail"
}
]
And built value class:
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
part 'test_class.g.dart';
abstract class TestClass implements Built<TestClass, TestClassBuilder> {
String get name;
String get result;
TestClass._();
factory TestClass([updates(TestClassBuilder b)]) = _$TestClass;
static Serializer<TestClass> get serializer => _$testClassSerializer;
}
You can use method deserializeListOf from above as:
import 'package:path_to_your_serializers_file/serializers.dart';
final BuiltList<TestClass> listOfTestClasses = deserializeListOf<TestClass>(json);
Yup. I missed something basic. I was thinking I was using a stream, but since it's a list you just have to use the .map function on a list.
List<Map<String, dynamic>> json = JSON.decode(DEMO_TASK);
List<Task> tasks = json.map<Task>((Map<String, dynamic> map) => _serializers.deserializeWith<Task>(Task.serializer, map)).toList();

Is there a way to pass a primitive parameter by reference in Dart?

I would like to pass a primitive (int, bool, ...) by reference. I found a discussion about it (paragraph "Passing value types by reference") here: value types in Dart, but I still wonder if there is a way to do it in Dart (except using an object wrapper) ? Any development ?
The Dart language does not support this and I doubt it ever will, but the future will tell.
Primitives will be passed by value, and as already mentioned here, the only way to 'pass primitives by reference' is by wrapping them like:
class PrimitiveWrapper {
var value;
PrimitiveWrapper(this.value);
}
void alter(PrimitiveWrapper data) {
data.value++;
}
main() {
var data = new PrimitiveWrapper(5);
print(data.value); // 5
alter(data);
print(data.value); // 6
}
If you don't want to do that, then you need to find another way around your problem.
One case where I see people needing to pass by reference is that they have some sort of value they want to pass to functions in a class:
class Foo {
void doFoo() {
var i = 0;
...
doBar(i); // We want to alter i in doBar().
...
i++;
}
void doBar(i) {
i++;
}
}
In this case you could just make i a class member instead.
No, wrappers are the only way.
They are passed by reference. It just doesn't matter because the "primitive" types don't have methods to change their internal value.
Correct me if I'm wrong, but maybe you are misunderstanding what "passing by reference" means? I'm assuming you want to do something like param1 = 10 and want this value to still be 10 when you return from your method. But references aren't pointers. When you assign the parameter a new value (with = operator), this change won't be reflected in the calling method. This is still true with non-primitive types (classes).
Example:
class Test {
int val;
Test(this.val);
}
void main() {
Test t = new Test(1);
fn1(t);
print(t.val); // 2
fn2(t);
print(t.val); // still 2, because "t" has been assigned a new instance in fn2()
}
void fn1(Test t) {
print(t.val); // 1
t.val = 2;
}
void fn2(Test t) {
t = new Test(10);
print(t.val); // 10
}
EDIT
I tried to make my answer more clear, based on the comments, but somehow I can't seem to phrase it right without causing more confusion. Basically, when someone coming from Java says "parameters are passed by reference", they mean what a C/C++ developer would mean by saying "parameters are passed as pointers".
As dart is compiled into JavaScript, I tried something that works for JS, and guess what!? It worked for dart!
Basically, what you can do is put your value inside an object, and then any changes made on that field value inside that function will change the value outside that function as well.
Code (You can run this on dartpad.dev)
main() {
var a = {"b": false};
print("Before passing: " + a["b"].toString());
trial(a);
print("After passing: " + a["b"].toString());
}
trial(param) {
param["b"] = true;
}
Output
Before passing: false
After passing: true
One of the way to pass the variables by reference by using the values in List. As arrays or lists are Pass by reference by default.
void main() {
List<String> name=['ali' ,'fana'];
updatename(name);
print(name);
}
updatename(List<String> name){
name[0]='gufran';
}
Try this one, This one of the simplest way to pass by reference.
You can use ValueNotifier
And, you can pass it as ValueListenable to classes or methods that needs to know up-to-date value, but should not edit it:
class Owner {
final theValue = ValueNotifier(true);
final user = User(theValue);
...
}
class User {
final ValueListeneble<bool> theValue;
User(this.theValue);
...
}
It provides more functionality than actually needed, but solves the problem.
If ValueNotifier + ValueListenable do not work for you (you want to make sure the client does not listen to every change of the value, or your package is pure Dart package and thus cannot reference Flutter libraries), use a function:
class Owner {
int _value = 0;
int getValue() => _value;
void increase() => _value++;
}
void main() {
final owner = Owner();
int Function() obtainer = owner.getValue;
print(obtainer());
owner.increase();
print(obtainer());
}
Output will be:
0
1
This approach has memory usage related downside: the obtainer will hold the reference to the owner, and this, even if owner is already not referenced, but obtainer is still reachable, owner will be also reachable
and thus will not be garbage collected.
If you do not want the downside, pass the smaller container than the entire owner:
import 'package:flutter/foundation.dart';
class ListenableAsObtainer<T> implements ValueObtainer<T> {
ListenableAsObtainer(this._listenable);
final ValueListenable<T> _listenable;
#override
T get value => _listenable.value;
}
class FunctionAsObtainer<T> implements ValueObtainer<T> {
FunctionAsObtainer(this._function);
final T Function() _function;
#override
T get value => _function();
}
class ValueAsObtainer<T> implements ValueObtainer<T> {
ValueAsObtainer(this.value);
#override
T value;
}
/// Use this interface when the client needs
/// access to the current value, but does not need the value to be listenable,
/// i.e. [ValueListenable] would be too strong requirement.
abstract class ValueObtainer<T> {
T get value;
}
The usage of FunctionAsObtainer will still result in holding the owner from garbage collection, but two other options will not.
Just to make it clear:
void main() {
var list1 = [0,1,2];
var modifiedList1 = addMutable(list1, 3);
var list2 = [0,1,2];
var modifiedList2 = addImmutable(list2, 3);
print(list1);
print(modifiedList1);
print(list2);
print(modifiedList2);
}
List<int> addMutable(List<int> list, int element){
return list..add(element);
}
List<int> addImmutable(List<int> list, int element){
return [...list, element];
}
Output:
[0, 1, 2, 3]
[0, 1, 2, 3]
[0, 1, 2]
[0, 1, 2, 3]
All variables are passed by value. If a variable contains a primitive (int, bool, etc.), that's it. You got its value. You can do with it whatever you want, it won't affect the source value. If a variable contains an object, what it really contains is a reference to that object.
The reference itself is also passed by value, but the object it references is not passed at all. It just stayed where it was. This means that you can actually make changes to this very object.
Therefore, if you pass a List and if you .add() something to it, you have internally changed it, like it is passed by reference. But if you use the spread operator [...list], you are creating a fresh new copy of it. In most cases that is what you really want to do.
Sounds complicated. Isn't really. Dart is cool.

Resources