json_serializable fails to deserialize - json

I am trying to add json support to my flutter project but has some hard time getting it right.
I love flutter but when it comes to json I wish for gson.
I have created a small project that exemplifies my problem.
Please se https://bitbucket.org/oakstair/json_lab
I get the error
type 'Match' is not a subtype of type 'Map' in type cast when trying to run the simple to/from json test.
There is obviously something that I miss here!
Thanks in advance from a stormy Stockholm!
import 'package:json_annotation/json_annotation.dart';
part 'json_lab.g.dart';
#JsonSerializable()
class Match {
int home;
int away;
double homePoints;
double awayPoints;
Match(this.home, this.away, {this.homePoints, this.awayPoints});
factory Match.fromJson(Map<String, dynamic> json) => _$MatchFromJson(json);
Map<String, dynamic> toJson() => _$MatchToJson(this);
}
#JsonSerializable()
class Tournament {
List<String> participants; // Teams or Players.
List<List<Match>> table = new List<List<Match>>();
Tournament(this.participants, {this.table});
factory Tournament.fromJson(Map<String, dynamic> json) => _$TournamentFromJson(json);
Map<String, dynamic> toJson() => _$TournamentToJson(this);
}

Because I cannot see your json data I've made assumptions on the information you've provided on naming the objects. You'll need to change the following to match the json names (case sensitive).
Try the following to create your Match object
#JsonSerializable(nullable: true) //allow null values
class Match extends Object with _$MatchSerializerMaxin {
int home;
int away;
double homePoints;
double awayPoints;
Match({this.home, this.away, this.homePoints, this.awayPoints});
factory Match.fromJson(Map<String, dynamic> json) => _$MatchFromJson(json);
Map<String, dynamic> toMap() {
var map = new Map<String, dynamic>();
map["Home"] = home;
map["Away"] = away;
map["HomePoints"] = homePoints;
map["AwayPoints"] = awayPoints;
return map;
}
Match.fromMap(Map map){
try{
home = map["Home"] as int;
away = map["Away"] as int;
homePoints = map["HomePoints"] as double;
awayPoints = map["AwayPoints"] as double;
}catch(e){
print("Error Match.fromMap: $e");
}
}
}
Match _$MatchFromJson(Map<String, dynamic> json){
Match match = new Match(
home: json['Home'] as int,
away: json['Away'] as int,
homePoints: json['HomePoints'] as double,
awayPoints: json['AwayPoints'] as double,
);
return match;
}
abstract class _$MatchSerializerMaxin {
int get home;
int get away;
double get homePoints;
double get awayPoints;
Match<String, dynamic> toJson() => <String, dynamic>{
'Home' : home,
'Away' : away,
'HomePoints' : homePoints,
'AwayPoints' : awayPoints
};
}

I just committed a solution to this problem to the repo.
I had to add explicitToJson.
#JsonSerializable(explicitToJson: true)

Related

Flutter/Dart: Reduce size of JSON file

I have a a few classes that i serialize nicely with the flutter jsonEncode/jsonDecode macros:
part 'friend.g.dart';
#JsonSerializable(explicitToJson: true)
class Friend {
Friend({required this.name});
#JsonKey(required: true)
late String name;
#JsonKey(required: true)
late final Preferences prefTree;
#JsonKey(required: false, defaultValue: "assets/avatars/cat.png")
late String avatarAsset = avatarAssets[Random().nextInt(avatarAssets.length-1)];
factory Friend.fromJson(Map<String, dynamic> json) => _$FriendFromJson(json);
Map<String, dynamic> toJson() => _$FriendToJson(this);
Map<String, dynamic> _$FriendToJson(Friend instance) => <String, dynamic>{
'name': instance.name,
'avatarAsset': instance.avatarAsset,
'isFavorite': instance.isFavorite,
'prefTree': instance.prefTree,
};
....
}
It works very finely, and when deserializing it will use the default values if the JSON value is not present in the json file.
The problem is the serializer.
What i would like is that
when my class attribute has a default value, and
the attribute is not required, and
the value of the attribute of my instance is the same as the default value,
==> then the serializer would not write down the value in the json file.
This would save me dozens of thousands of lines in the JSON file.
I read about the different JSON members, like "required", "defaultValue", etc, and i use them, but still the serializer does not seem to take this into account.
Again, the deserializer works like a charm.
So instead of having my classes serialized to this:
{
"itemName": "Brown",
"itemIconString": "",
"isAPreference": false
},
I'd like to have it serialized like this (because of the default values):
{
"itemName": "Brown",
},
Is it me or it is not possible to avoid the default value of a member to be outputed in the jsonEncode?
Thanks!
I don't think JsonSerializable provides such an option. but you can write this manually.
here is an example:
class Test {
final int a; // default is 1
final int b; // default is 2
Test({ this.a = 1, this.b = 2});
Map<String, dynamic> toJson() {
final result = <String, dynamic>{};
if (a != 1) {
result['a'] = a;
}
if (b != 2) {
result['b'] = b;
}
return result;
}
}
void main() {
final t = Test(a: 1, b: 1);
print(t.toJson());
}

Flutter json_serializable error: Unhandled Exception: type 'Null' is not a subtype of type 'String' in type cast

Im having the problem above, I understand that in my data some things are null for example the leistungData from Diesel doesn't contain anything, so the title is null:
"title":"Benzin",
"leistungData":[
{
"title":"240 PS"
}
]
},
{
"title":"Diesel",
"leistungData":[
]
},
So I get that I get the 'Null' is not a subtype of type 'String'. But how can I change my code below so that I can keep this Json structure?
import 'leistungData.dart';
part 'treibstoffData.g.dart';
#JsonSerializable(explicitToJson: true)
class TreibstoffData {
final String title;
final List <LeistungData> leistungData;
TreibstoffData(this.title, this.leistungData);
factory TreibstoffData.fromJson(Map<String, dynamic> json)
=> _$TreibstoffDataFromJson(json);
Map<String, dynamic> toJson() => _$TreibstoffDataToJson(this);
}
& here the LeistungData:
import 'package:json_annotation/json_annotation.dart';
import 'unterscheidungsData.dart';
part 'leistungData.g.dart';
#JsonSerializable()
class LeistungData {
final String title;
LeistungData(this.title);
factory LeistungData.fromJson(Map<String, dynamic> json)
=> _$LeistungDataFromJson(json);
Map<String, dynamic> toJson() => _$LeistungDataToJson(this);
}
I hope I was clear and didn't forget anything, just write to me if Im missing something
convert to nullable list.
replace final List <LeistungData> leistungData; with final List <LeistungData>? leistungData;
also try final String? title ;
let me know if it fails to solve the error.

Flutter serialize from json depending on type

I want to convert a nested model to the correct model depending on the type. I know that #JsonKey can be used to specifically handle certain properties. However, how can I access other properties on fromJson? The fromJson method has to be static, but then I cannot access the other properties. Does anyone have any idea how to solve this?
#JsonSerializable(explicitToJson: true, nullable: true)
class Model {
int type;
#JsonKey(
name: 'action',
fromJson: _convertActionToModel,
)
dynamic action;
Model({this.type, this.action});
factory Model.fromJson(Map<String, dynamic> json) =>
_$ModelFromJson(json);
Map<String, dynamic> toJson() => _$ModelToJson(this);
static dynamic _convertActionToModel(dynamic json) {
switch (type) { // How can i get this type?
case 0:
return OtherModel.fromJson(json as Map<String, dynamic>);
break;
....
}
}
How can I get the type for the switch case?
Your json is just a map<String, Object>, you could do:
var type = json['typeKey'] as String (or int, bool);

Flutter Persistence: how to jsonDecode a List<dynamic> to List<ClassType>?

I have a Todo-List app with Task class.
I want to serialize a Task List with jsonEncode and persist them onto a file in Docs dir.
after that, I want to be able to re-serialize the same list and convert them into my native List datatype (from List<String, dynamic> that I get from jsonDecode). Whats the best way to do it?
Currently, I tried:
void reSerializeTaskList() async {
final directory = await getApplicationDocumentsDirectory();
File f = File('${directory.path}/new.txt');
String fileContent = await f.readAsString();
List<dynamic> jsonList = jsonDecode(fileContent).cast<Task>(); // does not work
print("JSONSTRING: ${jsonList.runtimeType}");
print("$jsonList");
}
I/flutter (29177): JSONSTRING: CastList<dynamic, Task>
E/flutter (29177): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Task' in type cast
my workaround is to iterate through all array elements and build a Task type out of the values with "fromJson" method inside my Task class:
void reSerializeTaskList() async {
final directory = await getApplicationDocumentsDirectory();
File f = File('${directory.path}/new.txt');
String fileContent = await f.readAsString();
List<dynamic> jsonList = jsonDecode(fileContent);
List<Task> taskList = [];
for (var t in jsonList) {
print("T: $t and ${t.runtimeType}");
Task task = new Task();
taskList.add(task.fromJson(t));
}
print("JSONSTRING: ${jsonList.runtimeType}");
print("$jsonList");
print("$taskList");
print("$taskList.runtimeType");
}
my Task class:
import 'dart:io';
class Task {
String name;
bool isDone;
Task({this.name, this.isDone = false});
void toggleDone() {
isDone = !isDone;
}
#override
String toString() {
// TODO: implement toString
return "${this.name} is done: $isDone";
}
Map<String, dynamic> toJson() {
return {
"name": this.name,
"isDone": this.isDone,
};
}
Task fromJson(Map<String, dynamic> json) {
this.name = json['name'];
this.isDone = json['isDone'];
return this;
}
}
But is there maybe another (better) approach? This looks quite patchy to me...
Just to give you a little example, this is how I do it
final jsonResponse = json.decode(jsonString);
final List<Customer> customers = jsonResponse.map<Customer>((jR) => Customer.fromJson(jR)).toList();
and fromJson in Customer class looks like this
factory Customer.fromJson(Map<String, dynamic> json) => Customer(
id: json["id"] == null ? null : json["id"],
changeDate: json["changeDate"] == null ? null : DateTime.parse(json["changeDate"]),
name: json["name"] == null ? null : json["name"],
);

Extracting an internal JSON object from another JSON object

I'm trying to show a list of tweets using Twitter API, using the fromJson factory.
Each tweet object has an extended_entities object which is an array of media objects.
If you're not familiar with the Twitter API you can see all the different objects here.
Here are the models i created in order to achieve this:
class Tweet {
final String createdAt;
final int id;
final String idStr;
final String text;
final String inReplyToStatusIdStr;
final String inReplyToUserIdStr;
final TweetExtendedEntities tweetExtendedEntities;
Tweet(
{this.createdAt,
this.id,
this.idStr,
this.text,
this.inReplyToStatusIdStr,
this.inReplyToUserIdStr,
this.tweetExtendedEntities});
factory Tweet.fromJson(Map<String, dynamic> json) {
return new Tweet(
createdAt: json['created_at'] as String,
id: json['id'] as int,
idStr: json['id_str'] as String,
text: json['text'] as String,
inReplyToStatusIdStr: json['in_reply_to_status_id_str'] as String,
inReplyToUserIdStr: json['in_reply_to_user_id_str'] as String,
tweetExtendedEntities: json['extended_entities'] as TweetExtendedEntities,
);
}
}
class TweetExtendedEntities {
final List<TweetMedia> tweetMedia;
TweetExtendedEntities({this.tweetMedia});
factory TweetExtendedEntities.fromJson(Map<String, dynamic> json) {
return new TweetExtendedEntities(
tweetMedia: json['media'] as List<TweetMedia>);
}
}
class TweetMedia {
final String mediaType;
final String mediaUrl;
TweetMedia({this.mediaType, this.mediaUrl});
factory TweetMedia.fromJson(Map<String, dynamic> json) {
return new TweetMedia(
mediaType: json['type'] as String,
mediaUrl: json['media_url'] as String,
);
}
}
Before i tried to get the extended_entities object everything was fine and i successfully got the JSON data and parsed it, but when i try to get the media objects using the code above, i get this error:
I/flutter (29538): type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'TweetExtendedEntities' in type cast where
I/flutter (29538): _InternalLinkedHashMap is from dart:collection
I/flutter (29538): String is from dart:core
I/flutter (29538): TweetExtendedEntities is from package:pubg_companion/models/tweet.dart
How can i get nested JSON objects using factory or any other way?
Dart has no idea that your JSON structure will correspond to your object, or how. You can't directly cast the JSON (which is probably a Map<String, dynamic>, but could also be other things) to your objects. #betorcs answer is a start in the right direction but needs a bit more.
This line:
tweetExtendedEntities: json['extended_entities'] as TweetExtendedEntities,
Needs to be
tweetExtendedEntities: TweetExtendedEntities.fromJson['extended_entities'],
And your TweetExtendedEntities method should look more like this:
factory TweetExtendedEntities.fromJson(Map<String, dynamic> json) {
return new TweetExtendedEntities(
tweetMedia: createTweetMediaList(json['media']));
}
static List<TweetMedia> createTweetMediaList(List json) {
if (json == null) return null;
if (json.isEmpty) return [];
return json.map((tweetMediaJson) => TweetMedia.fromJson(tweetMediaJson)).toList();
}
You could also certainly look into json_serializable if your needs start getting more complicated and you want to try to generate some of this code.
Your json parameter is Map<String, dynamic>, dynamic is not TweetExtendedEntities, but it can be cast to Map.
factory Tweet.fromJson(Map<String, dynamic> json) {
return new Tweet(
createdAt: json['created_at'] as String,
id: json['id'] as int,
idStr: json['id_str'] as String,
text: json['text'] as String,
inReplyToStatusIdStr: json['in_reply_to_status_id_str'] as String,
inReplyToUserIdStr: json['in_reply_to_user_id_str'] as String,
tweetExtendedEntities: TweetExtendedEntities.fromJson(json['extended_entities'] as Map),
);
}
In flutter, this error will be thrown when you pass a string without json.decode() to the fromjson factory
eg:
Map bodyJson = json.decode(loginResponse.body);
var login = new LoginResponse.fromJson(bodyJson);
The LoginResponse class
class LoginResponse {
int responseCode;
String message;
String responseObject;
LoginResponse();
factory LoginResponse.fromJson(Map<String, dynamic> json) => _$LoginResponseFromJson(json);
}
JSON and serialization in Flutter