Extracting an internal JSON object from another JSON object - json

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

Related

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

How do I write a Dart model class which parses this JSON?

I'm working on a dart / flutter app which I need to parse JSON from firebase database. I've done this successfully multiple times in my code except I can't get the following JSON to parse. Can someone help?
Here is the JSON response I'm working with:
{favorites: {-MH1vwWPqXHSXkTe_MxC: [Green Bay Packers, Washington Football Team]}, firstname: Mark, email: mbilner#gmail.com, lastname: bilner, username: mbil}
Here is my model class:
class userProf {
String email;
String firstname;
String lastname;
String username;
List<String> favorites;
userProf({this.email, this.firstname, this.lastname, this.username, this.favorites});
factory userProf.fromJson(Map<dynamic, dynamic> parsedJson) {
var favsFromJson = parsedJson['favorites'];
List<String> favsList = new List<String>.from(favsFromJson);
return userProf(
email: parsedJson['email'],
firstname: parsedJson['firstname'],
lastname: parsedJson['lastname'],
username: parsedJson['username'],
favorites: favsList
);
}
}
The current code gives me the following error:
"Unhandled Exception: type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Iterable'"
This is how it is structured in the db:
you can convert any json data for any language in this https://app.quicktype.io/ web please try :)
I think List 'from' method needs a Iterable value but 'favorites' value is 'Map'.
List<String> favsList = new List<String>.from(favsFromJson);
If you just use Map values only without key, you can like below.
List<String> favsList = [];
favsFromJson.values((value) => favList += value);

json_serializable fails to deserialize

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)

Flutter parsing json with DateTime from Golang RFC3339: FormatException: Invalid date format

When trying to read json files generated with golangs json package in Dart / Flutter I noticed that parsing dates produce an error:
FormatException: Invalid date format
An example is the following json generated on the Go server:
{
...
"dateCreated": "2018-09-29T19:51:57.4139787-07:00",
...
}
I am using the code generation approach for json (de-)serialization to avoid writing all the boiler plate code. The json_serializable package is a standard package available for this purpose. So my code looks like the following:
#JsonSerializable()
class MyObj {
DateTime dateCreated;
MyObj( this.dateCreated);
factory MyObj.fromJson(Map<String, dynamic> json) => _$MyObjFromJson(json);
Map<String, dynamic> toJson() => _$MyObjToJson(this);
}
Because the documentation doesn't cover this sufficiently it took me a day of researching the flutter sources and trial & error of different things to solve it. So may as well share it.
Golang by default encodes the time.Time in RFC3339 when serializing to Json (like in the given example). Flutter explicitly supports RFC3339, so why doesn't it work? The answer is a small difference in how the seconds fraction part is supported. While Golang produces a precision of 7 digits Dart only supports up to 6 digits and does not gracefully handle violations. So if the example is corrected to only have 6 digits of precision it will parse just fine in Dart:
{
...
"dateCreated": "2018-09-29T19:51:57.413978-07:00",
...
}
In order to solve this in a generic way you have two options: 1. to truncate the additional precision from the string, or 2. implement your own parsing. Let's assume we extend the DateTime class and create your own CustomDateTime. The new class has the parse method overridden to remove all excess after 6 digits before handing it to the parent class' parse method.
Now we can use the CustomDateTime in our Dart classes. For example:
#JsonSerializable()
class MyObj {
CustomDateTime dateCreated;
MyObj( this.dateCreated);
factory MyObj.fromJson(Map<String, dynamic> json) => _$MyObjFromJson(json);
Map<String, dynamic> toJson() => _$MyObjToJson(this);
}
But of course now the code generation is broken and we get the following error:
Error running JsonSerializableGenerator
Could not generate 'toJson' code for 'dateCreated'.
None of the provided 'TypeHelper' instances support the defined type.
Luckily the json_annotation package now has an easy solution for us - The JsonConverter. Here is how to use it in our example:
First define a converter that explains to the code generator how to convert our CustomDateTime type:
class CustomDateTimeConverter implements JsonConverter<CustomDateTime, String> {
const CustomDateTimeConverter();
#override
CustomDateTime fromJson(String json) =>
json == null ? null : CustomDateTime.parse(json);
#override
String toJson(CustomDateTime object) => object.toIso8601String();
}
Second we just annotate this converter to every class that is using our CustomDateTime data type:
#JsonSerializable()
#CustomDateTimeConverter()
class MyObj {
CustomDateTime dateCreated;
MyObj( this.dateCreated);
factory MyObj.fromJson(Map<String, dynamic> json) => _$MyObjFromJson(json);
Map<String, dynamic> toJson() => _$MyObjToJson(this);
}
This satisfies the code generator and Voila! We can read json with RFC3339 timestamps that come from golang time.Time.
I havd the same problem. I found a very simple solution. We can use a custom converter with JsonConverter. For more explanation you can use my article.
import 'package:json_annotation/json_annotation.dart';
class CustomDateTimeConverter implements JsonConverter<DateTime, String> {
const CustomDateTimeConverter();
#override
DateTime fromJson(String json) {
if (json.contains(".")) {
json = json.substring(0, json.length - 1);
}
return DateTime.parse(json);
}
#override
String toJson(DateTime json) => json.toIso8601String();
}
import 'package:json_annotation/json_annotation.dart';
import 'package:my_app/shared/helpers/custom_datetime.dart';
part 'publication_document.g.dart';
#JsonSerializable()
#CustomDateTimeConverter()
class PublicationDocument {
final int id;
final int publicationId;
final DateTime publicationDate;
final DateTime createTime;
final DateTime updateTime;
final bool isFree;
PublicationDocument({
this.id,
this.publicationId,
this.publicationDate,
this.createTime,
this.updateTime,
this.isFree,
});
factory PublicationDocument.fromJson(Map<String, dynamic> json) =>
_$PublicationDocumentFromJson(json);
Map<String, dynamic> toJson() => _$PublicationDocumentToJson(this);
}