`JsonSerializable` with base classes - dart

Having the following classes:
#JsonSerializable()
class Person {
final String name;
Person({required this.name});
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
Map<String, dynamic> toJson() => _$PersonToJson(this);
}
#JsonSerializable()
class Taxable {
final String taxNumber;
Person({required this.taxNumber});
factory Taxable(Map<String, dynamic> json) => _$TaxableFromJson(json);
Map<String, dynamic> toJson() => _$TaxableToJson(this);
}
What's the best approach to create a TaxablePerson that both extends Taxable and Person and supports JSON serialisation?
Making TaxablePerson implements Taxable, Person requires to again declare all the inherited fields.

There isn't really a better way to do that with just dart and json_serializable. you could work with freezed unions instead of inheritance to achieve a similar result with more code generation and less manual copying.

Related

How to <T extend BaseClass> where BaseClass has a factory function?

Following is a simple class that provides a few helper functions for reading and writing data.
class BaseDAO<T> {
final String _modelName;
static late final StoreRef<int, Map<String, Object?>> _store;
BaseDAO(this._modelName) {
_store = intMapStoreFactory.store(_modelName);
}
Future<Database> get _db async => await AppDatabase().database;
Future<void> create(T object) async {
await _store.add(await _db, object.toJSON()); //The method 'toJSON' can't be unconditionally invoked because the receiver can be 'null'.
}
}
Now the issue with this is that the generic type T doesn't have toJSON function. I tried fixing this by writing an abstract class.
abstract class BaseModel {
Map<String, dynamic> toJSON();
factory BaseModel.fromJson(Map<String, dynamic> json);
}
and extending T with BaseModel. This presents all kinds of issues one of them being that I am unable to write an abstract class.
Any solution will be greatly appreciated.
In your case T object is empty. You need to extend it to some object which provides a method toMap().
Example:
class BaseDAO<T extends BaseModel> {
final String _modelName;
static late final StoreRef<int, Map<String, Object?>> _store;
BaseDAO(this._modelName) {
_store = intMapStoreFactory.store(_modelName);
}
Future<Database> get _db async => await AppDatabase().database;
Future<void> create(T object) async {
await _store.add(await _db, object.toMap()); // <- Dart see that this object extends to BaseModel and has a method `toMap()`,
}
}
abstract class BaseModel {
Map<String, dynamic> toMap();
}
class User extends BaseModel {
User({this.name});
final String? name;
#override
Map<String, dynamic> toMap() => {'name': name};
}
Future<void> create() async {
final user = User(name: 'Superman');
final base = BaseDAO('ModelName');
await base.create(user); // <- The user object will be added as `Map`.
}

How do I create an abstract factory?

Is it possible to somehow create an abstract factory method? Maybe what I'm trying to do is possible to implement differently?
abstract class ApiModel {
// Error: A function body must be provided.
factory ApiModel.fromJson(Map<String, dynamic> json);
}
class User extends ApiModel {
final int id;
final String name;
User({required this.id, required this.name});
#override
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
);
}
}
class ApiResponse<Model extends ApiModel> {
final List<Model> results;
ApiResponse({required this.results});
factory ApiResponse.fromJson(Map<String, dynamic> json) {
return ApiResponse(results: (json['results'] as List).map((item) => Model.fromJson(item)).toList());
}
}
I solved it like this:
factory ApiResponse.fromJson(Map<String, dynamic> json, Model Function(dynamic) mapper) {
return ApiResponse(
info: Info.fromJson(json['info']),
results: (json['results'] as List).map(mapper).toList(),
);
}

Creating a default .toString() method

I have a lot of JSON models in my dart project that I would like to have a string representation of. I know that I can override the .toString() method per class, but that feels like a lot of work to write basically the same thing a bunch of times. Is there a way that I can create a mixin or extention to override toString? Or is it better to use code generation? (I found this package, but it seems like it isn't maintained any more)
The string representation I am looking for is just a list of all parameters, for example:
#JsonSerializable()
class User {
UserOver(
this.userId,
this.name,
);
int userId;
/// The full name of the user.
String name;
factory UserOverview.fromJson(Map<String, dynamic> json) => _$UserOverviewFromJson(json);
Map<String, dynamic> toJson() => _$UserOverviewToJson(this);
}
should have the following string representation:
User(userId: 1, name: "Name")
You answered your own question there buddy. Just create a base class and outsource it. It simple.
For example:
#JsonSerializable()
class User extends BaseModel {
User({
required this.userId,
required this.name,
});
int userId;
/// The full name of the user.
String name;
factory User.fromJson(Map<String, dynamic> json) =>
_$UserOverviewFromJson(json);
#override
Map<String, dynamic> toJson() => _$UserOverviewToJson(this);
}
Create a base model class
abstract class BaseModel {
Map<String, dynamic> toJson();
#override
String toString() {
return toJson().toString();
}
}
Don't forget these
User _$UserOverviewFromJson(Map<String, dynamic> json) => User(name: json['name'] as String, userId: json['userId'] as int);
Map<String, dynamic> _$UserOverviewToJson(User instance) => <String, dynamic>{
'name': instance.name,
'userId': instance.userId,
};
Now to use:
final cool = User(userId: 1, name: "Name");
print(cool.toString()); //{name: Name, userId: 1}

Dart: how to override a generic method?

Consider my following base class:
abstract class IChat<Message extends IMessage> {
String get getId;
List<T> getUsers<T extends IUser>();
}
Now my extended Model looks like this:
class Chat extends IChat<ChatMessage> {
String id;
List<ChatUser> members;
Chat({this.chat, this.members});
#override
String get getId => id;
#override
List<T> getUsers<T extends IUser>() => members;
}
List<T> getUsers<T extends IUser>() => members; throws an error because I return List<ChatUser> instead of List<T>. How do I override the method to make this work? My ChatUser class obviously also extends IUser.
Edit
#jamesdlin gave one solution: #override List<T> getUsers<T extends IUser>() => members.cast<T>(). It is possible without type casting?
First consider what you actually want your classes to do.
It seems like the subclass has a specific type of IUser that it always wants to return, but a generic function takes its type argument per invocation. So, the type should probably be a property of the class, not of the invocation.
Consider:
abstract class IChat<Message extends IMessage> {
String get getId;
List<IUser> getUsers();
}
class Chat extends IChat<ChatMessage> {
String id;
List<ChatUser> members;
Chat({this.chat, this.members});
#override
String get getId => id;
#override
List<ChatUser> getUsers() => members;
}
Would that satisfy your needs?
As stylistic comments, your code is very C#-like, and doesn't look like Dart. I'd probably declare the classes as:
abstract class Chat<M extends Message> {
String get id;
List<User> get users;
}
class SomeChat extends Chat<ChatMessage> {
#override
final String id;
#override
List<ChatUser> users;
// ...
SomeChat({this.id, List<CharUser> members, ...}) : users = members;
// ...
}

How to create a base factory and override it on child class in Flutter

So I have a class like Question like bellow:
#JsonSerializable()
class Question {
String id;
String content;
Question({this.id, this.content});
factory Question.fromJson(Map<String, dynamic> json) =>
_$QuestionFromJson(json);
Map<String, dynamic> toJson() => _$QuestionToJson(this);
}
Please keep in mind that those _$QuestionFromJson and _$QuestionToJson comes from this library https://pub.dev/packages/json_serializable
Say I have many class like that which have a fromJson factory and a toJson method. I want to create a base class that contains those 2 method. A base model is easy for toJson as bellow:
abstract class BaseModel {
Map<String, dynamic> toJson();
}
But what about the factory method, I have no idea how to declare them then override it simply like:
#override
factory Question.fromJson(Map<String, dynamic> json) =>
_$QuestionFromJson(json);
EDIT:
My idea of using this is because I want to create a converter utility that I only need to pass in the class of the result like Converter.listFromJson<MyClass>(jsonString). For now, the helper is:
static List<T> listFromJson<T>(jsonString, Function mappingFunction) {
return myJsonMap.map(mappingFunction).cast<T>().toList();
}
so I have to map each item by passing the map function every time I use this helper method:
Converter.listFromJson<Question>(
jsonMap, (item) => Question.fromJson(item));
There'are a few more class that needs to be convert to the list like this. I want to reuse the method without the (item) => Question.fromJson(item) method part. That's why I want to create a base class that have the factory fromJson method so that I can use it in the converter
return myJsonMap.map((item) => BaseModel.fromJson(item)).cast<T>().toList();
then I just simply call
Converter.listFromJson<Question>(jsonMap);
Thank you for your time.
i don't know if i got you correctly, that's what i understood from your question
abstract class BaseModel{
BaseModel();
BaseModel.fromJson(Map<String,dynamic> json);
}
class Question extends BaseModel{
final String id;
final String name;
Question({this.id,this.name}): super();
#override
factory Question.fromJson(Map<String, dynamic> json) {
return Question(
id: json['id'],
name: json['name']
);
}
}
void main(){
Map<String,dynamic> json = {'id': "dsajdas",'name': 'test'};
Question question = Question.fromJson(json);
print('question: ${question.id}');
}
That was my approach but you can't do such a thing. There is a workaround by declaring .fromJson(json) in a variable. Look at my sample codes, hope you can get an idea.
class Categories {
final String id;
String name;
String image;
Categories({this.id, this.name, this.image});
Categories.fromJson(dynamic json)
: id = json['id'],
name = json['name'],
image = json['image'];
}
class CategoriesModel extends AppModel<Categories> {
List<Categories> list = [];
Function fromJson = (dynamic json) => Categories.fromJson(json);
}
class AppModel<T> {
List<T> list = [];
Function fromJson;
List<T> getList() {
if (this.list.isNotEmpty) return this.list;
List<dynamic> list = GetStorage().read('tableName');
list.forEach((data) {
this.list.add(fromJson(data));
});
return this.list;
}
}

Resources