In a Flutter app I'm writing, I'm a beginner, I want to return 3 Objects from an http request. The http request works fine and returns data like:
{"one":{"fname":"Wyn","lname":"Owen","updated":1648076276673,"uid":"contact's uid","email":"wyn#wyn.com"},
"two":{"updated":1648076276673,"uid":"contact's uid","fname":"Roli","email":"roli#roli.com","lname":"Spider"},
"three":{"lname":"Poodle","email":"bill#bill.com","updated":1648076276673,"fname":"Bill","uid":"contact's uid"},
"user":{"name":null,"premium":false,"reg_id":"123456","created":1648076276673,"ads":true},
"channels":{"whatsapp":false,"email":true,"sms":false,"video":false,"app_msg":true},"shorts":{"expire":null,"code":"short_code"}}
It returns 5 KV pairs. The keys will always remain the same. All I'm interested are the 3 KVs: one, two and three. I want to create an object for each KV pair and call them one, two and three. I created the following model: Object Contacts contains 3 Person Objects:
class Contacts {
Contacts({
required this.one,
required this.two,
required this.three,
});
Person one;
Person two;
Person three;
factory Contacts.fromJson(Map<String, dynamic> json) => Contacts(
one: json["one"],
two: json["two"],
three: json["three"],
);
Map<String, dynamic> toJson() => {
"one": one.toJson(),
"two": two.toJson(),
"three": three.toJson(),
};
}
class Person {
Person({
required this.uid,
required this.updated,
required this.email,
required this.lname,
required this.fname,
});
String uid;
int updated;
String email;
String lname;
String fname;
factory Person.fromJson(Map<String, dynamic> json) => Person(
uid: json["uid"],
updated: json["updated"],
email: json["email"],
lname: json["lname"],
fname: json["fname"],
);
Map<String, dynamic> toJson() => {
"uid": uid,
"updated": updated,
"email": email,
"lname": lname,
"fname": fname,
};
}
This is the class I wrote:
class ContactsService {
Future<List<Person>> fetchPersons(String uid) async {
http.Response response =
await http.get(Uri.parse("$PersonURL?uid=$uid"));
if (response.statusCode == 200) {
Map ContactData = jsonDecode(response.body);
Person one = Person.fromJson(ContactData["one"]);
Person two = Person.fromJson(ContactData["two"]);
Person three = Person.fromJson(ContactData["three"]);
List Persons = [];
Persons.add(one);
Persons.add(two);
Persons.add(three);
return Persons;
} else {
throw Exception("Something has gone wrong, ${response.statusCode}");
}
}
}
To get the objects in my Scaffold I would put
future: ContactsService.fetchPersons()
I want to be able to refer to one.fname, two.email etc I guess I'm missing something within fetchPersons(). So what is missing?
TIA
You can solve it by changing
Map ContactData = jsonDecode(response.body);
to
final contacts = Contacts.fromJson(jsonDecode(a));
and
Person one = Person.fromJson(ContactData["one"]);
Person two = Person.fromJson(ContactData["two"]);
Person three = Person.fromJson(ContactData["three"]);
to
final one = contacts.one;
final two = contacts.two;
final three = contacts.three;
And as a supplement, serialization codes will be unnecessary if you use JsonSerializable provided by Google.
Related
I made a small application, sort of like a library app where you can add books you've read. So, I have a local JSON file, which looks like this:
[
{
"name": "Harry Potter and the Deathly Hallows",
"author": "J.K. Rowling",
"rating": "4.5",
"category": "Fantasy",
"url": "some url"
},
{
"name": "For Whom The Bell Tolls",
"author": "E. Hemingway",
"rating": "4",
"category": "Novel",
"url": "some url#2"
}
]
In my main.dart file I have a function that reads my JSON file, decodes and loads it in a list named "data":
readData(){
DefaultAssetBundle.of(context).loadString("json/books.json").then((s){
setState(() {
data = json.decode(s);
});
});
}
#override
void initState() {
super.initState();
readData();
}
I can easily add new books to "data" and everything is fine except for one thing, - I don't know how to update/write information to JSON, so the app could show me the updated list with the new book after a restart. I've added. How do I do this? And what should I write into the JSON file, - the updated List with all books or just a Map with a new book?
Answer to NelsonThiago
This code is in "class AddingPageState extends State":
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/books.json');
}
Future<File> writeData(List list) async { // "list" is the updated book-list
final file = await _localFile;
String encodedData = jsonEncode(list);
return file.writeAsString('$encodedData');
}
And I call "writeData" in onPressed function.
It would be better to make this using a local NoSql database like sembast, this way you could add, delete and update your data. But as you are already working with json, You just need to encode your new changed data to json and write to the file again.
To write and read files, instead of using rootBundle read this read and write files.
As I can see from your JSON file, you are storing the Books as a list of JSON objects, which will make things easier.
But your request of solving this without adding new classes is a bit strange because adding a Book class in your case would make things much easier.
So I am going to give you a solution that assumes that you create a new Book class.
Start by reading the file as you are currently doing.
Store the content of the file in a List of dynamic List.
Iterate through the list using the map function, add using the from JSON function in the Book class decode the JSON to a book object, then apply the toList() function on that map.
Return the result to your UI and treat it as any other list.
When you want to add a new Book, create a new object of the book class and add it to your list.
When the user finishes adding, transform the list to a JSON object agian and store it in the file again;
Something like this:
The Book Class:
class Book {
String? name;
String? author;
String? rating;
String? category;
String? url;
Book({
required this.name,
required this.author,
required this.rating,
required this.category,
required this.url,
});
Book.fromJson(Map<String, dynamic> json) {
name = json['name'];
author = json['author'];
rating = json['rating'];
category = json['category'];
url = json['url'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['author'] = this.author;
data['rating'] = this.rating;
data['category'] = this.category;
data['url'] = this.url;
return data;
}
#override
String toString() {
return 'Book(name: $name, author: $author, rating: $rating, category: $category, url: $url)';
}
}
And main function
void main() {
/// read the file the way you like
List<dynamic> list = [
{
"name": "Harry Potter and the Deathly Hallows",
"author": "J.K. Rowling",
"rating": "4.5",
"category": "Fantasy",
"url": "some url"
},
{
"name": "For Whom The Bell Tolls",
"author": "E. Hemingway",
"rating": "4",
"category": "Novel",
"url": "some url#2"
}
];
List<Book> books = list
.map(
(jsonObject) => Book.fromJson(jsonObject),
)
.toList();
print(books);
Book newBook = Book(
name: 'Some Book',
author: 'Some Author',
rating: 'Some rating',
category: 'Some category',
url: 'Some Url');
books.add(newBook);
print(books);
books
.map(
(book) => book.toJson(),
)
.toList();
//Write the the file again to storage or anywhere else
}
See the following example. But it requires to create a class. The sample json file is like below (pretty much similar to your json file) -
[
{"name":"Ash","age":"22","hobby":"golf"},
{"name":"philip","age":"17","hobby":"fishing"},
{"name":"charles","age":"32","hobby":"drawing"},
]
And I am editing the above code answered by Ward Suleiman so that you can read from a local json file and write to it-
import 'dart:io';
import 'dart:convert';
List<Player> players = [];
void main() async{
print("hello world");
final File file = File('D:/Sadi/.../test.json'); //load the json file
await readPlayerData(file); //read data from json file
Player newPlayer = Player( //add a new item to data list
'Samy Brook',
'31',
'cooking'
);
players.add(newPlayer);
print(players.length);
players //convert list data to json
.map(
(player) => player.toJson(),
)
.toList();
file.writeAsStringSync(json.encode(players)); //write (the whole list) to json file
}
Future<void> readPlayerData (File file) async {
String contents = await file.readAsString();
var jsonResponse = jsonDecode(contents);
for(var p in jsonResponse){
Player player = Player(p['name'],p['age'],p['hobby']);
players.add(player);
}
}
And the Player class -
class Player {
late String name;
late String age;
late String hobby;
Player(
this.name,
this.age,
this.hobby,
);
Player.fromJson(Map<String, dynamic> json) {
name = json['name'];
age = json['age'];
hobby = json['hobby'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['age'] = this.age;
data['hobby'] = this.hobby;
return data;
}
}
I'm really new to flutter. I tried searching for this question and although I have found a solution but not all my problems are solved, as most of it just use return.
I have a JSON that i get from API calling here:
{
"error": false,
"message": "LOGIN_SUCCESS",
"user": {
"id": 1219,
"email": "john#example.com"
"name": "John Doe",
"category": 1,
"branch": 1004,
"lastlogin": "2020-12-04 03:12:43"
}
}
I already create the class for user as below
class User {
int id;
String name;
String email;
String category;
String branch;
String lastLogin;
User({
this.id,
this.name,
this.email,
this.category,
this.branch,
this.lastLogin
});
factory User.fromJson(Map<String, dynamic> datauser){
return User(
id: datauser['id'],
name: datauser['name'],
email: datauser['email'],
category: datauser['category'],
branch: datauser['branch'],
lastLogin: datauser['lastlogin']
);
}
}
and a result class as below..
class Result {
String message;
User user;
Result({
this.message,
this.user
});
factory Result.fromJson(Map<String, dynamic> resultData){
return Result(
message: resultData['message'],
user: User.fromJson(resultData['user'])
);
}
}
now here comes my problem as i don't know how to move forward from this point
login() async {
List<User> users;
final response = await http.post("myUrlWithAPIcalls",
body: {"email": email, "password": password});
final data = jsonDecode(response.body);
var rest = data['user'] as List;
users = rest.map<User>((json) => User.fromJson(json)).toList();
}
so the question is, how can i assign the value i get from the JSON that has converted into list into a variable?
now in example if it was only a simple JSON object, i could do it like this..
final data = jsonDecode(response.body);
int id = data['id'];
String name = data['name'];
String email = data['email'];
String category = data['category'];
String branch = data['branch'];
but how can i do that in a list object?
as of anyone was wondering why do i do like this, i was trying to save the data into a sharedpref class that i copied from someone else code.
Shared preferences are not meant to store objects. Use something like sqflite to persist objects (official cookbook here).
I don't understand why your JSON shows one user's data, but the login() function seems to decode a list of users.
I'm guessing that's what you want :
login() async {
final response = await http.post("myUrlWithAPIcalls",
body: {"email": email, "password": password});
final data = jsonDecode(response.body);
var user = User.fromJson(data['user']); // the variable you want
}
You don't say where that login() function is, or what you want to do with that User object. FYI, an essential part of Flutter is state management.
I hope I could explain clearly my need, What I have is an API that I connect to and recive a JSON from it.
The Json is nested into a list and an object that have a 2ed tier list.
What i want is to insert this Josn into my local DB (Using SQLite for Flutter).
The point I reached is the inserting of the 1st tier of the json data, but could not insert the data that exist as a list in the 2ed tier. Although I serilize it but the list after when i try to insert, have no mappnig from a list form to the database table fields.
Its like I have [Class User] and [Class Company] A user have many companies rolled in.
When serilaizing the data from the JSON I have the data come to the User Class then nesting to the Companies Class and do the serilaizng for the 2ed tier of the json object then return.
The next step is to insert the data into DB but the data I have in my last object is something like this:
"username" : userName
"company" : CompanyList --- This is a list and cant be inserted into the DB directly in a nested way. As the companylist have multyble fields. and more than one records ex: two companies.
I belive there would be A map to do this in reverse so i could insert it to the DB (I have it in my Company Class) but i cant call it from the place where i try to insert the data into DB due of the differnce in the Classes instances
The instance in my hand is a User Type instance [The main Class between the two], while the companyList if wanted to map it needs a Company insance type.
I can provide code or explain more, any help or idea about the approach may help. Thanks a lot!
#EDIT
This where i try to insert the data into DB
The first inserting is fine but the 2ed one is not.
Future<int> createUserPermissions(UserPermissions userPermissions, ComListPermissions comListPermissions) async {
final db = await dbProvider.database;
var result = db.insert(userPermissionsDBTable, userPermissions.mapToDB());
db.insert(userCompaniesTableDB, comListPermissions.toJson());
return result;
}
User Class
class UserPermissions {
final String userName;
final List<ComListPermissions> comLists;
final bool active;
final String groupName;
final int companyId;
final String permissions;
final ComListPermissions company;
UserPermissions({this.company, this.groupName, this.companyId, this.permissions, this.userName, this.active, this.comLists});
factory UserPermissions.mapFromJsonToDB(Map<String, dynamic> data) {
if (data['comLists'] != null) {
var companyObjJson = data['comLists'] as List;
List<ComListPermissions> _companies = companyObjJson
.map((companyJson) => ComListPermissions.fromJson(companyJson))
.toList();
return UserPermissions(
userName: data['userName'],
active: data['active'],
comLists: _companies,
);
} else {
return UserPermissions(
userName: data['userName'],
active: data['active'],
);
}
}
Map<String, dynamic> mapToDB() => {
"userName": userName,
// "comLists": comLists,
"active": active,
};
}
Comapny Class
class ComListPermissions {
ComListPermissions({
this.groupName,
this.companyId,
this.permissions,
});
String groupName;
int companyId;
String permissions;
//List<String> permissions;
factory ComListPermissions.fromJson(Map<String, dynamic> json) {
if (json['permissions'] != null) {
var permissionsObjJson = json['permissions'] as List;
String s = permissionsObjJson.toString();
return ComListPermissions(
groupName: json["groupName"],
companyId: json["companyID"],
permissions: s,
);
} else {
return ComListPermissions(
groupName: json["groupName"],
companyId: json["companyID"],
);
}
}
Map<String, dynamic> toJson() => {
"groupName": groupName,
"companyID": companyId,
"permissions": permissions,
//"permissions": List<dynamic>.from(permissions.map((x) => x)),
};
}
I Just added a for loop and sent index every time i needed to insert a new recırd into my DB
Future<int> createUserPermissions(UserPermissions userPermissions) async {
final db = await dbProvider.database;
var result = db.insert(userPermissionsDBTable, userPermissions.mapToDB());
int index = userPermissions.comLists.length;
for (int i = 0; i < index; i++) {
db.insert(userCompaniesTableDB, userPermissions.toDatabaseForCompany(i));
}
return result;
}
Map<String, dynamic> toDatabaseForCompany(int index) => {
"companyID": comLists[index].companyId,
"groupName": comLists[index].groupName,
"permissions": comLists[index].permissions,
};
I need to obtain a list of Articles(a custom object) from a realtime database in Firebase. I first decode my data from a json data type. Then I try to convert it into a list using this line of code:
List<Article> articles = List<Article>.from(articleResponse)
.map((Map model) => Article.fromJson(model))
.toList();
However, this gives a syntax error of "The argument type 'Article Function(Map<dynamic,dynamic>)' can't be assigned to the parameter type 'dynamic Function(Article)'." I have included the code I use to fetch an Article(the custom object) as well as the factory method for the class.
//Method to get articles
Future<List<Article>> fetchArticles() async {
final response = await http.get(
"https://some-server.firebaseio.com/some-url.json");
final articleResponse = json.decode(response.body);
List<Article> articles = List<Article>.from(articleResponse)
.map((Map model) => Article.fromJson(model))
.toList(); // Now we're looping over the response entries (maps of article info) to create Article instances
return articles;
}
\\Factory Method
factory Article.fromJson(Map<String, dynamic> json) {
return Article(
id: json['id'],
title: json['title'],
author: json['author'],
date: json['date'],
imageUrl: json['imageUrl'],
modalities: json['modalities'],
);
}
I make an example with something like a json response.
void main() {
//this is an example like a json response
List<Map<String, dynamic>> articleResponse = [
{
"id":"1",
"name":"test1"
},
{
"id":"2",
"name":"test2"
}
];
List<Article> articles = List<Article>.from(articleResponse.map((Map art)=>Article.fromJson(art)))
.toList();
print('${articles.length} articles in the list!! use to render de ui list');
}
class Article{
String id;
String name;
Article({this.id,this.name});
factory Article.fromJson(Map<String, dynamic> json) {
return Article(
id: json['id'],
name: json['name'],
);
}
}
basically you need to change your method to get articles with this.
//Method to get articles
Future<List<Article>> fetchArticles() async {
final response = await http.get(
"https://some-server.firebaseio.com/some-url.json");
final articleResponse = json.decode(response.body);
List<Article> articles = List<Article>.from(articleResponse.map((Map art)=>Article.fromJson(art)))
.toList(); // Now we're looping over the response entries (maps of article info) to create Article instances
return articles;
}
you can use JsonToDart
this is create a class for parse your complex json data
paste json and get class of model
you can overrride toString in your model like:
#override
String toString() {
return '{
id: $id,
title: $title,
author: $author,
date: $date,
imageUrl: $imageUrl,
modalities: $modalities
}';
}
and override toMap :
Map<String, dynamic> toMap() {
return <String, dynamic>{
'id': id,
'title': title,
'author': author,
'date': date,
'imageUrl': imageUrl,
'modalities': modalities,
};
}
and you can use serialization that. this can help you
My API returns the following response:
[{id: 1, nome: foo}, {id: 2, nome: bar}]
And I created a model Client to represent each one:
class Client {
final int id;
final String name;
Client({
this.id,
this.name,
});
factory Client.fromJson(Map<String, dynamic> json) {
return Client(
id: json['id'],
name: json['nome'],
);
}
Map<String, dynamic> toJson() => {
'id': id,
'nome': name,
};
}
Then, in my repository, the method fetching the data above is as follows:
Future<List<Client>> getClients() async {
try {
final _response = await _dio.get(
'/clientes',
options: Options(
headers: {'Authorization': 'Bearer $TOKEN'},
),
);
return Client.fromJson(_response.data[0]) as List; // Error pointed to this line
} on DioError catch (_e) {
throw _e;
}
}
Being stored here
#observable
List<Client> clients;
I am not sure what to do. What am I doing wrong?
dio will decode the response and you'll get a List<dynamic>. Use List.map to convert it to a list of clients, by passing a function that will turn a Map<String, dynamic> into a Client. (You already have one - the named constructor.)
For example:
var dioResponse = json.decode('[{"id": 1, "nome": "foo"}, {"id": 2, "nome": "bar"}]');
List<dynamic> decoded = dioResponse;
var clients = decoded.map<Client>((e) => Client.fromJson(e)).toList();
You're trying to cast a Client as a List<dynamic> which isn't a valid cast since Client doesn't implement List. If you want to return a List containing a single Client, you'll want to change the line with the error to:
return [Client.fromJson(_response.data[0])];