DTO generator
What inspired us?
When it comes to writting clean code, Data Transfer Objects (DTOs) help us separating stuff in a proper way. The problem comes when we need to create as many DTOs as models and this turns into a repetitive and painful job.
This is why I've created this extension, which allows us to generate fromJson, toJson, fromDomain and toDomain methods the fastest way, without writting repetitive code over and over again.
Usage
This extension works by creating a template class that will help us know what to generate next. The expected structure is as follows:
class YourClassName{
final String firstVariable;
final int secondVariable;
final CustomType thirdVariable;
}
This example allows us to generate the mentioned methods, resulting in the following code:
class YourClassName {
const YourClassName._({
this.firstVariable,
this.secondVariable,
this.thirdVariable,
});
final String firstVariable;
final int secondVariable;
final CustomType thirdVariable;
static YourClassName fromJson(Map<String, dynamic> json) {
return YourClassName._(
firstVariable: json['firstVariable'],
secondVariable: json['secondVariable'],
thirdVariable: json['thirdVariable'],
);
}
Map<String, dynamic> toJson() {
return {
'firstVariable': firstVariable,
'secondVariable': secondVariable,
'thirdVariable': thirdVariable,
};
}
static YourClassName fromDomain(YourClassName domain) {
return YourClassName._(
firstVariable: domain.firstVariable,
secondVariable: domain.secondVariable,
thirdVariable: domain.thirdVariable,
);
}
YourClassName toDomain() {
return YourClassName(
firstVariable: this.firstVariable,
secondVariable: this.secondVariable,
thirdVariable: this.thirdVariable,
);
}
}
To activate generation you must place the cursor in the class declaration line, and press cntrl (cmd) + .
If you want to add some customization to the resulting class, we provide you 5 handy annotations:
fromJson: Can be used in class properties only. This can be used if you want to provide a method or constructor that you want us to use to wrap the value that comes from the json. Like this:
class YourClassName {
@fromJson(customMethod)
final CustomType thirdVariable;
}
this generates (besides of other methods) the fromJson method that will wrap the value returned by the json with customMethod:
static YourClassName fromJson(Map<String, dynamic> json) {
return YourClassName._(
thirdVariable: customMethod(json['thirdVariable']),
);
}
toJson: works similar to fromJson. The difference is that you can use it to wrap the value returned in the toJson method. It will generate from this:
class YourClassName {
@toJson(customMethod)
final CustomType thirdVariable;
}
To this:
Map<String, dynamic> toJson(){
return {thirdVariable: customMethod(thirdVariable),};
}
toDomain: this allows us to wrap the returned values for properties in the toDomain method.
class YourClassName {
@toDomain(customMethod)
final CustomType thirdVariable;
}
Generates:
YourClassName toDomain() {
return YourClassName(
thirdVariable: customMethod(this.thirdVariable),
);
}
domainName: This annotation can be used on either properties or the class itself.
If you use it on the class, this will tell us what the name of the domain class is. For example when you're creating an UserDto, you can tell us that the domain name is the User class
@domainName(User)
class UserDto {
final String name;
}
Will generate
static UserDto fromDomain(User domain){
return UserDto._(
name:domain.name,
);
}
User toDomain(){
return User(name:this.name,);
}
Note that, thanks to this annotation, we realized that you wanted to use a class calles User as domain class
The other case for using this annotation is on properties. This will tell us what the name of the corresponding domain property is. For example:
@domainName(User)
class UserDto {
final String name;
}
class User {
User(this.firstName);
final String firstName;
}
In this case, the User name is called a different way (firstName) than the way we called it in out DTO (name). No worries, use the @domainName annotation on the DTO property and you're done:
@domainName(User)
class UserDto {
@domainName(firstName)
final String name;
}
This generates:
static UserDto fromDomain(User domain){
return UserDto._(
name:domain.firstName,
);
}
User toDomain(){
return User(firstName:this.name,);
}
jsonName: this was thought only to be used as a property annotation. It allows you to tell us when the name of the json property is different than the class' property name.
class UserDto {
@jsonName(jsonNameDifferent)
final String name;
final String surname;
}
This will generate:
static UserDto fromJson(Map<String, dynamic> json){
return UserDto._(name:json['jsonNameDifferent'], surname:json['surname'],);
}
Map<String, dynamic> toJson(){
return {'jsonNameDifferent': name, 'surname': surname,};
}
You can use these annotations together by writting them on the property/class you want to apply them to.
import 'package:streams/domain.dart';
import 'package:streams/user.dart';
@domainName(User)
class UserDto {
@domainName(firstName)
@fromJson(somemethodthatdoesntmatter)
@jsonName(jsonNameDifferent)
final String name;
@toJson(somemethodthatdoesntmatter)
@toDomain(Surname)
final String surname;
}
Let's describe this:
The DTO's domain class name is User
name property is called firstName in the domain class (User)
name property will use somemethodthatdoesntmatter for being converted from json to String
name property is called jsonNameDifferent in the json that's comming
surname will use somemethodthatdoesntmatter to be converted from String to json.
surname will be wrapped in an instance of Surname class when transformed into domain.
Generating this will result on:
import 'package:streams/domain.dart';
import 'package:streams/user.dart';
class UserDto {
const UserDto._({
this.name,
this.surname,
});
final String name;
final String surname;
static UserDto fromJson(Map<String, dynamic> json) {
return UserDto._(
name: somemethodthatdoesntmatter(json['jsonNameDifferent']),
surname: json['surname'],
);
}
Map<String, dynamic> toJson() {
return {
'jsonNameDifferent': name,
surname: somemethodthatdoesntmatter(surname),
};
}
static UserDto fromDomain(User domain) {
return UserDto._(
name: domain.firstName,
surname: domain.surname,
);
}
User toDomain() {
return User(
firstName: this.name,
surname: Surname(this.surname),
);
}
}
Support
I highly encourage you to create a pull request for this extension if you want to improve it. Your help is always welcome. Also, you can open issues if there's any trouble.