I have following model class.
#JsonSerializable()
class Login {
String? userName;
String? password;
Login({required this.userName, required this.password});
factory Login.fromJson(Map<String, dynamic> json) => _$LoginFromJson(json);
Map<String, dynamic> toJson() => _$LoginToJson(this);
}
Generated part file.
Login _$LoginFromJson(Map<String, dynamic> json) => Login(
userName: json['userName'] as String?,
password: json['password'] as String?,
);
Map<String, dynamic> _$LoginToJson(Login instance) => <String, dynamic>{
'userName': instance.userName,
'password': instance.password,
};
when i try to use it to post to api with follow code
Future<void> loginUser(Login login) async => {
print(login.toJson().toString());
}
Result from the print statement (cause convertion error)
{userName: test, password: password}
Expecting valid json to post is
{
"username": "string",
"password": "string"
}
Error Message
Error: DioError [DioErrorType.other]: Converting object to an encodable object failed: Instance of '_HashSet<String>'
Remove the => from
Future<void> loginUser(Login login) async => {
print(login.toJson().toString());
}
to
Future<void> loginUser(Login login) async {
print(login.toJson().toString());
}
In the first example, it is an arrow function where the brackets { } stand for a set literal since it's right after the arrow, much like in the generated _$LoginToJson function but without describing the types of the set.
In the second example, it is a normal function the brackets { } define the body of the function.
You might be looking to call jsonEncode (import dart:convert;) which takes a value, such as a Map<String, dynamic> and turns it into a String in json format.
Update the print statement as follows:
print(jsonEncode(login.toJson()));
While the generated functions parse the object to a format that is suitable for JSON, it doesn't turn it into an actual String. You'll need to encode it with jsonEncode. Similarly, when processing a JSON response, you'll need to jsonDecode the response body to get a Map<String, dynamic> structure that you can then pass into the generated fromJson functions.
I'm having trouble with jsonEncode even though I've done this multiple times and followed the documentation (Link). I'm not seeing what is wrong.
import 'package:json_annotation/json_annotation.dart';
part 'block.g.dart';
#JsonSerializable()
class Block {
Block(
this.name,
this.required,
this.type,
this.stringValue,
this.checkboxValue,
this.numericValue,
this.minNumericRestraint,
this.maxNumericRestraint
);
String name;
bool required;
String type;
String stringValue;
bool checkboxValue;
double numericValue;
double minNumericRestraint;
double maxNumericRestraint;
factory Block.fromJson(Map<String, dynamic> json) => _$BlockFromJson(json);
Map<String, dynamic> toJson() => _$BlockToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'block.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Block _$BlockFromJson(Map<String, dynamic> json) => Block(
json['name'] as String,
json['required'] as bool,
json['type'] as String,
json['stringValue'] as String,
json['checkboxValue'] as bool,
(json['numericValue'] as num).toDouble(),
(json['minNumericRestraint'] as num).toDouble(),
(json['maxNumericRestraint'] as num).toDouble(),
);
Map<String, dynamic> _$BlockToJson(Block instance) => <String, dynamic>{
'name': instance.name,
'required': instance.required,
'type': instance.type,
'stringValue': instance.stringValue,
'checkboxValue': instance.checkboxValue,
'numericValue': instance.numericValue,
'minNumericRestraint': instance.minNumericRestraint,
'maxNumericRestraint': instance.maxNumericRestraint,
}
Calling jsonEncode(Block(...)); results in following error: Error: Converting object to an encodable object failed: Instance of 'Block'
I reran the build script multiple times, wrote the toJson() method manually and invalidated my Android Studio cache.
I was wondering if anyone could help, please? I'm very new to Flutter/Dart here and I'm trying to parse a nested JSON response, into a model. I've used the "JSON to Dart" generator, which seems to have worked well, except when it is parsing "responses".
I suspect the issue is because the "responses" vary in results - sometimes it could be null, a single array, or multiple.
Running .runtimeType has shown me that it can return null if it's empty, List<dynamic> if there is only one array, and _InternalLinkedHashMap<String, dynamic> when there are multiple.
I have tried many different approaches to try and resolve this and looked through many different StackOverflow answers, but nothing seems to work. The error simply changes with every change I make.
Below is my code and my error.
The error:
_TypeError (type '(dynamic) => Null' is not a subtype of type '(String, dynamic) => void' of 'f')
The code:
class VideoComments {
int id;
String comment;
int uid;
int likes;
bool isLikedByUser;
String posterProfilePic;
String posterUsername;
List<Responses> responses;
VideoComments(
{this.id,
this.comment,
this.uid,
this.likes,
this.isLikedByUser,
this.posterProfilePic,
this.posterUsername,
this.responses});
VideoComments.fromJson(Map<String, dynamic> json) {
print("RESP: ${json['responses'].runtimeType}");
id = json['id'];
comment = json['comment'];
uid = json['uid'];
likes = json['likes'];
isLikedByUser = json['isLikedByUser'];
posterProfilePic = json['poster_profile_pic'];
posterUsername = json['poster_username'];
if (json['responses'] != null) {
List<Responses> responses = [];
json['responses'].forEach((v) {
responses.add(new Responses.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['comment'] = this.comment;
data['uid'] = this.uid;
data['likes'] = this.likes;
data['isLikedByUser'] = this.isLikedByUser;
data['poster_profile_pic'] = this.posterProfilePic;
data['poster_username'] = this.posterUsername;
if (this.responses != null) {
data['responses'] = this.responses.map((v) => v.toJson()).toList();
}
return data;
}
}
class Responses {
int id;
String comment;
int uid;
int likes;
bool isLikedByUser;
Responses({this.id, this.comment, this.uid, this.likes, this.isLikedByUser});
Responses.fromJson(Map<String, dynamic> json) {
id = json['id'];
comment = json['comment'];
uid = json['uid'];
likes = json['likes'];
isLikedByUser = json['isLikedByUser'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['comment'] = this.comment;
data['uid'] = this.uid;
data['likes'] = this.likes;
data['isLikedByUser'] = this.isLikedByUser;
return data;
}
}
Any help is appreciated!
Just for the fun of it - I tried to implement the same thing using json_serializable since I recommended it in my comment. Hopefully this shows you that it is not that complicated - in fact, it is simpler than actually figuring out how to code it your self.
First I started with the blank Flutter project.
Second, I added required dependencies to pubspec.yaml:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
# Added this
json_annotation: ^4.0.1
dev_dependencies:
flutter_test:
sdk: flutter
# and these two...
build_runner: ^2.0.4
json_serializable: ^4.1.3
Next thing, I created a separate file json_demo.dart, and took part of your code. I also added few things:
part instruction that will include generated json mapping file
For each class added .fromJson named constructor
For each class added toJson() method.
Added #JsonSerializable() for each class - this is how the tool knows which class to look at
Added #JsonKey(name: 'poster_profile_pic') and #JsonKey(name: 'poster_username') - since your filed names are different than Json property names (poster_profile_pic vs posterProfilePic), you need to tell the tool how to rename it back and forth.
Made all properties nullable (since I'm using latest version with null-safety)
import 'package:json_annotation/json_annotation.dart';
part 'json_demo.g.dart';
#JsonSerializable()
class VideoComments {
int? id;
String? comment;
int? uid;
int? likes;
bool? isLikedByUser;
#JsonKey(name: 'poster_profile_pic')
String? posterProfilePic;
#JsonKey(name: 'poster_username')
String? posterUsername;
List<Responses>? responses;
VideoComments(
{this.id,
this.comment,
this.uid,
this.likes,
this.isLikedByUser,
this.posterProfilePic,
this.posterUsername,
this.responses});
factory VideoComments.fromJson(Map<String, dynamic> json) => _$VideoCommentsFromJson(json);
Map<String, dynamic> toJson() => _$VideoCommentsToJson(this);
}
#JsonSerializable()
class Responses {
int? id;
String? comment;
int? uid;
int? likes;
bool? isLikedByUser;
Responses({this.id, this.comment, this.uid, this.likes, this.isLikedByUser});
factory Responses.fromJson(Map<String, dynamic> json) => _$ResponsesFromJson(json);
Map<String, dynamic> toJson() => _$ResponsesToJson(this);
}
Now, simply run flutter pub run build_runner build, and you get json_demo.g.dart file generated:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'json_demo.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
VideoComments _$VideoCommentsFromJson(Map<String, dynamic> json) {
return VideoComments(
id: json['id'] as int?,
comment: json['comment'] as String?,
uid: json['uid'] as int?,
likes: json['likes'] as int?,
isLikedByUser: json['isLikedByUser'] as bool?,
posterProfilePic: json['poster_profile_pic'] as String?,
posterUsername: json['poster_username'] as String?,
responses: (json['responses'] as List<dynamic>?)
?.map((e) => Responses.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
Map<String, dynamic> _$VideoCommentsToJson(VideoComments instance) =>
<String, dynamic>{
'id': instance.id,
'comment': instance.comment,
'uid': instance.uid,
'likes': instance.likes,
'isLikedByUser': instance.isLikedByUser,
'poster_profile_pic': instance.posterProfilePic,
'poster_username': instance.posterUsername,
'responses': instance.responses,
};
Responses _$ResponsesFromJson(Map<String, dynamic> json) {
return Responses(
id: json['id'] as int?,
comment: json['comment'] as String?,
uid: json['uid'] as int?,
likes: json['likes'] as int?,
isLikedByUser: json['isLikedByUser'] as bool?,
);
}
Map<String, dynamic> _$ResponsesToJson(Responses instance) => <String, dynamic>{
'id': instance.id,
'comment': instance.comment,
'uid': instance.uid,
'likes': instance.likes,
'isLikedByUser': instance.isLikedByUser,
};
Note how it correctly renamed the json property based on the annotation:
posterProfilePic: json['poster_profile_pic'] as String?,
One more thing to notice - this is how it fixed the problem you had:
responses: (json['responses'] as List<dynamic>?)
?.map((e) => Responses.fromJson(e as Map<String, dynamic>))
.toList(),
From now on, each time you change your class, simple re-run the build script.
As you explore further, you'll see that it can handle enums, it has nice annotations to automatically rename all fields in a class from snake case to kebab case (or whatever it is called). But most importantly - does it in a consistent and tested way. As you add more classes, it will really help you save some time...
And to make your life easier, create user snippet in VS Code (File->Preferences->User snippets):
{
"Json Serializible": {
"prefix": "serial",
"body": [
"factory ${1}.fromJson(Map<String, dynamic> json) => _$${1}FromJson(json);",
"Map<String, dynamic> toJson() => _$${1}ToJson(this);"
]
}
}
Here's how to use it:
Copy the name of your class
start typing 'serial' - this is the shortcut for the code snippet
Select the snippet and paste the class name - it will paste it 3 times where needed.
See - simpler than learning how to manually encode/decode Json....
As #Andrija suggested, that is certainly a good way, if you don't want to use that then go ahead with the vscode extension bendixma.dart-data-class-generator, create your class and keep your variables, just do ctrl + shift + p -> Search data class, hit enter when you find dart data class generator from class properties.
Thank you everyone for your responses. I have no resolved the issue.
I have changed my code (in respect to the issue area) to the following:
if (json['responses'].length > 0) {
json['responses'].forEach((e) => responses.add(Responses.fromJson(e)));
} else if (json['responses'].length == 0 || json['responses'] == null) {
responses = null;
}
and on my web server, I have wrapped the "responses" in another (parent) array.
it is an error related to data type. Please cross the the JSON response and Dart class variable data type. String variable can't be an Int or Double value. Or do one thing change all variable into Dynamic type.
But yes it is not good practice to have dynamic as datatype for all variable.
I'm trying to serialize a simple Dart class. As you can see, I added everything related to the serialization. The name of the file is ParamDescription.dart.
import 'package:json_annotation/json_annotation.dart';
part 'ParamDescription.g.dart';
#JsonSerializable()
class FxParamDescription {
String name;
double minValue;
double maxValue;
double defaultValue;
FxParamDescription(){
}
factory FxParamDescription.fromJson(Map<String, dynamic> json) => _$FxParamDescriptionFromJson(json);
Map<String, dynamic> toJson() => _$FxParamDescriptionFromJson(this);
}
I run flutter pub run build_runner build and it finishes without errors.
When trying to compile, I get this error
^
lib/fx/ParamDescription.dart:24:65: Error: The argument type 'FxParamDescription' can't be assigned to the parameter type 'Map<String, dynamic>'.
- 'FxParamDescription' is from 'package:flutter_app/fx/ParamDescription.dart' ('lib/fx/ParamDescription.dart').
- 'Map' is from 'dart:core'.
Map<String, dynamic> toJson() => _$FxParamDescriptionFromJson(this);
^
lib/fx/ParamDescription.dart:24:36: Error: A value of type 'FxParamDescription' can't be assigned to a variable of type 'Map<String, dynamic>'.
- 'FxParamDescription' is from 'package:flutter_app/fx/ParamDescription.dart' ('lib/fx/ParamDescription.dart').
- 'Map' is from 'dart:core'.
Map<String, dynamic> toJson() => _$FxParamDescriptionFromJson(this);
Change
Map<String, dynamic> toJson() => _$FxParamDescriptionFromJson(this);
To
Map<String, dynamic> toJson() => _$FxParamDescriptionToJson(this);
You're using the wrong generated function.
I have a response
[{details: {macId:789101112}}]
jsonData['details'] will return String {macId:789101112}
Parsing code :
fromJson(Map<String, dynamic> jsonData) => User(
details: UserDetails.fromJson(jsonData['details'] as Map<String, dynamic>),
);
class UserDetails{
final String macId;
UserDetails({this.macId});
static UserDetails fromJson(Map<String, dynamic> json) =>
UserDetails(macId: json['macId'] as String);
}
Try looking at the original source. From your description, it sounds like it looks something like:
[{"details": "{\"macId\": 789101112}"}]
That is, the value of the "details" entry is a string containing JSON source code.
You will need to parse that a second time:
fromJson(Map<String, dynamic> jsonData) =>
User(details: UserDetails.fromJson(jsonDecode(jsonData['details'])));
The reason for this is likely that the place where the you generate JSON data from UserDetails object, it calls jsonEncode on the map, and it should not do that. Just let the UserDetails-to-JSON function return a map, so that that map can be embedded in the larger JSON-like data structure. Then you won't need the second jsonDecode either.