Converting complex JSON string to Map - dart

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);
});
}

Related

Another instances of object in Dart [duplicate]

Is there a Language supported way to make a full (deep) copy of an Object in Dart?
If multiple options exist, what are their differences?
Darts built-in collections use a named constructor called "from" to accomplish this. See this post: Clone a List, Map or Set in Dart
Map mapA = {
'foo': 'bar'
};
Map mapB = new Map.from(mapA);
No as far as open issues seems to suggest:
https://github.com/dart-lang/sdk/issues/3367
And specifically:
... Objects have identity, and you can only pass around references to them. There is no implicit copying.
Late to the party, but I recently faced this problem and had to do something along the lines of :-
class RandomObject {
RandomObject(this.x, this.y);
RandomObject.clone(RandomObject randomObject): this(randomObject.x, randomObject.y);
int x;
int y;
}
Then, you can just call copy with the original, like so:
final RandomObject original = RandomObject(1, 2);
final RandomObject copy = RandomObject.clone(original);
I guess for not-too-complex objects, you could use the convert library:
import 'dart:convert';
and then use the JSON encode/decode functionality
Map clonedObject = JSON.decode(JSON.encode(object));
If you're using a custom class as a value in the object to clone, the class either needs to implement a toJson() method or you have to provide a toEncodable function for the JSON.encode method and a reviver method for the decode call.
Unfortunately no language support. What I did is to create an abstract class called Copyable which I can implement in the classes I want to be able to copy:
abstract class Copyable<T> {
T copy();
T copyWith();
}
I can then use this as follows, e.g. for a Location object:
class Location implements Copyable<Location> {
Location({
required this.longitude,
required this.latitude,
required this.timestamp,
});
final double longitude;
final double latitude;
final DateTime timestamp;
#override
Location copy() => Location(
longitude: longitude,
latitude: latitude,
timestamp: timestamp,
);
#override
Location copyWith({
double? longitude,
double? latitude,
DateTime? timestamp,
}) =>
Location(
longitude: longitude ?? this.longitude,
latitude: latitude ?? this.latitude,
timestamp: timestamp ?? this.timestamp,
);
}
To copy an object without reference, the solution I found was similar to the one posted here, however if the object contains MAP or LIST you have to do it this way:
class Item {
int id;
String nome;
String email;
bool logado;
Map mapa;
List lista;
Item({this.id, this.nome, this.email, this.logado, this.mapa, this.lista});
Item copyWith({ int id, String nome, String email, bool logado, Map mapa, List lista }) {
return Item(
id: id ?? this.id,
nome: nome ?? this.nome,
email: email ?? this.email,
logado: logado ?? this.logado,
mapa: mapa ?? Map.from(this.mapa ?? {}),
lista: lista ?? List.from(this.lista ?? []),
);
}
}
Item item1 = Item(
id: 1,
nome: 'João Silva',
email: 'joaosilva#gmail.com',
logado: true,
mapa: {
'chave1': 'valor1',
'chave2': 'valor2',
},
lista: ['1', '2'],
);
// -----------------
// copy and change data
Item item2 = item1.copyWith(
id: 2,
nome: 'Pedro de Nobrega',
lista: ['4', '5', '6', '7', '8']
);
// -----------------
// copy and not change data
Item item3 = item1.copyWith();
// -----------------
// copy and change a specific key of Map or List
Item item4 = item1.copyWith();
item4.mapa['chave2'] = 'valor2New';
See an example on dartpad
https://dartpad.dev/f114ef18700a41a3aa04a4837c13c70e
With reference to #Phill Wiggins's answer, here is an example with .from constructor and named parameters:
class SomeObject{
String parameter1;
String parameter2;
// Normal Constructor
SomeObject({
this.parameter1,
this.parameter2,
});
// .from Constructor for copying
factory SomeObject.from(SomeObject objectA){
return SomeObject(
parameter1: objectA.parameter1,
parameter2: objectA.parameter2,
);
}
}
Then, do this where you want to copy:
SomeObject a = SomeObject(parameter1: "param1", parameter2: "param2");
SomeObject copyOfA = SomeObject.from(a);
Let's say you a have class
Class DailyInfo
{
String xxx;
}
Make a new clone of the class object dailyInfo by
DailyInfo newDailyInfo = new DailyInfo.fromJson(dailyInfo.toJson());
For this to work your class must have implemented
factory DailyInfo.fromJson(Map<String, dynamic> json) => _$DailyInfoFromJson(json);
Map<String, dynamic> toJson() => _$DailyInfoToJson(this);
which can be done by making class serializable using
#JsonSerializable(fieldRename: FieldRename.snake, includeIfNull: false)
Class DailyInfo{
String xxx;
}
It only works for object types that can be represented by JSON.
ClassName newObj = ClassName.fromMap(obj.toMap());
or
ClassName newObj = ClassName.fromJson(obj.toJson());
Trying using a Copyable interface provided by Dart.
there is an easier way for this issue
just use ... operator
for example, clone a Map
Map p = {'name' : 'parsa','age' : 27};
Map n = {...p};
also, you can do this for class properties.
in my case, I was needed to clone a listed property of a class.
So:
class P1 {
List<String> names = [some data];
}
/// codes
P1 p = P1();
List<String> clonedList = [...p.names]
// now clonedList is an unreferenced type
There is no built-in way of deep cloning an object - you have to provide the method for it yourself.
I often have a need to encode/decode my classes from JSON, so I usually provide MyClass fromMap(Map) and Map<String, dynamic> toJson() methods. These can be used to create a deep clone by first encoding the object to JSON and then decoding it back.
However, for performance reasons, I usually implement a separate clone method instead. It's a few minutes work, but I find that it is often time well spent.
In the example below, cloneSlow uses the JSON-technique, and cloneFast uses the explicitly implemented clone method. The printouts prove that the clone is really a deep clone, and not just a copy of the reference to a.
import 'dart:convert';
class A{
String a;
A(this.a);
factory A.fromMap(Map map){
return A(
map['a']
);
}
Map<String, dynamic> toJson(){
return {
'a': a
};
}
A cloneSlow(){
return A.fromMap(jsonDecode(jsonEncode(this)));
}
A cloneFast(){
return A(
a
);
}
#override
String toString() => 'A(a: $a)';
}
void main() {
A a = A('a');
A b = a.cloneFast();
b.a = 'b';
print('a: $a b: $b');
}
There's no API for cloning/deep-copying built into Dart.
We have to write clone() methods ourselves & (for better or worse) the Dart authors want it that way.
Deep copy Object /w List
If the Object we're cloning has a List of Objects as a field, we need to List.generate that field and those Objects need their own clone method.
Example of cloning method (copyWith()) on an Order class with a List field of objects (and those nested objects also have a copyWith()):
Order copyWith({
int? id,
Customer? customer,
List<OrderItem>? items,
}) {
return Order(
id: id ?? this.id,
customer: customer ?? this.customer,
//items: items ?? this.items, // this will NOT work, it references
items: items ?? List.generate(this.items.length, (i) => this.items[i].copyWith()),
);
}
Gunter mentions this here.
Note, we cannot use List.from(items) nor [...items]. These both only make shallow copies.
Dart does not share Memory within multiple threads (isolate), so...
extension Clone<T> on T {
/// in Flutter
Future<T> clone() => compute<T, T>((e) => e, this);
/// in Dart
Future<T> clone() async {
final receive = ReceivePort();
receive.sendPort.send(this);
return receive.first.then((e) => e as T).whenComplete(receive.close);
}
}
An example of Deep copy in dart.
void main() {
Person person1 = Person(
id: 1001,
firstName: 'John',
lastName: 'Doe',
email: 'john.doe#email.com',
alive: true);
Person person2 = Person(
id: person1.id,
firstName: person1.firstName,
lastName: person1.lastName,
email: person1.email,
alive: person1.alive);
print('Object: person1');
print('id : ${person1.id}');
print('fName : ${person1.firstName}');
print('lName : ${person1.lastName}');
print('email : ${person1.email}');
print('alive : ${person1.alive}');
print('=hashCode=: ${person1.hashCode}');
print('Object: person2');
print('id : ${person2.id}');
print('fName : ${person2.firstName}');
print('lName : ${person2.lastName}');
print('email : ${person2.email}');
print('alive : ${person2.alive}');
print('=hashCode=: ${person2.hashCode}');
}
class Person {
int id;
String firstName;
String lastName;
String email;
bool alive;
Person({this.id, this.firstName, this.lastName, this.email, this.alive});
}
And the output below.
id : 1001
fName : John
lName : Doe
email : john.doe#email.com
alive : true
=hashCode=: 515186678
Object: person2
id : 1001
fName : John
lName : Doe
email : john.doe#email.com
alive : true
=hashCode=: 686393765
// Hope this work
void main() {
List newList = [{"top": 179.399, "left": 384.5, "bottom": 362.6, "right": 1534.5}, {"top": 384.4, "left": 656.5, "bottom": 574.6, "right": 1264.5}];
List tempList = cloneMyList(newList);
tempList[0]["top"] = 100;
newList[1]["left"] = 300;
print(newList);
print(tempList);
}
List cloneMyList(List originalList) {
List clonedList = new List();
for(Map data in originalList) {
clonedList.add(Map.from(data));
}
return clonedList;
}
This works for me.
Use the fromJson and toJson from your Object's Class on JSON serializing
var copy = ObjectClass.fromJson(OrigObject.toJson());
make a helper class:
class DeepCopy {
static clone(obj) {
var tempObj = {};
for (var key in obj.keys) {
tempObj[key] = obj[key];
}
return tempObj;
}
}
and copy what you want:
List cloneList = [];
if (existList.length > 0) {
for (var element in existList) {
cloneList.add(DeepCopy.clone(element));
}
}
Let's say, you want to deep copy an object Person which has an attribute that is a list of other objects Skills. By convention, we use the copyWith method with optional parameters for deep copy, but you can name it anything you want.
You can do something like this
class Skills {
final String name;
Skills({required this.name});
Skills copyWith({
String? name,
}) {
return Skills(
name: name ?? this.name,
);
}
}
class Person {
final List<Skills> skills;
const Person({required this.skills});
Person copyWith({
List<Skills>? skills,
}) =>
Person(skills: skills ?? this.skills.map((e) => e.copyWith()).toList());
}
Keep in mind that using only this.skills will only copy the reference of the list. So original object and the copied object will point to the same list of skills.
Person copyWith({
List<Skills>? skills,
}) =>
Person(skills: skills ?? this.skills);
If your list is primitive type you can do it like this. Primitive types are automatically copied so you can use this shorter syntax.
class Person {
final List<int> names;
const Person({required this.names});
Person copyWith({
List<int>? names,
}) =>
Person(names: names ?? []...addAll(names));
}
The accepted answer doesn't provide an answer, and the highest-rated answer 'doesn't work' for more complex Map types.
It also doesn't make a deep copy, it makes a shallow copy which seems to be how most people land on this page. My solution also makes a shallow copy.
JSON-cloning, which a few people suggest, just seems like gross overhead for a shallow-clone.
I had this basically
List <Map<String, dynamic>> source = [{'sampledata', []}];
List <Map<String, dynamic>> destination = [];
This worked, but of course, it's not a clone, it's just a reference, but it proved in my real code that the data types of source and destination were compatible (identical in my case, and this case).
destination[0] = source[0];
This did not work
destination[0] = Map.from(source[0]);
This is the easy solution
destionation[0] = Map<String, dynamic>.from(source[0]);

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.

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.

Can i store a Map<String, Object> inside a Shared Preferences in dart?

Is there a way that we could save a map object into shared preferences so that we can fetch the data from shared preferences rather than listening to the database all the time.
actually i want to reduce the amount of data downloaded from firebase. so i am thinking of a solution to have a listener for shared prefs and read the data from shared prefs.
But i dont see a way of achieving this in flutter or dart.
Please can someone help me to achieve this if there is a workaround.
Many Thanks,
Mahi
If you convert it to a string, you can store it
import 'dart:convert';
...
var s = json.encode(myMap);
// or var s = jsonEncode(myMap);
json.decode(...)/jsonDecode(...) makes a map from a string when you load it.
Might be easier with this package:
https://pub.dartlang.org/packages/pref_dessert
Look at the example:
import 'package:pref_dessert/pref_dessert.dart';
/// Person class that you want to serialize:
class Person {
String name;
int age;
Person(this.name, this.age);
}
/// PersonDesSer which extends DesSer<T> and implements two methods which serialize this objects using CSV format:
class PersonDesSer extends DesSer<Person>{
#override
Person deserialize(String s) {
var split = s.split(",");
return new Person(split[0], int.parse(split[1]));
}
#override
String serialize(Person t) {
return "${t.name},${t.age}";
}
}
void main() {
var repo = new FuturePreferencesRepository<Person>(new PersonDesSer());
repo.save(new Person("Foo", 42));
repo.save(new Person("Bar", 1));
var list = repo.findAll();
}
Package is still under development so it might change, but any improvements and ideas are welcomed! :)
In dart's Shared Preferences there is no way to store Map directly but you can easily trick this by, converting Map to String then save it as usual, and when you need it to retrieve the String and then convert it back to Map. So simple!
Convert your map into String using json.encode() and then save it,
When you need your map use json.decode() to get back your map from the string.
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
final yourStr = sharedPreferences.getString("yourkey");
var map = json.decode(yourStr);
sharedPreferences.setString("yourkey", json.encode("value"));
For those who don't like to convert String to JSON or vice versa, personally recommend localstorage, this is the easiest way I had ever found to store any data<T> in Flutter.
import 'package:localstorage/localstorage.dart';
final LocalStorage store = new LocalStorage('myapp');
...
setLocalStorage() async {
await store.ready; // Make sure store is ready
store.setItem('myMap', myMapData);
}
...
Hope this helps!

Resources