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