How to Generate Json_Serializable Dart (Flutter) on Inheritance class? - json

Hello i have 2 parent class Student and Room, which two of them inheritance class model StudentModel and RoomModel, i am try to generate json converter using Json_Serializable Library on Model Class, but when i try to generate with build_runner its show error like this :
My Code
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'student.g.dart';
/// * Student & StudentModel
class Student extends Equatable {
final int id;
final String name;
final Room studentRoom;
const Student({this.id = 0, this.name = "", this.studentRoom = const Room()});
#override
List<Object?> get props => [id, name];
}
#JsonSerializable(explicitToJson: true)
class StudentModel extends Student {
const StudentModel({int id = 0, String name = "", RoomModel studentRoom = const RoomModel()});
factory StudentModel.fromJson(Map<String, dynamic> json) => _$StudentModelFromJson(json);
Map<String, dynamic> toJson() => _$StudentModelToJson(this);
}
/// * Room & RoomModel
class Room extends Equatable {
final int id;
final String roomName;
const Room({this.id = 0, this.roomName = ""});
#override
List<Object?> get props => [id, roomName];
}
#JsonSerializable()
class RoomModel extends Room {
const RoomModel({int id = 0, String roomName = ""}) : super(id: id, roomName: roomName);
factory RoomModel.fromJson(Map<String, dynamic> json) => _$RoomModelFromJson(json);
Map<String, dynamic> toJson() => _$RoomModelToJson(this);
}
Response Builder
Could not generate `toJson` code for `studentRoom`.
To support the type `Room` you can:
* Use `JsonConverter`
https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonConverter-class.html
* Use `JsonKey` fields `fromJson` and `toJson`
https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/fromJson.html
https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/toJson.html
package:untitled1/model/student.dart:11:14
╷
11 │ final Room studentRoom;
│ ^^^^^^^^^^^
╵
The problem is builder is search for Room toJson and fromJson not RoomModel, i used jsonKey but it doesn't help, how to fix that problem? Thanks.

use following code
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'student.g.dart';
class Student extends Equatable {
final int id;
final String name;
final Room studentRoom;
const Student({this.id = 0, this.name = "", this.studentRoom = const Room()});
#override
List<Object?> get props => [id, name];
}
#JsonSerializable(explicitToJson: true)
class StudentModel extends Student {
StudentModel({int id = 0, String name = ""});
factory StudentModel.fromJson(Map<String, dynamic> json) =>
_$StudentModelFromJson(json);
Map<String, dynamic> toJson() => _$StudentModelToJson(this);
}
/// * Room & RoomModel
class Room extends Equatable {
final int id;
final String roomName;
const Room({this.id = 0, this.roomName = ""});
#override
List<Object?> get props => [id, roomName];
}
#JsonSerializable()
class RoomModel extends Room {
const RoomModel({int id = 0, String roomName = ""})
: super(id: id, roomName: roomName);
factory RoomModel.fromJson(Map<String, dynamic> json) =>
_$RoomModelFromJson(json);
Map<String, dynamic> toJson() => _$RoomModelToJson(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.

json serialization with inheritance in dart

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

Flutter class in class to json decode

How can I use class in class value?
For example I have a class like below.
import 'dart:convert';
class Product {
final String productId;
final String productName;
final String productImageUrl;
final String productCategory;
Product(
{required this.productId,
required this.productName,
required this.productImageUrl,
required this.productCategory});
factory Product.fromJson(Map<String, dynamic> json) {
return Product(
productId: json['productId'],
productName: json['productName'],
productImageUrl: json['productImageUrl'],
productCategory: json['productCategory'],
);
}
static Map<String, dynamic> toMap(Product product) => {
'productId': product.productId,
'productName': product.productName,
'productImageUrl': product.productImageUrl,
'productCategory': product.productCategory,
};
static String encode(List<Product> products) => json.encode(
products
.map<Map<String, dynamic>>((product) => Product.toMap(product))
.toList(),
);
static List<Product> decode(String products) =>
(json.decode(products) as List<dynamic>)
.map<Product>((item) => Product.fromJson(item))
.toList();
}
And I can use encode and decode like this.
Encode:
final String encodedData = Product.encode([
Product(
productId: "1",
productName: "Americano",
productImageUrl: "assets/americano.jpeg",
productCategory: "Warm"),
Product(
productId: "2",
productName: "Latte",
productImageUrl: "assets/americano.jpeg",
productCategory: "Cold"),
]);
Decode:
List<Product> products;
products = Product.decode(encodedData);
Now here is my question. If I want to use this product class like list in another class. How can I write encode, decode and fromJson methods? Can you help me? And please give me example usages.
import 'dart:convert';
import 'package:orderapp/models/product.dart';
class Bill {
final String billNo;
final String billNumber;
final String tableNo;
final String orderDateTime;
final String orderWaiter;
final List<Product> products;
Bill({
required this.billNo,
required this.billNumber,
required this.tableNo,
required this.orderDateTime,
required this.orderWaiter,
required this.products,
});
}
The data class with JSON decoder and encoder support.
import 'dart:convert';
class Product {
final String productId;
final String productName;
final String productImageUrl;
final String productCategory;
Product({
required this.productId,
required this.productName,
required this.productImageUrl,
required this.productCategory,
});
Map<String, dynamic> toMap() => {
'productId': productId,
'productName': productName,
'productImageUrl': productImageUrl,
'productCategory': productCategory,
};
factory Product.fromMap(Map<String, dynamic> map) => Product(
productId: map['productId'] as String,
productName: map['productName'] as String,
productImageUrl: map['productImageUrl'] as String,
productCategory: map['productCategory'] as String,
);
String toJson() => json.encode(toMap());
factory Product.fromJson(String source) {
return Product.fromMap(json.decode(source) as Map<String, dynamic>);
}
}

How to parse json array into list in dart?

As for the script below, I have two different json for single record and multiple record as a list. I can parse the single data using the Person class. For the json with a list, I'm using ther PersonList class but I'm getting an error because the json key result is not needed anymore. Is there a way to parse the list without changing the Person class? Or should I not use the PersonList class and just create a List<Person>?
I saw this example but its only working if the json is a whole list like this
var jsonResponse = convert.jsonDecode(rawJsonMulti) as List;
return jsonResponse.map((p) => Person.fromJson(p).toList();
Can you show me how to use the above script using my json. Thanks.
import 'dart:convert';
void main() {
String rawJsonMulti = '{"result": [{"name":"Mary","age":30},{"name":"John","age":25}]}';
String rawJsonSingle = '{"result": {"name":"Mary","age":30}}';
// Working for non list json
// final result = json.decode(rawJsonSingle);
// var obj = Person.fromJson(result);
// print(obj.name);
final result = json.decode(rawJsonMulti);
var obj = PersonList.fromJson(result);
print(obj.listOfPerson[0].name);
}
class PersonList {
final List<Person> listOfPerson;
PersonList({this.listOfPerson});
factory PersonList.fromJson(Map<String, dynamic> json) {
var personFromJson = json['result'] as List;
List<Person> lst =
personFromJson.map((i) => Person.fromJson(i)).toList();
return PersonList(listOfPerson: lst);
}
}
class Person {
String name;
int age;
//will only work on result is not a list
Person({this.name, this.age});
factory Person.fromJson(Map<String, dynamic> json) {
return Person(name: json['result']['name'],
age: json['result']['age']);
}
// This will work on json with list but not in single
// factory Person.fromJson(Map<String, dynamic> json) {
// return Person(name: json['name'],
// age: json['age']);
// }
}
If you convert the dynamic to a List<dynamic> you can map each element in the jsonDecoded list.
Example:
import 'dart:convert';
final decodedJson = jsonDecode(rawJsonMulti) as List<dynamic>;
final personList = decodedJson
.map((e) => Person.fromJson(
e as Map<String, dynamic>))
.toList();
return PersonList(listOfPerson: personList);
```
Try take a look at this solution where I have fixed your code:
import 'dart:convert';
void main() {
const rawJsonMulti =
'{"result": [{"name":"Mary","age":30},{"name":"John","age":25}]}';
const rawJsonSingle = '{"result": {"name":"Mary","age":30}}';
final resultMulti = json.decode(rawJsonMulti) as Map<String, dynamic>;
final personListMulti = PersonList.fromJson(resultMulti);
print(personListMulti.listOfPerson[0]); // Mary (age: 30)
print(personListMulti.listOfPerson[1]); // John (age: 25)
final resultSingle = json.decode(rawJsonSingle) as Map<String, dynamic>;
final personListSingle = PersonList.fromJson(resultMulti);
print(personListSingle.listOfPerson[0]); // Mary (age: 30)
}
class PersonList {
final List<Person> listOfPerson;
PersonList({this.listOfPerson});
factory PersonList.fromJson(Map<String, dynamic> json) {
if (json['result'] is List) {
final personsFromJson = json['result'] as List;
return PersonList(listOfPerson: [
...personsFromJson
.cast<Map<String, Object>>()
.map((i) => Person.fromJson(i))
]);
} else {
final personFromJson = json['result'] as Map<String, Object>;
return PersonList(listOfPerson: [Person.fromJson(personFromJson)]);
}
}
}
class Person {
String name;
int age;
Person({this.name, this.age});
factory Person.fromJson(Map<String, dynamic> json) {
return Person(name: json['name'] as String, age: json['age'] as int);
}
#override
String toString() => '$name (age: $age)';
}

Local JSON deserialization in Flutter using Json_Serializable

I'm trying to access a local JSON file in my flutter app but am stuck on how to:
1) Load the json file into my project
2) Parse/decode it using the serialization module I've put together.
The code for the serialisation module Classes:
import 'package:json_annotation/json_annotation.dart';
part 'testJson.g.dart';
#JsonSerializable()
class BaseResponse extends Object with _$BaseResponseSerializerMixin {
final List<Topics> topic;
BaseResponse(
this.topic
);
factory BaseResponse.fromJson(Map<String, dynamic> json) => _$BaseResponseFromJson(json);
}
#JsonSerializable()
class Topics extends Object with _$TopicsSerializerMixin {
final int id;
final String name;
final String color;
final List<Reading> reading;
final int tCompletion;
Topics(
this.id,
this.name,
this.color,
this.reading,
this.tCompletion
);
factory Topics.fromJson(Map<String, dynamic> json) => _$TopicsFromJson(json);
}
#JsonSerializable()
class Reading extends Object with _$ReadingSerializerMixin{
final String name;
final int nLos;
final String description;
final String summary;
final List<LOS> los;
final int rCompletion;
Reading(
this.name,
this.nLos,
this.description,
this.summary,
this.los,
this.rCompletion,
);
factory Reading.fromJson(Map<String, dynamic> json) => _$ReadingFromJson(json);
}
#JsonSerializable()
class LOS extends Object with _$LOSSerializerMixin{
final int id;
final String objective;
final String description;
final String formulae;
LOS(this.id, this.objective, this.description, this.formulae);
factory LOS.fromJson(Map<String, dynamic> json) => _$LOSFromJson(json);
}
#JsonLiteral('jsondata.json')
Map get glossaryData => _$glossaryDataJsonLiteral;
I have built a Model to interpret the response for the Topic Class
class topicModel {
final String topic;
final String color;
final int completion;
topicModel({
this.topic,
this.color,
this.completion
});
topicModel.fromResponse(Topics response)
: topic = response.name,
color = response.color,
completion = response.tCompletion;
}
I'm trying to use future builder to load the local JSON file, but am struggling to return the data and parse it through the classes / model
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(context: context),
builder: (BuildContext context, AsyncSnapshot<List> jsonData) {
if (!jsonData.hasData) {
return Text('not loaded');
}
return Text('loaded');
},
}
Future<List<topicModel>> getData({BuildContext context}) async {
String data = await DefaultAssetBundle.of(context).loadString("assets/data/jsondata.json");
Map jsonMap = json.decode(data);
var testdata = BaseResponse.fromJson(jsonMap);
return topicModel.fromResponse(testdata) as List;
}