Local JSON deserialization in Flutter using Json_Serializable - json

I'm trying to access a local JSON file in my flutter app but am stuck on how to:
1) Load the json file into my project
2) Parse/decode it using the serialization module I've put together.
The code for the serialisation module Classes:
import 'package:json_annotation/json_annotation.dart';
part 'testJson.g.dart';
#JsonSerializable()
class BaseResponse extends Object with _$BaseResponseSerializerMixin {
final List<Topics> topic;
BaseResponse(
this.topic
);
factory BaseResponse.fromJson(Map<String, dynamic> json) => _$BaseResponseFromJson(json);
}
#JsonSerializable()
class Topics extends Object with _$TopicsSerializerMixin {
final int id;
final String name;
final String color;
final List<Reading> reading;
final int tCompletion;
Topics(
this.id,
this.name,
this.color,
this.reading,
this.tCompletion
);
factory Topics.fromJson(Map<String, dynamic> json) => _$TopicsFromJson(json);
}
#JsonSerializable()
class Reading extends Object with _$ReadingSerializerMixin{
final String name;
final int nLos;
final String description;
final String summary;
final List<LOS> los;
final int rCompletion;
Reading(
this.name,
this.nLos,
this.description,
this.summary,
this.los,
this.rCompletion,
);
factory Reading.fromJson(Map<String, dynamic> json) => _$ReadingFromJson(json);
}
#JsonSerializable()
class LOS extends Object with _$LOSSerializerMixin{
final int id;
final String objective;
final String description;
final String formulae;
LOS(this.id, this.objective, this.description, this.formulae);
factory LOS.fromJson(Map<String, dynamic> json) => _$LOSFromJson(json);
}
#JsonLiteral('jsondata.json')
Map get glossaryData => _$glossaryDataJsonLiteral;
I have built a Model to interpret the response for the Topic Class
class topicModel {
final String topic;
final String color;
final int completion;
topicModel({
this.topic,
this.color,
this.completion
});
topicModel.fromResponse(Topics response)
: topic = response.name,
color = response.color,
completion = response.tCompletion;
}
I'm trying to use future builder to load the local JSON file, but am struggling to return the data and parse it through the classes / model
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(context: context),
builder: (BuildContext context, AsyncSnapshot<List> jsonData) {
if (!jsonData.hasData) {
return Text('not loaded');
}
return Text('loaded');
},
}
Future<List<topicModel>> getData({BuildContext context}) async {
String data = await DefaultAssetBundle.of(context).loadString("assets/data/jsondata.json");
Map jsonMap = json.decode(data);
var testdata = BaseResponse.fromJson(jsonMap);
return topicModel.fromResponse(testdata) as List;
}

Related

How to properly create instances containing a Map from a json file in dart?

I'm trying to deserialize a json file and create instances from it but whatever way I use, I end up stucked because of the dynamic type :
type '_Map<String, dynamic>' is not a subtype of type 'Map<String, int>'
Here's my model :
class Race {
final String name;
final Map<String, int> abilitiesUpdater;
const Race({
required this.name,
required this.abilitiesUpdater
});
static fromJson(json) => Race(name: json['name'], abilitiesUpdater: json['abilitiesUpdater']);
}
Here's how I'm trying to deserialize the json file :
class RacesApi {
static Future<List<Race>> getRacesLocally(BuildContext context) async {
final assetBundle = DefaultAssetBundle.of(context);
final String fileContent = await assetBundle.loadString('Assets/COC_Monstres/Races.json');
List<dynamic> parsedListJson = jsonDecode(fileContent);
List<Race> racesList = List<Race>.from(parsedListJson.map<Race>((dynamic i) => Race.fromJson(i)));
return racesList;
}
}
Here's my json file :
[
{
"name": "Vampire",
"abilitiesUpdater": {
"DEX": 2,
"CHA": 2
}
},
{
"name": "Créature du lagon",
"abilitiesUpdater": {
"FOR": 2,
"CON": 2
}
},
...
]
How can I properly cast this json object to fit into my class ?
This works:
class Race {
final String name;
// changed to dynamic
final Map<String, dynamic> abilitiesUpdater;
const Race({required this.name, required this.abilitiesUpdater});
static fromJson(json) =>
Race(name: json['name'], abilitiesUpdater: json['abilitiesUpdater']);
}
Maybe after get the object you can parse that dynamic into int if you need it.
Change your Model Class to this:
class Race {
Race({
required this.name,
required this.abilitiesUpdater,
});
late final String name;
late final AbilitiesUpdater abilitiesUpdater;
Race.fromJson(Map<String, dynamic> json){
name = json['name'];
abilitiesUpdater = AbilitiesUpdater.fromJson(json['abilitiesUpdater']);
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['name'] = name;
_data['abilitiesUpdater'] = abilitiesUpdater.toJson();
return _data;
}
}
class AbilitiesUpdater {
AbilitiesUpdater({
required this.FOR,
required this.CON,
});
late final int FOR;
late final int CON;
AbilitiesUpdater.fromJson(Map<String, dynamic> json){
FOR = json['FOR'];
CON = json['CON'];
}
Map<String, dynamic> toJson() {
final _data = <String, dynamic>{};
_data['FOR'] = FOR;
_data['CON'] = CON;
return _data;
}
}
you can cast the json['abilitiesUpdater'] as Map<String, int> because internally flutter will set it default as Map<String, dynamic>
Code
class Race {
final String name;
final Map<String, int> abilitiesUpdater;
const Race({
required this.name,
required this.abilitiesUpdater
});
static fromJson(json) => Race(name: json['name'], abilitiesUpdater: json['abilitiesUpdater']) as Map<String,int>;
}
it is working fine with me i tried it here is the link to the code https://dartpad.dev/?id=550918b56987552eb3d631ce8cb9e063.
If you still getting error you can try this
class Race {
final String name;
final Map<String, int> abilitiesUpdater;
Race({
required this.name,
required this.abilitiesUpdater
});
static fromJson(json) => Race(name: json['name'], abilitiesUpdater: (json['abilitiesUpdater']as Map<String,int>)) ;
}
or you can try this
class Race {
final String name;
final Map<String, int> abilitiesUpdater;
const Race({required this.name, required this.abilitiesUpdater});
static fromJson(json) => Race(
name: json['name'],
abilitiesUpdater: json['abilitiesUpdater']
.map((key, value) => MapEntry<String, int>(key, value as int)),
);
}
Edit : To have something a little bit more handy and scalable, I created an extension, and it works fine eventhough I have to cast twice the object...
My model :
// import my extension
class Race {
Race({
required this.name,
required this.abilitiesUpdater,
});
late final String name;
late final Map<String, int> abilitiesUpdater;
// late final AbilitiesUpdater abilitiesUpdater;
Race.fromJson(Map<String, dynamic> json){
name = json['name'];
abilitiesUpdater = (json['abilitiesUpdater'] as Map<String, dynamic>).parseToStringInt();
}
}
My extension :
extension Casting on Map<String, dynamic> {
Map<String, int> parseToStringInt() {
final Map<String, int> map = {};
forEach((key, value) {
int? testInt = int.tryParse(value.toString());
if (testInt != null) {
map[key] = testInt;
} else {
debugPrint("$value can't be parsed to int");
}
});
return map;
}
}
Once again, any help on cleaning this is appreciated !
Original answer :
Thanks to Sanket Patel's answer, I ended up with a few changes that made my code works. However I'm pretty clueless on why I can't directly cast a
Map<String, dynamic>
object into a
Map<String, int>
one.
Any info on this would be appreciated :)
Here's how I changed my model class in the end :
class Race {
Race({
required this.name,
required this.abilitiesUpdater,
});
late final String name;
late final AbilitiesUpdater abilitiesUpdater;
Race.fromJson(Map<String, dynamic> json){
name = json['name'];
abilitiesUpdater = AbilitiesUpdater.fromJson(json['abilitiesUpdater']);
}
}
class AbilitiesUpdater {
final Map<String, int> abilitiesUpdater = {};
AbilitiesUpdater.fromJson(Map<String, dynamic> json){
json.forEach((key, value) {
abilitiesUpdater[key] = int.parse(value.toString());
});
}
}

How do I populate a GridView by traversing a JSON array in Flutter the JSON_Serializable way?

I'm trying to parse an array of JSON Objects to populate a GridView in Flutter.
So far, I can only get a single object, but can't traverse the whole array of objects.
JSON String: A list of Beef recipe objects within 'beef' array.
My code:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class SpecificCategoryPage extends StatefulWidget {
late final String category;
SpecificCategoryPage({Key? key, required this.category}) : super(key: key);
#override
_SpecificCategoryPageState createState() => _SpecificCategoryPageState();
}
class _SpecificCategoryPageState extends State<SpecificCategoryPage> {
late Future<Meal> meals;
late List<Widget> mealCards;
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<Meal>(
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(
'Truest\nId: ${snapshot.data!.id}. ${snapshot.data!.meal}');
} else {
return Text('${snapshot.error}');
}
// Be default, show a loading spinner.
return CircularProgressIndicator();
},
future: meals,
),
);
}
#override
void initState() {
super.initState();
meals = _fetchMeals();
}
Future<Meal> _fetchMeals() async {
final http.Response mealsData = await http.get(
Uri.parse('https://www.themealdb.com/api/json/v1/1/filter.php?c=Beef'));
if (mealsData.statusCode == 200)
return Meal.fromJson(jsonDecode(mealsData.body));
else
throw Exception('Failed to load meals');
}
class Meal {
final String? id, meal;
Meal({required this.id, required this.meal});
factory Meal.fromJson(Map<String, dynamic> json) {
return Meal(
id: json['meals'][0]['idMeal'], meal: json['meals'][0]['strMeal']);
}
}
Sample object traversal path:
{"meals":[{"strMeal":"Beef and Mustard Pie","strMealThumb":"https:\/\/www.themealdb.com\/images\/media\/meals\/sytuqu1511553755.jpg","idMeal":"52874"}, {object1}, {object2}]}
What I'm getting:
{"strMeal":"Beef and Mustard
Pie","strMealThumb":"https://www.themealdb.com/images/media/meals/sytuqu1511553755.jpg","idMeal":"52874"}
How do I get all objects in the array and inflate the GridView widget?
import 'dart:convert';
// First you should create a model to represent a meal
class Meal {
// Place all the meal properties here
final String strMeal;
final String strMealThumb;
final String idMeal;
// Create a constructor that accepts all properties. They can be required or not
Meal({
required this.strMeal,
required this.strMealThumb,
required this.idMeal,
});
// Create a method (or factory constructor to populate the object based on a json input)
factory Meal.fromJson(Map<String, dynamic> json) => Meal(
strMeal: json['strMeal'],
strMealThumb: json['strMealThumb'],
idMeal: json['idMeal'],
);
String toString() {
return 'strMeal: $strMeal, strMealThumb: $strMealThumb, idMeal: $idMeal';
}
}
/// Then you should create another object to represent your response
/// It holds a list of meals that'll be populated by your API response
class YourAPIResponse {
final List<Meal> meals;
YourAPIResponse({required this.meals});
factory YourAPIResponse.fromJson(Map<String, dynamic> json) =>
YourAPIResponse(
meals: List<Meal>.from(
json['meals'].map((meal) => Meal.fromJson(meal)),
),
);
}
void main() {
// Test data: This will be your API response
String jsonString = '{"meals": [{"strMeal": "Beef and Mustard Pie","strMealThumb": "https://www.themealdb.com/images/media/meals/sytuqu1511553755.jpg","idMeal": "52874"}]}';
final apiResponse = YourAPIResponse.fromJson(json.decode(jsonString));
// Your meals list
// You can use this to populate the gridview
print(apiResponse.meals);
}
Try something like:
...
return jsonDecode(mealsData.body)['meals'].map((meal) => Meal.fromJson(meal)).toList();
...
class Meal {
final String? id, meal;
Meal({required this.id, required this.meal});
factory Meal.fromJson(Map<String, dynamic> json) {
return Meal(
id: json['idMeal'], meal: json['strMeal']);
}
}
This iterates the meals in your response body and maps them to a List of Meals.

Error: type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>'

So I created an app to read data via api and I tried parsing the JSON api
this is my
Error screenshot
I've tried to change it to a list, but it still reads an error
this is my code
elephant.dart
#JsonSerializable()
class ElephantList{
ElephantList({this.biodata});
final List<Elephant> biodata;
factory ElephantList.fromJson(Map<String, dynamic> json) => _$ElephantListFromJson(json);
Map<String, dynamic> toJson() => _$ElephantListToJson(this);
}
#JsonSerializable()
class Elephant{
Elephant({this.name, this.affiliation, this.species,
this.sex, this.fictional, this.dob, this.dod, this.wikilink, this.image, this.note});
//final String index;
final String name;
final String affiliation;
final String species;
final String sex;
final String fictional;
final String dob;
final String dod;
final String wikilink;
final String image;
final String note;
factory Elephant.fromJson(Map<String, dynamic> json) => _$ElephantFromJson(json);
Map<String, dynamic> toJson() => _$ElephantToJson(this);
}
Future<ElephantList> getElephantList() async{
const url = 'https://elephant-api.herokuapp.com/elephants/sex/male';
final response = await http.get(url);
if(response.statusCode == 200){
return ElephantList.fromJson(json.decode(response.body));
}else{
throw HttpException('Error ${response.reasonPhrase}', uri: Uri.parse(url));
}
}
How do I rectify this error?
please help
The api : https://elephant-api.herokuapp.com/elephants/sex/male returns a List of elephants in the form : [Elephant , Elephant , ... ] while you're specifying that the json received from this request is a Map<String, dynamic> which have a form : { elephants : [ ... ] } so just replace every Map<String, dynamic> with List<dynamic>
#JsonSerializable(anyMap: true)
class ElephantList{
ElephantList({this.biodata});
final List<Elephant> biodata;
factory ElephantList.fromJson(List<dynamic> json) => _$ElephantListFromJson(json);
List<dynamic> toJson() => _$ElephantListToJson(this);
}
#JsonSerializable()
class Elephant{
Elephant({this.name, this.affiliation, this.species,
this.sex, this.fictional, this.dob, this.dod, this.wikilink, this.image, this.note});
//final String index;
final String name;
final String affiliation;
final String species;
final String sex;
final String fictional;
final String dob;
final String dod;
final String wikilink;
final String image;
final String note;
factory Elephant.fromJson(Map<String, dynamic> json) => _$ElephantFromJson(json);
Map<String, dynamic> toJson() => _$ElephantToJson(this);
}
Future<ElephantList> getElephantList() async{
const url = 'https://elephant-api.herokuapp.com/elephants/sex/male';
final response = await http.get(url);
if(response.statusCode == 200){
return ElephantList.fromJson(json.decode(response.body));
}else{
throw HttpException('Error ${response.reasonPhrase}', uri: Uri.parse(url));
}
}
Edit: #JsonSerializable expects the json as Map<string,dynamic>, so to change the expected type you should add anyMap: true
I changed the code accordingly

Access nested objects in json using json_serializable in Dart

Trying to convert my json to objects in Dart/Flutter using the json_serializable. I cannot seem to find a way to access a nested ID (data is coming from MongoDB thus the $ in the json).
Here is the json:
{
"_id": {
"$oid": "5c00b227" <-- this is what I am trying to access
},
"base": 1,
"tax": 1,
"minimum": 5,
"type": "blah"
}
Result:
class Thing {
final int id;
final String base;
final String tax;
final String type;
final int minimum;
}
It is not possible with json_serializable package itself. You have to create separate objects for getting this nested data.
Look the discussion here
https://github.com/google/json_serializable.dart/issues/490
But, there is possible way to get nested fields with added converter (solution was found here https://github.com/google/json_serializable.dart/blob/master/example/lib/nested_values_example.dart)
import 'package:json_annotation/json_annotation.dart';
part 'nested_values_example.g.dart';
/// An example work-around for
/// https://github.com/google/json_serializable.dart/issues/490
#JsonSerializable()
class NestedValueExample {
NestedValueExample(this.nestedValues);
factory NestedValueExample.fromJson(Map<String, dynamic> json) =>
_$NestedValueExampleFromJson(json);
#_NestedListConverter()
#JsonKey(name: 'root_items')
final List<String> nestedValues;
Map<String, dynamic> toJson() => _$NestedValueExampleToJson(this);
}
class _NestedListConverter
extends JsonConverter<List<String>, Map<String, dynamic>> {
const _NestedListConverter();
#override
List<String> fromJson(Map<String, dynamic> json) => [
for (var e in json['items'] as List)
(e as Map<String, dynamic>)['name'] as String
];
#override
Map<String, dynamic> toJson(List<String> object) => {
'items': [
for (var item in object) {'name': item}
]
};
}
try this,
class Thing {
int id;
String base;
String tax;
String type;
int minimum;
Thing({
this.id,
this.base,
this.tax,
this.type,
this.minimum,
});
factory Thing.fromJson(Map<String, dynamic> json) {
return Thing(
id: json['_id']["oid"],
base: json['base'].toString(),
tax: json['tax'].toString(),
type: json['type'].toString(),
minimum: json['minimum'],
);
}
}

Exception: type 'String' is not a subtype of type 'Map<String, dynamic>

Exception: type 'String' is not a subtype of type 'Map<String, dynamic>'
{"collection":{"data":"{\"id\": 1, \"name\": \"Marko\", \"picture\":
\"https://lh3.googleusercontent.com/a-/AAuE7mC1vqaKk_Eylt-fcKgJxuN96yQ7dsd2dBdsdsViK959TKsHQ=s96-
c\"}","statusCode":202,"version":"1.0"}}
This is the above json and i want to put it at User pojo class only the [data].
But it threw the above exception type.
class UserCollection {
final User data;
final int statusCode;
final String version;
UserCollection({this.data, this.statusCode, this.version});
factory UserCollection.fromJson(Map<String, dynamic> json) {
return UserCollection(
statusCode: json['statusCode'] as int,
data: User.fromJson(json['data']) ,
version: json['version'] as String );
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['data'] = this.data;
data['statusCode'] = this.statusCode;
data['version'] = this.version;
return data;
}
}
User Pojo class
#JsonSerializable()
class User {
final int id;
final String sub;
final String home;
final String work;
final String name;
final String mobileNo;
final String email;
final String favMechId;
final String appVersionCode;
final String picture;
final String serverTime;
final String dateCreated;
final String dateModified;
final String fcmTokenId;
User(
{this.id,
this.sub,
this.home,
this.work,
this.name,
this.mobileNo,
this.email,
this.favMechId,
this.appVersionCode,
this.picture,
this.serverTime,
this.dateCreated,
this.dateModified,
this.fcmTokenId});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String,dynamic> toJson() => _$UserToJson(this);
I have referring this medium site for clarity, medium flutter json
but in vein more than 4 hours i couldn't what was wrong.
If change the User.from() to String then it's okay. But i need to parse the [data] from json to User pojo class.
Try below,
factory UserCollection.fromJson(Map<String, dynamic> json) {
return UserCollection(
statusCode: json['statusCode'] as int,
data: User.fromJson(json.decode(json['data'])),
version: json['version'] as String );
}
Change in data: User.fromJson(json.decode(json['data'])),