Creating a default .toString() method - dart

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}

Related

Dart parse json map with json_serializable, but with the key

Suppose I have the following json (structured as <String key, Map value>):
{
'A1': {'name': 'a'},
'B2': {'name': 'b'}
}
and I want to parse it to this class (notice that I use the key as the id for that user), using the fromJson factory method, which accepts two arguments:
Class User {
final String id;
final String name;
factory User.fromJson(Map<String, dynamic> json, String key) {
return User(
id: key,
name: json['name'],
);
}
}
Can I achieve it using json_serializable ?
The json Map expected by this factory method is just the values of the top-level JSON object you're parsing.
All you need to do is parse the JSON, extract all keys, then pass the values to the factory method.
Something like this:
import 'dart:convert';
const json = '''
{
"A1": {"name": "a"},
"B2": {"name": "b"}
}
''';
class User {
final String id;
final String name;
User({required this.id, required this.name});
factory User.fromJson(Map<String, dynamic> json, String key) {
return User(
id: key,
name: json['name'],
);
}
#override
String toString() => 'User(id=$id, name=$name)';
}
main() {
final map = jsonDecode(json);
map.forEach((id, userJson) {
final user = User.fromJson(userJson, id);
print(user);
});
}
Prints:
User(id=A1, name=a)
User(id=B2, name=b)
Now, to use json_serializable, just annotate it and replace your implementation with the generated one...
#JsonSerializable()
class User {
...
factory User.fromJson(Map<String, dynamic> json, String key) =>
// pass in only the relevant json Map!
_$UserFromJson(json[key]);
}

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`.
}

`JsonSerializable` with base classes

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.

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

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