Json serialization does not include embedded member despite the use of #JsonSerializable(explicitToJson: true).
The member capacity is there, but failureState is not part of the json.
What am I missing?
part 'batteryFailureState.g.dart';
/// The live battery data
#JsonSerializable(explicitToJson: true)
class BatteryFailureState {
// Byte 0
bool? cellVoltageIsTooHighLevel1;
bool? cellVoltageIsTooHighLevel2;
/// Default constructor as required by jaon serializer
BatteryFailureState();
/// The factory constructor fro JSON
factory BatteryFailureState.fromJson(Map<String, dynamic> json) =>
_$BatteryFailureStateFromJson(json);
/// `toJson` serialisation
Map<String, dynamic> toJson() => _$BatteryFailureStateToJson(this);
}
import 'batteryFailureState.dart';
part 'batteryData.g.dart';
/// The live battery data
#JsonSerializable(explicitToJson: true)
class BatteryData {
/// Current capacity in A.h
double? capacity; // A.h
/// Battery failure state
final failureState = BatteryFailureState();
/// Default constructor as required by jaon serializer
BatteryData();
/// The factory constructor fro JSON
factory BatteryData.fromJson(Map<String, dynamic> json) =>
_$BatteryDataFromJson(json);
/// `toJson` serialisation
Map<String, dynamic> toJson() => _$BatteryDataToJson(this);
}
It seems that the code generation is getting tripped up by a field that is final and also can't be passed into the default constructor.
For example if you refactor so that failureState can be passed into the constructor:
import 'package:json_annotation/json_annotation.dart';
import 'batteryFailureState.dart';
part 'batteryData.g.dart';
/// The live battery data
#JsonSerializable(explicitToJson: true)
class BatteryData {
/// Current capacity in A.h
double? capacity; // A.h
/// Battery failure state
final BatteryFailureState? failureState;
/// Default constructor as required by jaon serializer
BatteryData({this.capacity, this.failureState});
/// The factory constructor fro JSON
factory BatteryData.fromJson(Map<String, dynamic> json) =>
_$BatteryDataFromJson(json);
/// `toJson` serialisation
Map<String, dynamic> toJson() => _$BatteryDataToJson(this);
}
The generated output is:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'batteryData.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BatteryData _$BatteryDataFromJson(Map<String, dynamic> json) => BatteryData(
capacity: (json['capacity'] as num?)?.toDouble(),
failureState: json['failureState'] == null
? null
: BatteryFailureState.fromJson(
json['failureState'] as Map<String, dynamic>),
);
Map<String, dynamic> _$BatteryDataToJson(BatteryData instance) =>
<String, dynamic>{
'capacity': instance.capacity,
'failureState': instance.failureState?.toJson(),
};
Notice that _$BatteryDataFromJson passes in a value for failureState at the constructor.
However, if you refactor instead to remove final, but don't accept it as a constructor parameter like so:
import 'package:json_annotation/json_annotation.dart';
import 'batteryFailureState.dart';
part 'batteryData.g.dart';
/// The live battery data
#JsonSerializable(explicitToJson: true)
class BatteryData {
/// Current capacity in A.h
double? capacity; // A.h
/// Battery failure state
BatteryFailureState? failureState = BatteryFailureState();
/// Default constructor as required by jaon serializer
BatteryData();
/// The factory constructor fro JSON
factory BatteryData.fromJson(Map<String, dynamic> json) =>
_$BatteryDataFromJson(json);
/// `toJson` serialisation
Map<String, dynamic> toJson() => _$BatteryDataToJson(this);
}
Now the generated output looks like this:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'batteryData.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BatteryData _$BatteryDataFromJson(Map<String, dynamic> json) => BatteryData()
..capacity = (json['capacity'] as num?)?.toDouble()
..failureState = json['failureState'] == null
? null
: BatteryFailureState.fromJson(
json['failureState'] as Map<String, dynamic>);
Map<String, dynamic> _$BatteryDataToJson(BatteryData instance) =>
<String, dynamic>{
'capacity': instance.capacity,
'failureState': instance.failureState?.toJson(),
};
Notice that instead of failureState being passed into the constructor it instead sets the value using a cascade ..faulureState =.
In your original implementation neither of these approaches would work in the generated code and so it appears to be ignoring the field altogether.
Related
I am using json annotation package to generate the json serialization boilerplate code. I have Base class
#JsonSerializable()
class UIWidget {
double? size = 50;
List<UIWidget> children = List.empty(growable: true);
factory UIWidget.fromJson(Map<String, dynamic> json) => _$UIWidgetFromJson(json);
Map<String, dynamic> toJson() => _$UIWidgetToJson(this);
}
I have several subclasses and one such is given below.
#JsonSerializable(explicitToJson: true)
class UIGridView extends UIWidget {
int scrollDirection = Axis.vertical.index;
String _showAxis = "Vertical";
int crossAxisCount = 3;
double mainAxisSpacing = 0.0;
double crossAxisSpacing = 0.0;
double childAspectRatio = 1.0;
factory UIGridView.fromJson(Map<String, dynamic> json) => _$UIGridViewFromJson(json);
Map<String, dynamic> toJson() => _$UIGridViewToJson(this);
}
If you notice, the UIWidget class has children property, which can contain any of the sub classes. The problem arise when I tried to generate the code. The fromJson method is generated as follows
..children = (json['children'] as List<dynamic>)
.map((e) => UIWidget.fromJson(e as Map<String, dynamic>))
.toList();
However, I needed to call the subclass fromjson and create instance of subclass. Is there a way to do this?
Where's your super() constructor? If you're extending UIWidget, you need to pass your params from the extending class to the parent class, and codegen will work as expected.
// Base class
#JsonSerializable()
class ParentClass {
final String? someString;
const ParentClass({
this.someString,
});
factory ParentClass.fromJson(Map<String, dynamic> json) =>
_$ParentClassFromJson(json);
Map<String, String> toJson() => Map.from(_$ParentClassToJson(this));
}
// Child class extending base
#JsonSerializable()
class ChildClass extends ParentClass {
final List<int> exampleList;
const ChildClass({
required this.exampleList,
String? someString,
}) : super(someString: someString);
factory ChildClass.fromJson(Map<String, dynamic> json) =>
_$ChildClassFromJson(json);
Map<String, String> toJson() =>
Map.from(_$ChildClassToJson(this));
}
I want to define a simple class model UserResponse in Flutter 2.0.5 and build a fromJson method attached to this class to create an instance easily after receiving the data from the backend in json format.
class UserResponse {
String name;
UserResponse ({
required this.name,
});
UserResponse.fromJson(Map<String, dynamic> json) {
name= json['name'].toString();
}
}
The dart compiler however throws an error here:
dart(not_initialized_non_nullable_instance_field)
Furthermore:
Non-nullable instance field 'name' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
If I know that I will only call the fromJson method if I have all the required data, how should I create the new Instance in this method? I don't want to change the name field in the class to late.
Use a factory constructor.
class UserResponse {
final String name;
UserResponse({
required this.name,
});
factory UserResponse.fromJson(Map<String, dynamic> json) {
return UserResponse(name: json['name'].toString());
}
}
For null-safety. You need to be check null right way. And front-end need handle server don't return this key, we need mock data and sure app can't crash.
class UserResponse {
UserResponse({
this.name,
});
final String? name;
factory UserResponse.fromRawJson(String str) =>
UserResponse.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory UserResponse.fromJson(Map<String, dynamic> json) => UserResponse(
name: json["name"] == null ? null : json["name"].toString(),
);
Map<String, dynamic> toJson() => {
"name": name == null ? null : name,
};
}
According to this very similar question there are mainly to ways:
Use an initializer list for the method
Use a factory method
Thus,
UserResponse.fromJson(Map<String, dynamic> json) :
name= json['name'] as String;
or
factory UserResponse.fromJson(Map<String, dynamic> json) {
return UserResponse(
name: json['name'] as String,
);
}
I'm trying to figure out how I can decode the following JSON in Flutter.
https://covid.ourworldindata.org/data/owid-covid-data.json
I tried the following structure, but it doesn't work.
#JsonSerializable()
class StatisticsResponse {
Map<String, Country> data;
//List<Country> data;
StatisticsResponse({this.data});
factory StatisticsResponse.fromJson(Map<String, dynamic> json) =>
_$StatisticsResponseFromJson(json);
}
#JsonSerializable()
class Country {
String continent;
String location;
int population;
//Map<String, Data> data;
List<Data> data;
Country({this.continent, this.location, this.population, this.data
});
factory Country.fromJson(Map<String, dynamic> json) =>
_$CountryFromJson(json);
}
Use Map to convert dynamic to String. Directly assigning List to List will throw an error. You have create getters yourself.
Consider this example:
(jsonDecode(response.body)["data"] as List).map((e) => e as Map<String, dynamic>)?.toList();
Now assign to an instance of the custom class you have made.
I have worked on decoding/encoding JSONs in my Flutter/Dart app. The decoding works just fine, but I have a very nasty problem when encoding my objects to JSON.
These are nested objects. Every one of them has its toJson and fromJson methods, in order to ensure that jsonEncode and Decode works. A small snippet of my work:
class App {
factory App.fromJson(Map<String, dynamic> json) => App(
langPref: json["langPref"],
langFallb: json["langFallb"],
users: List.of(json["users"]).map((i) => i).toList(),
);
String langPref;
String langFallb;
List<User> users;
/// JSON-Export
Map<String, dynamic> toJson() => {
"langPref": langPref,
"langFallb": langFallb,
"users": jsonEncode(users),
};
}
and the nested class:
class User {
int userid;
// actually there's more, including more nested objects
/// JSON-Import
factory User.fromJson(Map<String, dynamic> json) {
return User(
userid: int.parse(json["userid"]),
);
}
/// JSON-Export
Map<String, dynamic> toJson() {
return {
"userid": this.userid,
};
}
}
The problem is: When I encode the top level class "App", it correctly calls the toJson() method of the nested class. However, the corresponding JSON should read like this:
{
"langPref":"de-DE",
"langFallb":"en-GB",
"users":
[
{
"userid": 1
// and so on
It does, however, look like this:
{
"langPref":"de-DE",
"langFallb":"en-GB",
"users":"[{\"userid\":1
// and so on
So, the jsonEncode somehow introduces additional double quotes, which even makes sense somehow. It produces a String, and inside the JSON a string should be encoded .... But I guuess I'm just doing something wrong and missing something obvious .... How can I tell jsonEncode to accept the result of the operation, instead of encoding it as a string?
Can somebody help me?
This problem rises because you use jsonEncode() which return string object
you must use jsonDecode() that return a Map<String, dynamic>
and your App class will be like following
class App {
factory App.fromJson(Map<String, dynamic> json) => App(
langPref: json["langPref"],
langFallb: json["langFallb"],
users: List.of(json["users"]).map((i) => i).toList(),
);
String langPref;
String langFallb;
List<User> users;
/// JSON-Export
Map<String, dynamic> toJson() => {
"langPref": langPref,
"langFallb": langFallb,
"users": jsonEDecode(users),
};
}
Update
2nd method is to remove jsonEncode() without use jsonDecode()
3rd method use tojson() method in user class like following code
"users": users.map((user) => user.tojson()).toList(),
4th method the best method
use json_serializable library with json_annotation library to generate json serialization for annotated classes,
Flutter team approve this method as the best and the official one as described in Official Flutter Documentation.
app.dart
import 'package:json_annotation/json_annotation.dart';
part 'app.g.dart';
#JsonSerializable()
class App{
String langPref;
String langFallb;
List<User> users;
App({this.langPref, this.langFallb, this.users});
factory App.fromJson(Map<String, dynamic> json) => _$AppFromJson(json);
Map<String, dynamic> toJson() => _$AppToJson(this);
}
user.dart
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
#JsonSerializable()
class User{
int userId;
User({this.userId});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Great .... I will answer my own question. :-)
Further experiments lead to the following conclusion:
class App {
factory App.fromJson(Map<String, dynamic> json) => App(
langPref: json["langPref"],
langFallb: json["langFallb"],
users: List.of(json["users"]).map((i) => i).toList(),
);
String langPref;
String langFallb;
List<User> users;
/// JSON-Export
Map<String, dynamic> toJson() => {
"langPref": langPref,
"langFallb": langFallb,
"users": users,
};
}
New is only the last line .... I can directly pass the users list. It's not necessary to use jsonEncode for all of the nested objects and lists and maps etc.
Sorry for stealing the time of the readers, but maybe this answer will help others.
I have a class:
import 'package:google_maps_flutter/google_maps_flutter.dart';
class Place {
Place({
this.address,
this.coordinates,
});
final String address;
final LatLng coordinates;
}
LatLng is a class of google_maps_flutter. How can I make my Place class serializable using json_annotation and json_serializable?
Thank you very much!
You can explicitly specify which methods of LatLng should be used for serialization using JsonKey annotation:
#JsonSerializable()
class Place {
Place({
this.address,
this.coordinates,
});
final String address;
#JsonKey(fromJson: LatLng.fromJson, toJson: jsonEncode)
final LatLng coordinates;
}
Put this code in your model
to get the information from JSON response simply do that after your request
final place = placeFromJson(response.body);
to get address => = place.address
to get coordinates => place.coordinates.lng , place.coordinates.lat
=============================================
import 'dart:convert';
Place placeFromJson(String str) => Place.fromJson(json.decode(str));
String placeToJson(Place data) => json.encode(data.toJson());
class Place {
String address;
Coordinates coordinates;
Place({
this.address,
this.coordinates,
});
factory Place.fromJson(Map<String, dynamic> json) => Place(
address: json["address"],
coordinates: Coordinates.fromJson(json["coordinates"]),
);
Map<String, dynamic> toJson() => {
"address": address,
"coordinates": coordinates.toJson(),
};
}
class Coordinates {
String lat;
String lng;
Coordinates({
this.lat,
this.lng,
});
factory Coordinates.fromJson(Map<String, dynamic> json) => Coordinates(
lat: json["lat"],
lng: json["lng"],
);
Map<String, dynamic> toJson() => {
"lat": lat,
"lng": lng,
};
}
I didn't find a super easy solution for this. I ended up, writing a custom toJson/fromJson for the LatLng property. I've put it statically onto my model, but you could also create a global function, if you need to reuse it.
Take care, that in my example LatLng is nullable.
import 'dart:collection';
import 'dart:convert';
import 'package:latlong2/latlong.dart';
part 'challenge_model.g.dart';
#JsonSerializable()
class ChallengeModel with _$ChallengeModel {
ChallengeModel({
required this.startPosition,
});
#override
#JsonKey(fromJson: latLngFromJson, toJson: latLngToJson)
final LatLng? startPosition;
/// Create [ChallengeModel] from a json representation
static fromJson(Map<String, dynamic> json) => _$ChallengeModelFromJson(json);
/// Json representation
#override
Map<String, dynamic> toJson() => _$ChallengeModelToJson(this);
static String latLngToJson(LatLng? latLng) =>
jsonEncode(latLng != null ? {'latitude': latLng.latitude, 'longitude': latLng.longitude} : null);
static LatLng? latLngFromJson(String jsonString) {
final LinkedHashMap<String, dynamic>? jsonMap = jsonDecode(jsonString);
return jsonMap != null ? LatLng(jsonMap['latitude'], jsonMap['longitude']) : null;
}
}