custom ==
This commit is contained in:
parent
cf0a7eb652
commit
21dc10de5a
11 changed files with 155 additions and 19 deletions
|
@ -23,7 +23,7 @@ dependencies:
|
||||||
angel_serialize: ^2.0.0
|
angel_serialize: ^2.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
angel_serialize_generator: ^2.0.0
|
angel_serialize_generator: ^2.0.0
|
||||||
build_runner: ^0.7.0
|
build_runner: ^0.8.0
|
||||||
```
|
```
|
||||||
|
|
||||||
With the recent updates to `package:build_runner`, you can build models in
|
With the recent updates to `package:build_runner`, you can build models in
|
||||||
|
@ -41,7 +41,7 @@ class to have it serialized, and a serializable model class's name should also s
|
||||||
with a leading underscore.
|
with a leading underscore.
|
||||||
|
|
||||||
In addition, you may consider using an `abstract` class to ensure immutability
|
In addition, you may consider using an `abstract` class to ensure immutability
|
||||||
of models.s
|
of models.
|
||||||
|
|
||||||
Rather you writing the public class, `angel_serialize` does it for you. This means that the main class can have
|
Rather you writing the public class, `angel_serialize` does it for you. This means that the main class can have
|
||||||
its constructors automatically generated, in addition into serialization functions.
|
its constructors automatically generated, in addition into serialization functions.
|
||||||
|
@ -85,13 +85,15 @@ myFunction() {
|
||||||
var map = BookSerializer.toMap(warAndPeace);
|
var map = BookSerializer.toMap(warAndPeace);
|
||||||
|
|
||||||
// Also deserialize from Maps
|
// Also deserialize from Maps
|
||||||
var book = BookSerialize.fromMap(map);
|
var book = BookSerializer.fromMap(map);
|
||||||
print(book.title); // 'War and Peace'
|
print(book.title); // 'War and Peace'
|
||||||
|
|
||||||
// For compatibility with `JSON.encode`, a `toJson` method
|
// For compatibility with `JSON.encode`, a `toJson` method
|
||||||
// is included that forwards to `BookSerializer.toMap`:
|
// is included that forwards to `BookSerializer.toMap`:
|
||||||
expect(book.toJson(), map);
|
expect(book.toJson(), map);
|
||||||
|
|
||||||
|
// Generated classes act as value types, and thus can be compared.
|
||||||
|
expect(BookSerializer.fromMap(map), equals(warAndPeace));
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# 2.0.6
|
# 2.0.6
|
||||||
* Support for using `abstract` to create immutable model classes.
|
* Support for using `abstract` to create immutable model classes.
|
||||||
* Add support for custom constructor parameters.
|
* Add support for custom constructor parameters.
|
||||||
|
* Closed [#21](https://github.com/angel-dart/serialize/issues/21) - better naming
|
||||||
|
of `Map` types.
|
||||||
|
* Added overridden `==` operators.
|
||||||
|
|
||||||
# 2.0.5
|
# 2.0.5
|
||||||
* Deserialization now supports un-serialized `DateTime`.
|
* Deserialization now supports un-serialized `DateTime`.
|
||||||
|
|
|
@ -42,6 +42,7 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
|
||||||
|
|
||||||
generateConstructor(ctx, clazz, file);
|
generateConstructor(ctx, clazz, file);
|
||||||
generateCopyWithMethod(ctx, clazz, file);
|
generateCopyWithMethod(ctx, clazz, file);
|
||||||
|
generateEqualsOperator(ctx, clazz, file);
|
||||||
|
|
||||||
// Generate toJson() method if necessary
|
// Generate toJson() method if necessary
|
||||||
var serializers = annotation.peek('serializers')?.listValue ?? [];
|
var serializers = annotation.peek('serializers')?.listValue ?? [];
|
||||||
|
@ -91,7 +92,7 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
|
||||||
method
|
method
|
||||||
..name = 'copyWith'
|
..name = 'copyWith'
|
||||||
..returns = ctx.modelClassType;
|
..returns = ctx.modelClassType;
|
||||||
|
|
||||||
// Add all `super` params
|
// Add all `super` params
|
||||||
if (ctx.constructorParameters.isNotEmpty) {
|
if (ctx.constructorParameters.isNotEmpty) {
|
||||||
for (var param in ctx.constructorParameters) {
|
for (var param in ctx.constructorParameters) {
|
||||||
|
@ -126,4 +127,50 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
|
||||||
method.body = new Code(buf.toString());
|
method.body = new Code(buf.toString());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String generateEquality(DartType type, [bool nullable = false]) {
|
||||||
|
//if (type is! InterfaceType) return 'const DefaultEquality()';
|
||||||
|
var it = type as InterfaceType;
|
||||||
|
if (const TypeChecker.fromRuntime(List).isAssignableFromType(type)) {
|
||||||
|
if (it.typeParameters.length == 1) {
|
||||||
|
var eq = generateEquality(it.typeArguments[0]);
|
||||||
|
return 'const ListEquality<${it.typeArguments[0].name}>($eq)';
|
||||||
|
} else
|
||||||
|
return 'const ListEquality<${it.typeArguments[0].name}>()';
|
||||||
|
} else if (const TypeChecker.fromRuntime(Map).isAssignableFromType(type)) {
|
||||||
|
if (it.typeParameters.length == 2) {
|
||||||
|
var keq = generateEquality(it.typeArguments[0]),
|
||||||
|
veq = generateEquality(it.typeArguments[1]);
|
||||||
|
return 'const MapEquality<${it.typeArguments[0].name}, ${it.typeArguments[1].name}>(keys: $keq, values: $veq)';
|
||||||
|
} else
|
||||||
|
return 'const MapEquality()<${it.typeArguments[0].name}, ${it.typeArguments[1].name}>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullable ? null : 'const DefaultEquality<${type.name}>()';
|
||||||
|
}
|
||||||
|
|
||||||
|
static String Function(String, String) generateComparator(DartType type) {
|
||||||
|
if (type is! InterfaceType) return (a, b) => '$a == $b';
|
||||||
|
var eq = generateEquality(type, true);
|
||||||
|
if (eq == null) return (a, b) => '$a == $b';
|
||||||
|
return (a, b) => '$eq.equals($a, $b)';
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateEqualsOperator(
|
||||||
|
BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) {
|
||||||
|
clazz.methods.add(new Method((method) {
|
||||||
|
method
|
||||||
|
..name = 'operator =='
|
||||||
|
..returns = new Reference('bool')
|
||||||
|
..requiredParameters.add(new Parameter((b) => b.name = 'other'));
|
||||||
|
|
||||||
|
var buf = ['other is ${ctx.originalClassName}'];
|
||||||
|
|
||||||
|
buf.addAll(ctx.fields.map((f) {
|
||||||
|
return generateComparator(f.type)('other.${f.name}', f.name);
|
||||||
|
}));
|
||||||
|
|
||||||
|
method.body = new Code('return ${buf.join('&&')};');
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ class TypeScriptDefinitionBuilder implements Builder {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> compileToTypeScriptType(String fieldName, InterfaceType type,
|
Future<String> compileToTypeScriptType(BuildContext ctx, String fieldName,
|
||||||
List<CodeBuffer> ext, BuildStep buildStep) async {
|
InterfaceType type, List<CodeBuffer> ext, BuildStep buildStep) async {
|
||||||
String typeScriptType = 'any';
|
String typeScriptType = 'any';
|
||||||
|
|
||||||
var types = const {
|
var types = const {
|
||||||
|
@ -30,16 +30,16 @@ class TypeScriptDefinitionBuilder implements Builder {
|
||||||
|
|
||||||
if (isListModelType(type)) {
|
if (isListModelType(type)) {
|
||||||
var arg = await compileToTypeScriptType(
|
var arg = await compileToTypeScriptType(
|
||||||
fieldName, type.typeArguments[0], ext, buildStep);
|
ctx, fieldName, type.typeArguments[0], ext, buildStep);
|
||||||
typeScriptType = '$arg[]';
|
typeScriptType = '$arg[]';
|
||||||
} else if (const TypeChecker.fromRuntime(List).isAssignableFromType(type) &&
|
} else if (const TypeChecker.fromRuntime(Map).isAssignableFromType(type) &&
|
||||||
type.typeArguments.length == 2) {
|
type.typeArguments.length == 2) {
|
||||||
var key = await compileToTypeScriptType(
|
var key = await compileToTypeScriptType(
|
||||||
fieldName, type.typeArguments[0], ext, buildStep);
|
ctx, fieldName, type.typeArguments[0], ext, buildStep);
|
||||||
var value = await compileToTypeScriptType(
|
var value = await compileToTypeScriptType(
|
||||||
fieldName, type.typeArguments[1], ext, buildStep);
|
ctx, fieldName, type.typeArguments[1], ext, buildStep);
|
||||||
var modelType = type.typeArguments[1];
|
//var modelType = type.typeArguments[1];
|
||||||
var ctx = await buildContext(
|
/*var innerCtx = await buildContext(
|
||||||
modelType.element,
|
modelType.element,
|
||||||
new ConstantReader(
|
new ConstantReader(
|
||||||
serializableTypeChecker.firstAnnotationOf(modelType.element)),
|
serializableTypeChecker.firstAnnotationOf(modelType.element)),
|
||||||
|
@ -47,7 +47,7 @@ class TypeScriptDefinitionBuilder implements Builder {
|
||||||
buildStep.resolver,
|
buildStep.resolver,
|
||||||
autoSnakeCaseNames,
|
autoSnakeCaseNames,
|
||||||
true,
|
true,
|
||||||
);
|
);*/
|
||||||
|
|
||||||
typeScriptType = ctx.modelClassNameRecase.pascalCase +
|
typeScriptType = ctx.modelClassNameRecase.pascalCase +
|
||||||
new ReCase(fieldName).pascalCase;
|
new ReCase(fieldName).pascalCase;
|
||||||
|
@ -63,7 +63,7 @@ class TypeScriptDefinitionBuilder implements Builder {
|
||||||
typeScriptType = 'any[]';
|
typeScriptType = 'any[]';
|
||||||
else {
|
else {
|
||||||
var arg = await compileToTypeScriptType(
|
var arg = await compileToTypeScriptType(
|
||||||
fieldName, type.typeArguments[0], ext, buildStep);
|
ctx, fieldName, type.typeArguments[0], ext, buildStep);
|
||||||
typeScriptType = '$arg[]';
|
typeScriptType = '$arg[]';
|
||||||
}
|
}
|
||||||
} else if (isModelClass(type)) {
|
} else if (isModelClass(type)) {
|
||||||
|
@ -148,7 +148,7 @@ class TypeScriptDefinitionBuilder implements Builder {
|
||||||
|
|
||||||
var alias = ctx.resolveFieldName(field.name);
|
var alias = ctx.resolveFieldName(field.name);
|
||||||
var typeScriptType = await compileToTypeScriptType(
|
var typeScriptType = await compileToTypeScriptType(
|
||||||
field.name, field.type, ext, buildStep);
|
ctx, field.name, field.type, ext, buildStep);
|
||||||
|
|
||||||
// foo: string;
|
// foo: string;
|
||||||
buf.writeln('$alias?: $typeScriptType;');
|
buf.writeln('$alias?: $typeScriptType;');
|
||||||
|
|
|
@ -85,6 +85,12 @@ main() {
|
||||||
expect(BookFields.camelCaseString, 'camelCase');
|
expect(BookFields.camelCaseString, 'camelCase');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('equals', () {
|
||||||
|
expect(jkRowling.copyWith(), jkRowling);
|
||||||
|
expect(deathlyHallows.copyWith(), deathlyHallows);
|
||||||
|
expect(library.copyWith(), library);
|
||||||
|
});
|
||||||
|
|
||||||
group('deserialization', () {
|
group('deserialization', () {
|
||||||
test('deserialization sets proper fields', () {
|
test('deserialization sets proper fields', () {
|
||||||
var book = BookSerializer.fromMap(deathlyHallowsMap);
|
var book = BookSerializer.fromMap(deathlyHallowsMap);
|
||||||
|
|
|
@ -1,7 +1,18 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
interface Library {
|
interface Library {
|
||||||
id?: string;
|
id?: string;
|
||||||
collection?: any;
|
collection?: LibraryCollection;
|
||||||
|
created_at?: any;
|
||||||
|
updated_at?: any;
|
||||||
|
}
|
||||||
|
interface LibraryCollection {
|
||||||
|
[key: string]: Book;
|
||||||
|
}
|
||||||
|
interface Bookmark {
|
||||||
|
id?: string;
|
||||||
|
history?: number[];
|
||||||
|
page?: number;
|
||||||
|
comment?: string;
|
||||||
created_at?: any;
|
created_at?: any;
|
||||||
updated_at?: any;
|
updated_at?: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ library angel_serialize.test.models.author;
|
||||||
|
|
||||||
import 'package:angel_framework/common.dart';
|
import 'package:angel_framework/common.dart';
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'book.dart';
|
import 'book.dart';
|
||||||
|
|
||||||
part 'author.g.dart';
|
part 'author.g.dart';
|
||||||
|
@ -27,13 +28,14 @@ abstract class _Library extends Model {
|
||||||
Map<String, Book> get collection;
|
Map<String, Book> get collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@serializable
|
@Serializable(serializers: Serializers.all)
|
||||||
abstract class _Bookmark extends Model {
|
abstract class _Bookmark extends Model {
|
||||||
@exclude
|
@exclude
|
||||||
final Book book;
|
final Book book;
|
||||||
|
|
||||||
|
List<int> get history;
|
||||||
int get page;
|
int get page;
|
||||||
String get comment;
|
String get comment;
|
||||||
|
|
||||||
_Bookmark(this.book);
|
_Bookmark(this.book);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,20 @@ class Author extends _Author {
|
||||||
updatedAt: updatedAt ?? this.updatedAt);
|
updatedAt: updatedAt ?? this.updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator ==(other) {
|
||||||
|
return other is _Author &&
|
||||||
|
other.id == id &&
|
||||||
|
other.name == name &&
|
||||||
|
other.age == age &&
|
||||||
|
const ListEquality<Book>(const DefaultEquality<Book>())
|
||||||
|
.equals(other.books, books) &&
|
||||||
|
other.newestBook == newestBook &&
|
||||||
|
other.secret == secret &&
|
||||||
|
other.obscured == obscured &&
|
||||||
|
other.createdAt == createdAt &&
|
||||||
|
other.updatedAt == updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return AuthorSerializer.toMap(this);
|
return AuthorSerializer.toMap(this);
|
||||||
}
|
}
|
||||||
|
@ -99,6 +113,17 @@ class Library extends _Library {
|
||||||
updatedAt: updatedAt ?? this.updatedAt);
|
updatedAt: updatedAt ?? this.updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator ==(other) {
|
||||||
|
return other is _Library &&
|
||||||
|
other.id == id &&
|
||||||
|
const MapEquality<String, Book>(
|
||||||
|
keys: const DefaultEquality<String>(),
|
||||||
|
values: const DefaultEquality<Book>())
|
||||||
|
.equals(other.collection, collection) &&
|
||||||
|
other.createdAt == createdAt &&
|
||||||
|
other.updatedAt == updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return LibrarySerializer.toMap(this);
|
return LibrarySerializer.toMap(this);
|
||||||
}
|
}
|
||||||
|
@ -106,12 +131,20 @@ class Library extends _Library {
|
||||||
|
|
||||||
class Bookmark extends _Bookmark {
|
class Bookmark extends _Bookmark {
|
||||||
Bookmark(Book book,
|
Bookmark(Book book,
|
||||||
{this.id, this.page, this.comment, this.createdAt, this.updatedAt})
|
{this.id,
|
||||||
|
this.history,
|
||||||
|
this.page,
|
||||||
|
this.comment,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt})
|
||||||
: super(book);
|
: super(book);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final List<int> history;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final int page;
|
final int page;
|
||||||
|
|
||||||
|
@ -126,18 +159,31 @@ class Bookmark extends _Bookmark {
|
||||||
|
|
||||||
Bookmark copyWith(Book book,
|
Bookmark copyWith(Book book,
|
||||||
{String id,
|
{String id,
|
||||||
|
List<int> history,
|
||||||
int page,
|
int page,
|
||||||
String comment,
|
String comment,
|
||||||
DateTime createdAt,
|
DateTime createdAt,
|
||||||
DateTime updatedAt}) {
|
DateTime updatedAt}) {
|
||||||
return new Bookmark(book,
|
return new Bookmark(book,
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
|
history: history ?? this.history,
|
||||||
page: page ?? this.page,
|
page: page ?? this.page,
|
||||||
comment: comment ?? this.comment,
|
comment: comment ?? this.comment,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
updatedAt: updatedAt ?? this.updatedAt);
|
updatedAt: updatedAt ?? this.updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator ==(other) {
|
||||||
|
return other is _Bookmark &&
|
||||||
|
other.id == id &&
|
||||||
|
const ListEquality<int>(const DefaultEquality<int>())
|
||||||
|
.equals(other.history, history) &&
|
||||||
|
other.page == page &&
|
||||||
|
other.comment == comment &&
|
||||||
|
other.createdAt == createdAt &&
|
||||||
|
other.updatedAt == updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return BookmarkSerializer.toMap(this);
|
return BookmarkSerializer.toMap(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,7 @@ abstract class BookmarkSerializer {
|
||||||
static Bookmark fromMap(Map map, Book book) {
|
static Bookmark fromMap(Map map, Book book) {
|
||||||
return new Bookmark(book,
|
return new Bookmark(book,
|
||||||
id: map['id'],
|
id: map['id'],
|
||||||
|
history: map['history'],
|
||||||
page: map['page'],
|
page: map['page'],
|
||||||
comment: map['comment'],
|
comment: map['comment'],
|
||||||
createdAt: map['created_at'] != null
|
createdAt: map['created_at'] != null
|
||||||
|
@ -129,6 +130,7 @@ abstract class BookmarkSerializer {
|
||||||
static Map<String, dynamic> toMap(Bookmark model) {
|
static Map<String, dynamic> toMap(Bookmark model) {
|
||||||
return {
|
return {
|
||||||
'id': model.id,
|
'id': model.id,
|
||||||
|
'history': model.history,
|
||||||
'page': model.page,
|
'page': model.page,
|
||||||
'comment': model.comment,
|
'comment': model.comment,
|
||||||
'created_at': model.createdAt?.toIso8601String(),
|
'created_at': model.createdAt?.toIso8601String(),
|
||||||
|
@ -140,6 +142,8 @@ abstract class BookmarkSerializer {
|
||||||
abstract class BookmarkFields {
|
abstract class BookmarkFields {
|
||||||
static const String id = 'id';
|
static const String id = 'id';
|
||||||
|
|
||||||
|
static const String history = 'history';
|
||||||
|
|
||||||
static const String page = 'page';
|
static const String page = 'page';
|
||||||
|
|
||||||
static const String comment = 'comment';
|
static const String comment = 'comment';
|
||||||
|
|
|
@ -2,6 +2,7 @@ library angel_serialize.test.models.book;
|
||||||
|
|
||||||
import 'package:angel_framework/common.dart';
|
import 'package:angel_framework/common.dart';
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
part 'book.g.dart';
|
part 'book.g.dart';
|
||||||
part 'book.serializer.g.dart';
|
part 'book.serializer.g.dart';
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,20 @@ class Book extends _Book {
|
||||||
updatedAt: updatedAt ?? this.updatedAt);
|
updatedAt: updatedAt ?? this.updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator ==(other) {
|
||||||
|
return other is _Book &&
|
||||||
|
other.id == id &&
|
||||||
|
other.author == author &&
|
||||||
|
other.title == title &&
|
||||||
|
other.description == description &&
|
||||||
|
other.pageCount == pageCount &&
|
||||||
|
const ListEquality<double>(const DefaultEquality<double>())
|
||||||
|
.equals(other.notModels, notModels) &&
|
||||||
|
other.camelCaseString == camelCaseString &&
|
||||||
|
other.createdAt == createdAt &&
|
||||||
|
other.updatedAt == updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return BookSerializer.toMap(this);
|
return BookSerializer.toMap(this);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue