I have a problem with JSON convert in child class in dart. For example:
class Person {
String firstName;
String lastName;
Person({
this.firstName,
this.lastName
})
factory Person.fromJson(Map<String, dynamic> json){
return Person()
..firstName = json['firstName']
..lastName = json['firstName'];
}
}
class User extends Person {
String token;
User({
this.token
});
factory User.fromJson(Map<String, dynamic> json){
return User()
..token = json['token']
// The problem is here. I need dublicate code to fill parent
// class properties as first & last name of person:
..firstName = json['firstName']
..lastName = json['firstName'];
}
}
My question is how to avoid code dublication in this case?
Please help.
You could create a method on your base class that takes in the Map<String, dynamic> and populates the fields on itself, then call that from the subclass. However, I'd really recommend using something like json_serializable to generate this code for you instead. It adds an additional step (you need to run pub run build_runner build or pub run build_runner watch to have the code re-generated) but it avoids you having to hand-write all of this.
Related
I'm trying to have a base Freezed interface which my app entity interfaces can extend so I can call the freezed functions on the interfaces. I've started the process here which seems to be working so far:
abstract class IUserRegistrationEntity<T> extends FreezedClass<T> {
String get nickName;
String get email;
String get confirmEmail;
String get password;
String get confirmPassword;
}
abstract class FreezedClass<T> {
T get copyWith;
Map<String, dynamic> toJson();
}
freezed class:
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:vepo/domain/user_registration/i_user_registration_entity.dart';
part 'user_registration_entity.freezed.dart';
part 'user_registration_entity.g.dart';
#freezed
abstract class UserRegistrationEntity with _$UserRegistrationEntity {
#Implements.fromString(
'IUserRegistrationEntity<\$UserRegistrationEntityCopyWith<IUserRegistrationEntity>>')
const factory UserRegistrationEntity(
{String nickName,
String email,
String confirmEmail,
String password,
String confirmPassword}) = _IUserRegistrationEntity;
factory UserRegistrationEntity.fromJson(Map<String, dynamic> json) =>
_$UserRegistrationEntityFromJson(json);
}
But now I need to add the fromJson factory constructor to the interface. I think this may be what I'm looking for although I can't really tell how to implement it in my code:
T deserialize<T extends JsonSerializable>(
String json,
T factory(Map<String, dynamic> data),
) {
return factory(jsonDecode(json) as Map<String, dynamic>);
}
You an then call it with:
var myValue = deserialize(jsonString, (x) => MyClass.fromJson(x));
Any help adding the fromJson to my freezed interface would be appreciated.
I've found a way to get the same benefits of programming to an interface or "abstraction" with freezed objects, while still getting to call those freezed functions:
#freezed
abstract class Person with _$Person {
#With(BasicPersonMixin)
const factory Person.basicPerson(
{int? id, String? firstName, String? lastName}) = BasicPerson;
#With(FancyPersonMixin)
const factory Person.fancyPerson({String? firstName, required String extraPropMiddleName, String? lastName}) =
FancyPerson;
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
const Person._();
void functionThatEveryPersonShares() {
print('I am a person');
}
String greet() {
return 'override me with a mixin or abstract class';
}
}
mixin FancyPersonMixin {
String get extraPropMiddleName {
return 'my default middle name is John`;
}
String greet() {
return 'Salutations!';
}
void specialisedFunctionThatOnlyIHave() {
print('My middle name is $extraPropMiddleName');
}
}
mixin BasicPersonMixin {
String greet() {
return 'Hi.';
}
}
Now we have 2 concrete classes: BasicPerson, and FancyPerson which are both a Person. Now we can program to Person throughout the app, and still call .copyWith and .fromJson and so on and so forth. The different types of Person can vary independently from each other by using mixins and still be used as a Person type. Works with generics etc (from docs - #With.fromString('AdministrativeArea<House>')) but I have kept the example simple for this question to most simply show the benefits. You can also make Person extend another base class.
I've found another way to let you be a bit more abstract than my other answer. Say you're in a highly abstract super-class, so you don't want to work with objects as specific as Person. You want to work with "a base freezed object"; just cast your type to dynamic in brackets and go ahead and use copyWith freely. Sure, it's not typesafe, but it's a worthy option if it allows you to do something in a super-class rather than in every sub-class.
mixin LocalSaveMixin<TEntity extends LocalSaveMixin<TEntity>> on Entity {
LocalRepository<TEntity> get $repository;
Ref? get provider;
TEntity $localFetch() {
return ($repository.$localFetch() as dynamic).copyWith(provider: provider)
as TEntity;
}
TEntity $localSave() {
return $repository.$localSave(entity: this as TEntity);
}
}
How can I abstract that a methods has optional parameters?
abstract class CopyWith<T>{
T copyWith({}); // Error : Expected an identifier.
}
If I add an identifier like {test} it works and subclasses can have additional arguments
What I want to achieve?
I have a complex state manager, I make some abstraction , the following code is a minimal code, show my problem
import 'dart:collection';
abstract class CopyWith<T> {
T copyWith(OPTIONAL_NAMED_ARGUMENTS);
}
abstract class Manager<K, V extends CopyWith> {
final _map = HashMap<K, V>();
add(K key,V value){
_map[key] = value;
}
void copyWith(K key,OPTIONAL_NAMED_ARGUMENTS) {
assert(key != null);
if (_map.containsKey(key)) {
_map[key].copyWith(OPTIONAL_NAMED_ARGUMENTS);
}
}
}
class User implements CopyWith {
final int id;
final String name;
User({this.id, this.name});
User copyWith({int id, String name}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
);
}
}
class UserManager extends Manager<int, User> {}
void main() {
final userManager = UserManager();
userManager.add(1,User(1,'test'));
userManager.copyWith(1,{test:'test2'})
}
As some one who has faced this issue in my library, I would say the only way is to not put a copyWith in your base class.
Why? Because you should only make a function polymorphic when there IS actually a shared calling convention and behavior. In your example, The way that these two classes perform copyWith is just different. It is, and should be, an error to send a name to Manager.copyWith, because Manager does not have a name to begin with. If you encounter a name inside a Manager.copyWith, that means there is some serious error in your code.
Also, if you actually try to invoke copyWith, as a responsible programmer, you will probably check if you are allowed to pass a name, which is,
if (someObj is User) {
someObj.copyWith(key, name: name);
} else if (someObj is Manager) {
throw IllegalStateError('You should not pass a name to a Manager! What am I supposed to do with the name now?');
}
There, you have already done type checking, so no need to make copyWith polymorphic.
However, some common behaviors can be made polymorphic, like updateKey. You can make Keyable as an interface, and Keyable updateKey(Key key) as an abstract method, and delegate to a non-polymorphic copyWith inside each subclasses.
i have no experience with extended classes
so don't be shocked... that's what I got:
the 'basic class' I want to extend in my models
to avoid repeat fromJson/toJson every 2 lines
import 'dart:convert';
class BaseModel {
Map<String, dynamic> json2Map(String json) => jsonDecode(json);
String map2Json(Map<String, dynamic> map) => jsonEncode(map);
json2List(String jsonList) {
List<Map<String, dynamic>> _list = [];
jsonDecode(jsonList).forEach((_json) => _list.add(jsonDecode(_json)));
return _list;
}
mapList2Json(List<Map<String,dynamic>> list) {
List<String> jsonList= [];
list.forEach((_map) => jsonList.add(map2Json(_map)));
return jsonEncode(jsonList);
}
}
and here is one of the class that extends this:
import 'package:bloc_hub/models/base_model.dart';
class Info extends BaseModel {
final String name;
final String company;
Info({this.name,this.company});
factory Info.fromMap(Map<String, dynamic> json) => new Info(
name: json['name'],
company: json['company'],
);
Map<String, dynamic> toMap() {
var map = new Map<String, dynamic>();
map['name'] = name;
map['company'] = company;
return map;
}
}
(I'm in a streambuilder and client.info is a json)
then... when I try to call 'json2map'
which is from the extended class...
Info info = Info.fromMap(json2Map(client.info));
i get this:
[dart] The method 'json2Map' isn't defined for the class 'ListPage'. [undefined_method]
what did I get wrong?
if I wasn't clear don't refrain to ask me anything
thank you for your help
[edit: bonus question
how a mixin is different from what I'm doing?]
json2Map is an instance method of BaseModel, so in order to call it you must use an instance of BaseModel or a class that extends it (like Info), like:
var b = new BaseModel();
b.json2Map(something);
The error message says you're calling it from ListPage, so the method is not found.
Alternatively, you could make the methods static and call it like BaseModel.json2Map(...) (without an instance).
There are some good explanations about mixins here (with Python examples, but the concepts are the same for Dart). I guess in your example it would make more sense to have a mixin with JSON related functions, since you could use them in other kind of objects.
There is nameof operator in C#, it allows to get property name at compile time:
var name = nameof(User.email);
Console.WriteLine(name);
//Prints: email
It is not possible to use reflection in flutter and I do not want to hardcode names of properties i.e. to be used for querying SQLite tables. Is there any workaround?
***Currently I'm using built_value library.
For the archives, I guess, this isn't possible as Dart doesn't store the names of variables after compiling, and as you mentioned, Flutter doesn't support reflection.
But you can still hardcode responsibly by grouping your properties as part of the object that they belong to, like with JSON:
class User {
final String email;
final String name;
const User({required this.email, required this.name});
Map toJson() => {
"email": email,
"name": name,
};
}
Instead of remembering to type out "email" and "name" whenever you use User, just call User.toJson(). Then, when you want to rename your variables, you can use your IDE's "rename all", or just skim over your User class to quickly change all of the names without missing any.
I'm currently monitoring the progress on the dart:mirrors package, which offers some neat reflective properties and methods, though, I hadn't found a simple way to just get the name of a symbol like nameof() does.
Example:
import 'dart:mirrors';
class User {
final String email;
final String name;
const User({required this.email, required this.name});
}
void main() {
reflectClass(User).declarations.forEach((key, value) {
print(value.simpleName);
});
}
Output:
Symbol("email")
Symbol("name")
Symbol("User")
These are of type Symbol.
More here: https://api.dart.dev/stable/2.4.0/dart-mirrors/dart-mirrors-library.html
So, until they develop a nameof, I've created an extension on symbol:
extension SymbolExtensions on Symbol {
String get nameof =>
RegExp(r'"(.*?)"').firstMatch(toString())!.group(1).toString();
}
So, you could do:
print(reflectClass(User)
.declarations[#email)]!
.simpleName
.nameof);
Output:
email
It's a start. Dart is still growing.
You can use code generation.
The basic idea is to create a nameof annotation class and mark parts of your code with it. You also need to create a code generator that handles your annotated code. Look at the json_serializable package for an example and create your own code generator.
If you do not want to create your own generator, use a ready-made package nameof: https://pub.dev/packages/nameof
Short how-to with this package.
Mark your class with nameof annotation.
#nameof
class Car {
final double price;
final double weigth;
final int year;
final String model;
Car(this.price, this.weigth, this.year, this.model);
Car.sedan(double price, double weigth, int year)
: this(price, weigth, year, 'Sedan');
}
Run the code generator.
flutter pub run build_runner build
Then use the generated class, which will look something like this.
/// Container for names of elements belonging to the [Car] class
abstract class NameofCar {
static const String className = 'Car';
static const String constructor = '';
static const String constructorSedan = 'sedan';
static const String fieldPrice = 'price';
static const String fieldWeigth = 'weigth';
static const String fieldYear = 'year';
static const String fieldModel = 'model';
}
You can implement your own nameOf function:
String? nameOf(dynamic o) {
if (o == null) return "null";
try {
if (o is List) {
var first = o.length > 0 ? o[0] : null;
if (first != null) {
var elementType = nameOf(first)!;
Log.debug("nameOf: List<$elementType>");
if (!isMinified(elementType))
return "List<$elementType>";
}
} else {
Function? getTypeName = o.getTypeName;
if (getTypeName != null) return getTypeName();
}
} catch (e) {
Log.debug("ignored nameOf error: $e, falling back to o.runtimeType: ${o.runtimeType}");
}
return o.runtimeType.toString();
}
bool isMinified(String type) => type.startsWith("minified:");
I have an Identity class in dart which looks (simplified) like this
class Identity {
final String phoneNumber;
Identity({#required this.phoneNumber});
Identity.fromJson(Map<String, dynamic> json)
: phoneNumber = json['phoneNumber'];
}
I will use this class to send an http POST to my identity service; this service will return a json which I want to map to ActiveIdentity, which looks like this (also simplified).
class ActiveIdentity extends Identity {
final String id;
ActiveIdentity.fromJson(Map<String, dynamic> json)
: id = json['id'];
}
Now, is there a way to extend or call fromJson in Identity so that I can "extend" this method? Ideally when calling fromJson in ActiveIdentity I should receive a new ActiveIdentity instance with all properties inititalized (phoneNumber and id) but on ActiveIdentity I only want to deal with id.
I also tried to think about this in terms of mixins, but failed miserably... any idea on how would be the best way to achieve this?
Thanks!
I think the following should solve your problem:
class Identity {
final String phoneNumber;
Identity({#required this.phoneNumber});
Identity.fromJson(Map<String, dynamic> json)
: phoneNumber = json['phoneNumber'];
}
class ActiveIdentity extends Identity {
final String id;
ActiveIdentity.fromJson(Map<String, dynamic> json)
: id = json['id'],
super.fromJson(json) {
print('$phoneNumber $id');
}
}
Try having a look at the Dart documentation of constructors for clarification on this topic.