custom ==

This commit is contained in:
Tobe O 2018-05-13 14:02:47 -04:00
parent cf0a7eb652
commit 21dc10de5a
11 changed files with 155 additions and 19 deletions

View file

@ -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));
} }
``` ```

View file

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

View file

@ -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('&&')};');
}));
}
} }

View file

@ -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;');

View file

@ -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);

View file

@ -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;
} }

View file

@ -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);
} }

View file

@ -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);
} }

View file

@ -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';

View file

@ -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';

View file

@ -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);
} }