json serialization with inheritance in dart - json

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

Related

Flutter ~ Abstract constructor workaround for Json Serialization

Background: I would like to create an abstract JsonSerializable class that allows its extenders to have an toJsonString-method and a fromJsonString constructor, so that I can bundle all calls to jsonDecode and jsonEncode in that one class, while also enforcing that the extenders implement the required toJson-metrhod and fromJson-constructor.
Thus far, I have managed to do half of that like this:
json_serializable.dart
import 'dart:convert';
abstract class JsonSerializable {
Map<String, dynamic> toJson();
String toJsonString() => jsonEncode(toJson());
}
readable_uuid.dart
import 'dart:convert';
import 'package:uuid/uuid.dart';
import 'json_serializable.dart';
class ReadableUuid extends JsonSerializable {
static const Uuid uuidGenerator = Uuid();
String uuid = uuidGenerator.v4();
static const String _uuidFieldName = "uuid";
ReadableUuid();
ReadableUuid.fromJson(Map<String, dynamic> json)
: uuid = json[_uuidFieldName];
ReadableUuid.fromJsonString(String jsonString)
: this.fromJson(jsonDecode(jsonString));
#override
Map<String, dynamic> toJson() => {
_uuidFieldName: uuid,
};
[...]
}
character_id.dart
import 'dart:convert';
import 'package:ceal_chronicler_f/utils/readable_uuid.dart';
import '../utils/json_serializable.dart';
class CharacterId extends JsonSerializable {
static const String _idFieldName = "id";
var id = ReadableUuid();
CharacterId();
CharacterId.fromJson(Map<String, dynamic> json)
: id = ReadableUuid.fromJson(json[_idFieldName]);
CharacterId.fromJsonString(String jsonString)
: this.fromJson(jsonDecode(jsonString));
#override
Map<String, dynamic> toJson() => {
_idFieldName: id,
};
[...]
}
This is already not bad, but there's still some duplication here. Both my concrete classes still need to have a fromJsonString constructor that is effectively identical, and they both still need to import dart:convert because of that. Also, I can't enforce that they have a fromJson constructor like that.
Now, what I would like to have is something like this:
json_serializable.dart
import 'dart:convert';
abstract class JsonSerializable {
abstract JsonSerializable.fromJson(Map<String, dynamic> json);
JsonSerializable.fromJsonString(String jsonString)
: this.fromJson(jsonDecode(jsonString));
Map<String, dynamic> toJson();
String toJsonString() => jsonEncode(toJson());
}
readable_uuid.dart
import 'package:uuid/uuid.dart';
import 'json_serializable.dart';
class ReadableUuid extends JsonSerializable {
static const Uuid uuidGenerator = Uuid();
String uuid = uuidGenerator.v4();
static const String _uuidFieldName = "uuid";
ReadableUuid();
#override
ReadableUuid.fromJson(Map<String, dynamic> json)
: uuid = json[_uuidFieldName];
#override
Map<String, dynamic> toJson() => {
_uuidFieldName: uuid,
};
[...]
}
character_id.dart
import 'package:ceal_chronicler_f/utils/readable_uuid.dart';
import '../utils/json_serializable.dart';
class CharacterId extends JsonSerializable {
static const String _idFieldName = "id";
var id = ReadableUuid();
CharacterId();
#override
CharacterId.fromJson(Map<String, dynamic> json)
: id = ReadableUuid.fromJson(json[_idFieldName]);
#override
Map<String, dynamic> toJson() => {
_idFieldName: id,
};
[...]
}
Now, I know this does not work because constructors are not part of the interface in Dart (and it's made more complicated by Flutter disallowing Runtime Reflection in favor of Tree Shaking). My question is, can anyone think of a workaround here that achieves as many of the following goals as possible:
No duplicate code in concrete classes
All dependencies on dart:convert are concentrated inside JsonSerializable
The fromJson constructor and toJson method are enforced to exist in concrete classes
I have now come up with a "solution" that addresses about 1.75 of the above requirements, but has some drawbacks.
First, here's the "solution":
json_serializable.dart
import 'dart:convert';
abstract class JsonSerializable {
JsonSerializable();
JsonSerializable.fromJsonString(String jsonString)
: this.fromJson(jsonDecode(jsonString));
JsonSerializable.fromJson(Map<String, dynamic> jsonMap){
decodeJson(jsonMap);
}
decodeJson(Map<String, dynamic> jsonMap);
Map<String, dynamic> toJson();
String toJsonString() => jsonEncode(toJson());
}
readable_uuid.dart
import 'package:uuid/uuid.dart';
import 'json_serializable.dart';
class ReadableUuid extends JsonSerializable {
static const Uuid uuidGenerator = Uuid();
String uuid = uuidGenerator.v4();
static const String _uuidFieldName = "uuid";
ReadableUuid();
ReadableUuid.fromJsonString(String jsonString)
: super.fromJsonString(jsonString);
ReadableUuid.fromJson(Map<String, dynamic> jsonMap) : super.fromJson(jsonMap);
#override
decodeJson(Map<String, dynamic> jsonMap) {
uuid = jsonMap[_uuidFieldName];
}
#override
Map<String, dynamic> toJson() => {
_uuidFieldName: uuid,
};
[...]
}
character_id.dart
import 'package:ceal_chronicler_f/utils/readable_uuid.dart';
import '../utils/json_serializable.dart';
class CharacterId extends JsonSerializable {
static const String _idFieldName = "id";
var id = ReadableUuid();
CharacterId();
CharacterId.fromJsonString(String jsonString)
: super.fromJsonString(jsonString);
CharacterId.fromJson(Map<String, dynamic> jsonMap) : super.fromJson(jsonMap);
#override
decodeJson(Map<String, dynamic> jsonMap) {
id = ReadableUuid.fromJson(jsonMap[_idFieldName]);
}
#override
Map<String, dynamic> toJson() => {
_idFieldName: id,
};
[...]
}
Pros
All dependencies on dart:convert are concentrated inside JsonSerializable
JsonSerializable enforces the creation of toJson amd decodeJson methods
Little boilerplate code
Cons
Only works if all fields of concrete classes are either initialized with default values, late or nullable
The conveyor-belt constructors need to be implemented every time even though they always look the same, which feels like a bit of duplication
That would be the best solution that I can come up at the moment. If anyone has a better solution, please feel free to share it here.

Getting type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>'error in JSON

Im sure you know the problem above. Im wondering how I can solve it. I understand that my data is in form of a list but inside the data class I used map. I don't really understand how I should change it to work, basically I just followed the flutter.dev documentation
So if you are wondering what I did
I basically parsed my data with json_serializable. In testing with test data all worked fine.
My data:
My model contains a title, image & a nested class called modelData.
`import 'package:json_annotation/json_annotation.dart';
import 'modelData.dart';
part 'Modell.g.dart';
#JsonSerializable(explicitToJson: true)
class Modell {
final String title;
final String image;
final ModelData modelData;
Modell(this.title, this.image, this.modelData);
factory Modell.fromJson(Map<String, dynamic> json)
=> _$ModellFromJson(json);
Map<String, dynamic> toJson() => _$ModellToJson(this);}
`
import 'package:json_annotation/json_annotation.dart';
part 'modelData.g.dart';
#JsonSerializable()
class ModelData {
final String title;
ModelData(this.title);
factory ModelData.fromJson(Map<String, dynamic> json)
=> _$ModelDataFromJson(json);
Map<String, dynamic> toJson() => _$ModelDataToJson(this);
}
Im consuming the data with this code:
var modelle = const[];
Future loadDataList() async {
String content = await rootBundle.loadString("assets/ddddddd.json");
List collection = json.decode(content);
List<Modell> _modelle = collection.map((json) => Modell.fromJson(json)).toList();
setState(() {
modelle = _modelle;
});
}
void initState() {
loadDataList();
super.initState();
& if needed here a part of my Data:
[
{
"title":" Alfa Romeo ",
"image":" AlfaRomeo.png ",
"modelData":[
{
"title":" 4C ",
"variantenData":[
]
},
I hope I wrote clear & detailed enough. If Im missing something, sry
-----Update-----
I tested a bit & found out that with simply adding List I no longer get the error, will test further to see whether the solution really works as intended
import 'package:json_annotation/json_annotation.dart';
import 'modelData.dart';
part 'Modell.g.dart';
#JsonSerializable(explicitToJson: true)
class Modell {
final String title;
final String image;
List <ModelData> modelData; //adding List
Modell(this.title, this.image, this.modelData);
factory Modell.fromJson(Map<String, dynamic> json)
=> _$ModellFromJson(json);
Map<String, dynamic> toJson() => _$ModellToJson(this);
}
You have parsed the data and all but the class named Modell should contain a list of ModelData and not a single ModelData. Please change the type to a list of ModelData and it should work hopefully.
class Modell {
final String title;
final String image;
final List<ModelData> modelDataList;
Modell(this.title, this.image, this.modelDataList);
factory Modell.fromJson(Map<String, dynamic> json)
=> _$ModellFromJson(json);
Map<String, dynamic> toJson() => _$ModellToJson(this);}```
In this part:
var modelle = const[];
Future loadDataList() async {
String content = await rootBundle.loadString("assets/ddddddd.json");
List collection = json.decode(content);
List<Modell> _modelle = collection.map((json) => Modell.fromJson(json)).toList();
setState(() {
modelle = _modelle;
});
}
void initState() {
loadDataList();
super.initState();
More specific here:
List collection = json.decode(content);
If you go to Flutter Example will see that collection will return a Map<String, dynamic>.
Try change to:
Map<String, dynamic> collection = json.decode(content);

How to decode nested JSON List of Objects in Dart/Flutter

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.

JSON Encode/Decode List with json_serializable

I'm using the packages: https://pub.dev/packages/json_annotation, https://pub.dev/packages/json_serializable
I want to encode/decode a list of objects in my flutter app. The class which is in the list is as follows:
#JsonSerializable()
class Trial {
final ProtocolType type;
List<List<double>> data;
#JsonKey()
final DateTime date;
Trial({this.type, this.data, this.date});
Map<String, dynamic> toMap() {
return {'type': this.type, 'data': this.data, 'date': this.date};
}
factory Trial.fromJson(Map<String, dynamic> json) => _$TrialFromJson(json);
Map<String, dynamic> toJson() => _$TrialToJson(this);
}
I want to decode/encode a list of these items, and I couldn't figure out how else to do it so I made a new class:
#JsonSerializable()
class TrialList {
final List<Trial> trials;
TrialList({this.trials});
factory TrialList.fromJson(List json) => _$TrialListFromJson(json);
List toJson() => _$TrialListToJson(this);
}
Notice the List toJson() in this new class. It seems Json_serializable only wants me to decode/encode maps, is there any way to support lists?
Solution:
#JsonSerializable()
class TrialList {
final List<Trial> trials;
TrialList({this.trials});
factory TrialList.fromJson(json) => _$TrialListFromJson({'trials': json});
List toJson() => _$TrialListToJson(this)['trials'];
}
credit to Null from the /r/FlutterDev discord

Flutter/Dart JSON and serialization of an existing library class

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