I'm using the json_serializable pub package for all my RESTful API needs, and it worked great, but now i'm trying to serialize content from Wikipedia.
My problem is with the response structure, one of the keys is unknown beforehand and dynamic according to the returned page ID, here's an example:
I would like to get only the intro part of a page, my query is:
https://en.wikipedia.org/w/api.php?action=query&format=json&prop=extracts&redirects=1&exintro=1&explaintext=1&titles=Stack%20Overflow
The response will be:
{
"batchcomplete": "",
"query": {
"pages": {
"21721040": {
"pageid": 21721040,
"ns": 0,
"title": "Stack Overflow",
"extract": "Stack Overflow is a privately held website, the flagship site of ......."
}
}
}
}
Eventually i would like to get only the internal extract string, but "on my way" there i have the page id under pages.
I can't find how i can get an object when i cannot tell what is the key, neither using json_serializable package nor explicitly writing the JSON import code. Is it possible?
PS, i do think i found a way that required two API calls - if i add the indexpageids flag and set it to true, i will receive an additional dictionary entry called pageids with the page id numbers as string, then i can use the retrieved string in the second API call.
I still don't know the exact way i'll do it but i'm pretty sure it's possible, but sending 2 API requests every time is expensive and i would like to know if there's a more elegant way to do it.
Make sure that you use the latest version of json_serializable. Use a Map<String, Page> for key-value pairs:
#JsonSerializable()
class Query {
final Map<String, Page> pages;
Query(this.pages);
factory Query.fromJson(Map<String, dynamic> json) => _$QueryFromJson(json);
Map<String, dynamic> toJson() => _$QueryToJson(this);
}
Just to prove that it works, here is what json_serializable is generating:
Query _$QueryFromJson(Map<String, dynamic> json) {
return new Query((json['pages'] as Map<String, dynamic>)?.map((k, e) =>
new MapEntry(
k, e == null ? null : new Page.fromJson(e as Map<String, dynamic>))));
}
You can get keys of json & then use that key to fetch value as
var response = await http.get(API_ENDPOINT);
var responseJson = json.decode(response.body);
Map<String, dynamic> json =responseJson['query']['pages'];
String pageId = json.keys.toList()[0]; // 0 for first page, you can iterate for multiple pages
firstPage = json[pageId];
String title = firstPage["title"];
How would you do if you have another level of dynamic keys inside the dynamic key? I had to edit the generated json_serializable for my second class to use json only instead of the generated json['key'].
Page _$PageFromJson(Map<String, dynamic> json) {
return new Page((json as Map<String, dynamic>)?.map((k, e) =>
new MapEntry(
k, e == null ? null : new SecondPage.fromJson(e as Map<String, dynamic>))));
}
It feels like there is some better way? Because this generated code gets overwritten everytime I make changes in any file and run generator..
Im new to SO so I cant rate answers and write comments. But boformer's answer helped me but then I ran into this second question.
Edit: I found the solution by trial and error, used:
class Query {
final Map<String, Map<String, SecondPage>> pages;
Related
An API gives me json response like this:
[{"version": "v3.5"}, {"setup": true}, {"address": "e7d398b"}, {"connected": true}, {"active": true}, {"id": "ce7143"}, {"genuine": true}]
As you can see, this is a list of objects. I tried parsing it like this using quicktype generated model class-
List<Result>.from(result.map((x) => Result.fromJson(x)));
But it's failing since each of the objects are of different types.
I think I have to convert each of the objects in the array one by one to Dart classes and add it to an Array.
So I tried this (I am using dio) -
final result = response.data;
var b = List.empty(growable: true);
result.map((x) => b.add(x));
But it's not working.
How can I atleast access the elements of the array?
Solved
Inspired by the accepted answer, I was able to generate corresponding Dart Class. Never thought can looping through a map is possible, IDE was not giving any clue.
final result = response.data;
Map<String, dynamic> map = {};
for (var e in result) {
map.addAll(e);
}
final finalResult = Result.fromJson(map);
return finalResult;
As Randal Schwartz mentioned above, there is no JSON you can not parse with Dart.
In your case, you have a List of Map objects. What you can do is:
final data = jsonDecode(json) as List;
Map<String, dynamic> map = {};
for (var e in data) {
map.addAll(e);
}
print(map);
//prints
{version: v3.5, setup: true, address: e7d398b, connected: true, active: true, id: ce7143, genuine: true}
If you're using the dio flutter package it returns decoded json, no
need to call for jsonDecode.
I recommend using json code generation if you face large json instead of relying on quicktype generated models.
There's no JSON that isn't parsable with Dart. But you might end up with a data structure that requires careful navigation. Classes make it easier, but there isn't always a class structure for any arbitrary JSON, and maybe that's your point. In that case, you'll have to navigate to the data of interest in an ad-hoc fashion.
An API is giving me a JSON response like so :
{
"amountCredited":0,
"isFirstOrder":false,
"orderItems":[
{
"_id":624342e1c66be9001d501230,
"status":2,
"pinCode":749326,
"kioskId":61bb3982089a66001db4ab77,
"kioskActivityId":620668ad433322b99557c874
}
]
}
I'm trying to access the data inside the "orderItems" in order to feed it to an existing parsing model in the App
order = OrderItemModel.fromJson(response.body['orderItems'] as Map<String, dynamic>);
but since the data inside orderItems JSON response is inside an array I can't access it this way..
How can I access it knowing that this JSON "orderItems" array will always only have one item as a response ?
Would something like response.body['orderItems' : [0]] enable me to access the first item data ?
Since you always know that the array will always have only one item. We'll first make it a List & then access the first element.
order = OrderItemModel.fromJson((response.body['orderItems'] as List<dynamic>).first as Map<String, dynamic>);
To understand it a bit better refer to the code below:
The JSON response contains an array, which we need to access.
final ordersArray = response.body['orderItems'] as List<dynamic>;
Then we want to access the first order (according to the question)
final firstOrder = ordersArray.first as Map<String, dynamic>;
Once we have the order, we'll convert it to the model
final order = OrderItemModel.fromJson(firstOrder);
EDIT:
as it was pointed out in a comment List objects have a getter called first which can be used to get the first element, the code has been updated with that.
You need to convert as Map and List because Flutter deal only with this datatypes. For example:
Map order1 = {
"_id":"624342e1c66be9001d501230",
"status":2,
"pinCode":749326,
"kioskId":"61bb3982089a66001db4ab77",
"kioskActivityId":"620668ad433322b99557c874"
};
Map order2 = {
"_id":"224342e1c66be9001d501232",
"status":1,
"pinCode":248023,
"kioskId":"41bb3982089a66001db4ab74",
"kioskActivityId":"720668ad433322b99557c875"
};
Map m = {
"amountCredited":0,
"isFirstOrder":false,
"orderItems":[
order1,
order2
]
};
print(m['orderItems'][1]);
print(m['orderItems'][1]['pinCode']);
The result will be:
{_id: 224342e1c66be9001d501232, status: 1, pinCode: 248023, kioskId: 41bb3982089a66001db4ab74, kioskActivityId: 720668ad433322b99557c875}
248023
In my app, the user chooses the chapter he wants to read, the verse he wants to begin from, and the end verse.
I'm going to store these three strings and show in his "reading history" list, where he can see all of his previous readings.
I read that you can do that by creating a class, storing these in an object and converting it to JSON then storing it inside sharedprefs.(or something like that).
But I didn't understand them as they were a little different from my case.
this is the class:
class Segment {
final String chapter;
final String from;
final String to;
Segment({this.chapter, this.from, this.to});
factory Segment.fromJson(Map<String, dynamic> json) {
return Segment(
chapter: json['chapter'],
from: json['from'],
to: json['to'],
);
}
Map<String, dynamic> toJson() {
return {
'chapter': chapter,
'from': from,
'to': to,
};
}
}
these the steps i want to know how to do:
store the string in the object.
Encode the object to JSON.
store it inside sharedprefs.
decode it back and choose a certain item from the list.
You can store a JSON (Map) object with shared preferences in Flutter by encoding the Map to raw JSON (it is basically a String).
To store something with shared preferences you should first of all add it to your dependencies. Here's how.
Then somewhere in your code get the instance of SharedPreferences object by doing:
final prefs = await SharedPreferences.getInstance();
After that you should encode the Map that you got from your Segment class by doing this:
Segment segment = Segment(...);
String rawJson = jsonEncode(segment.toJson());
To save this new JSON with shared preferences run this command to store the whole JSON as a String:
prefs.setString('my_string_key', rawJson);
When you want to read your data from shared preferences use this:
final rawJson = prefs.getString('my_string_key') ?? '';
Map<String, dynamic> map = jsonDecode(rawJson);
final Segment = Segment.fromJson(map);
For more details see this article.
I have a json file with 40,000 entries approx. I am trying to parse it in my flutter application. i am getting the json and converting it successfully in desired model. But the problem is, it is taking too much time to load. My json file is stored in Firebase storage bucket. Is there any other way so that it could load quickly ? I was thinking of storing the data in realtime database, but i am sure that there also it will quickly give the results or not. Any suggestions would be greatly helpful.
class CollegeCode {
final String college_code;
final String studentcode;
final String college_Name;
final String college_Location;
CollegeCode({this.college_code, this.college_Name, this.college_Location, this.studentcode});
factory CollegeCode.fromJson(Map<String, dynamic> json) =>
CollegeCode(
college_code: json['College Code'] ,
college_Location: json['Location'],
college_Name: json['College Name'],
studentcode: json['Student Code']
);
}
class GetList{
GetList({this.data});
List<CollegeCode> data;
factory GetList.fromJson(Map<String, dynamic> json) {
// print(json['CollegesList'].toString());
if(json != null){
return GetList(
data: List<CollegeCode>.from(
json["CollegesList"].map((x) => CollegeCode.fromJson(x))).toList() ?? [],
);
}
return GetList(data: []);
}
}
Future <List<CollegeCode>> parsePhotos(String responseBody) async{
if(responseBody.isNotEmpty) {
final parsed = await GetList.fromJson(json.decode(responseBody));
final datalist = parsed.data.toList();
return datalist;
}
}
Future<List<CollegeCode>> fetchPhotos(http.Client client) async {
final response =
await client.get(url of file in storage bucket');
// print(response.body);
return parsePhotos(response.body);
// return compute(parsePhotos, response.body);
}
I want to know if there is any alternate of doing the same.
Don't expect to have 40 000 entries getting processed in an instant. If you desire to use a local database, the sqflite package is great for that purpose, as it even provides batches, where large amounts of information can be processed in shorter times, albeit costing memory.
Furthermore, you might want to check if the amount of information that you are processing falls under these parameters
Are you sure it's the decoding which takes time vs transferring this giant file over the wire?
From my REST API a JSON string is received as
{"total":"30","results":[
{"ID":"1809221034017","DATE":"2018-09-22","REG":"(E9)","START":"10:40","END":"10:48"},
{"ID":"1809221337250","DATE":"2018-09-22","REG":"(E4)","START":"13:43","END":"13:57"},
{"ID":"1809161032213","DATE":"2018-09-16","REG":"(E1)","START":"11:04","END":"11:13"}]}
The total field tells me that the database contains in total 30 records, the requested data (only 3 rows) is included in the results section.
I need to parse the data, so I can show the results in ListView. I managed to do this with a simple JSON string, but not with this complex JSON string. Unfortunately I am not able to change the output of the web service since this is hosted by a 3rd party.
Any help, or a code example, is appreciated.
Thanks in advance
Read first my other answer here.
Then I suggest you to use a class generation library like quicktype.
Using quick type for example you can easily and automatically genearate your moidel class in dart using your JSON. Here the generated file.
quicktype --lang dart --all-properties-optional https://www.shadowsheep.it/so/53968769/testjson.php -o my_json_class.dart
Then use it in code:
import 'my_json_class.dart';
import 'package:http/http.dart' as http;
var response = await http.get('https://www.shadowsheep.it/so/53968769/testjson.php');
var myClass = MyJsonClass.fromJson(jsonDecode(response.body));
for(var result in myClass.results.toList()) {
print(result?.id);
}
N.B. If you'll master a code generator library, then you'll be able to parse any type of JSON coming from a REST API and you'll have more time for fun.
So here we can see that there is a JSON object having two values a JSON array and a total number.Sometimes the internet services don't return the full value but only a part due to network issues.
So now in the array we have number of results objects with an ID,DATE,REG,START,END.
If you format the JSON you will decode it easily.
i recommend this article: https://medium.com/flutter-community/parsing-complex-json-in-flutter-747c46655f51
class Result
{
String id ;
String date;
String reg ;
String start;
String end ;
Result({this.date,this.end,this.id,this.reg,this.start});
factory Result.fromJson(Map<String, dynamic> parsedJson) {
return new Result(
id: parsedJson['ID'],
date: parsedJson['DATE'],
reg: parsedJson['REG'],
start: parsedJson['START'],
end: parsedJson['END'],
);
}
}
class Results
{
String total;
List<Result> results;
Results({this.results, this.total});
factory Results.fromJson(Map<String, dynamic> parsedJson) {
var list = parsedJson['results'][] as List;
return new Results(
total: parsedJson['total'],
results: list.map((i) => Result.fromJson(i)).toList());
}}