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
dev_dependencies:
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
@ -41,7 +41,7 @@ class to have it serialized, and a serializable model class's name should also s
with a leading underscore.
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
its constructors automatically generated, in addition into serialization functions.
@ -85,13 +85,15 @@ myFunction() {
var map = BookSerializer.toMap(warAndPeace);
// Also deserialize from Maps
var book = BookSerialize.fromMap(map);
var book = BookSerializer.fromMap(map);
print(book.title); // 'War and Peace'
// For compatibility with `JSON.encode`, a `toJson` method
// is included that forwards to `BookSerializer.toMap`:
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
* Support for using `abstract` to create immutable model classes.
* 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
* Deserialization now supports un-serialized `DateTime`.

View file

@ -42,6 +42,7 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
generateConstructor(ctx, clazz, file);
generateCopyWithMethod(ctx, clazz, file);
generateEqualsOperator(ctx, clazz, file);
// Generate toJson() method if necessary
var serializers = annotation.peek('serializers')?.listValue ?? [];
@ -91,7 +92,7 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
method
..name = 'copyWith'
..returns = ctx.modelClassType;
// Add all `super` params
if (ctx.constructorParameters.isNotEmpty) {
for (var param in ctx.constructorParameters) {
@ -126,4 +127,50 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
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,
List<CodeBuffer> ext, BuildStep buildStep) async {
Future<String> compileToTypeScriptType(BuildContext ctx, String fieldName,
InterfaceType type, List<CodeBuffer> ext, BuildStep buildStep) async {
String typeScriptType = 'any';
var types = const {
@ -30,16 +30,16 @@ class TypeScriptDefinitionBuilder implements Builder {
if (isListModelType(type)) {
var arg = await compileToTypeScriptType(
fieldName, type.typeArguments[0], ext, buildStep);
ctx, fieldName, type.typeArguments[0], ext, buildStep);
typeScriptType = '$arg[]';
} else if (const TypeChecker.fromRuntime(List).isAssignableFromType(type) &&
} else if (const TypeChecker.fromRuntime(Map).isAssignableFromType(type) &&
type.typeArguments.length == 2) {
var key = await compileToTypeScriptType(
fieldName, type.typeArguments[0], ext, buildStep);
ctx, fieldName, type.typeArguments[0], ext, buildStep);
var value = await compileToTypeScriptType(
fieldName, type.typeArguments[1], ext, buildStep);
var modelType = type.typeArguments[1];
var ctx = await buildContext(
ctx, fieldName, type.typeArguments[1], ext, buildStep);
//var modelType = type.typeArguments[1];
/*var innerCtx = await buildContext(
modelType.element,
new ConstantReader(
serializableTypeChecker.firstAnnotationOf(modelType.element)),
@ -47,7 +47,7 @@ class TypeScriptDefinitionBuilder implements Builder {
buildStep.resolver,
autoSnakeCaseNames,
true,
);
);*/
typeScriptType = ctx.modelClassNameRecase.pascalCase +
new ReCase(fieldName).pascalCase;
@ -63,7 +63,7 @@ class TypeScriptDefinitionBuilder implements Builder {
typeScriptType = 'any[]';
else {
var arg = await compileToTypeScriptType(
fieldName, type.typeArguments[0], ext, buildStep);
ctx, fieldName, type.typeArguments[0], ext, buildStep);
typeScriptType = '$arg[]';
}
} else if (isModelClass(type)) {
@ -148,7 +148,7 @@ class TypeScriptDefinitionBuilder implements Builder {
var alias = ctx.resolveFieldName(field.name);
var typeScriptType = await compileToTypeScriptType(
field.name, field.type, ext, buildStep);
ctx, field.name, field.type, ext, buildStep);
// foo: string;
buf.writeln('$alias?: $typeScriptType;');

View file

@ -85,6 +85,12 @@ main() {
expect(BookFields.camelCaseString, 'camelCase');
});
test('equals', () {
expect(jkRowling.copyWith(), jkRowling);
expect(deathlyHallows.copyWith(), deathlyHallows);
expect(library.copyWith(), library);
});
group('deserialization', () {
test('deserialization sets proper fields', () {
var book = BookSerializer.fromMap(deathlyHallowsMap);

View file

@ -1,7 +1,18 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
interface Library {
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;
updated_at?: any;
}

View file

@ -2,6 +2,7 @@ library angel_serialize.test.models.author;
import 'package:angel_framework/common.dart';
import 'package:angel_serialize/angel_serialize.dart';
import 'package:collection/collection.dart';
import 'book.dart';
part 'author.g.dart';
@ -27,13 +28,14 @@ abstract class _Library extends Model {
Map<String, Book> get collection;
}
@serializable
@Serializable(serializers: Serializers.all)
abstract class _Bookmark extends Model {
@exclude
final Book book;
List<int> get history;
int get page;
String get comment;
_Bookmark(this.book);
}

View file

@ -67,6 +67,20 @@ class Author extends _Author {
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() {
return AuthorSerializer.toMap(this);
}
@ -99,6 +113,17 @@ class Library extends _Library {
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() {
return LibrarySerializer.toMap(this);
}
@ -106,12 +131,20 @@ class Library extends _Library {
class Bookmark extends _Bookmark {
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);
@override
final String id;
@override
final List<int> history;
@override
final int page;
@ -126,18 +159,31 @@ class Bookmark extends _Bookmark {
Bookmark copyWith(Book book,
{String id,
List<int> history,
int page,
String comment,
DateTime createdAt,
DateTime updatedAt}) {
return new Bookmark(book,
id: id ?? this.id,
history: history ?? this.history,
page: page ?? this.page,
comment: comment ?? this.comment,
createdAt: createdAt ?? this.createdAt,
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() {
return BookmarkSerializer.toMap(this);
}

View file

@ -112,6 +112,7 @@ abstract class BookmarkSerializer {
static Bookmark fromMap(Map map, Book book) {
return new Bookmark(book,
id: map['id'],
history: map['history'],
page: map['page'],
comment: map['comment'],
createdAt: map['created_at'] != null
@ -129,6 +130,7 @@ abstract class BookmarkSerializer {
static Map<String, dynamic> toMap(Bookmark model) {
return {
'id': model.id,
'history': model.history,
'page': model.page,
'comment': model.comment,
'created_at': model.createdAt?.toIso8601String(),
@ -140,6 +142,8 @@ abstract class BookmarkSerializer {
abstract class BookmarkFields {
static const String id = 'id';
static const String history = 'history';
static const String page = 'page';
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_serialize/angel_serialize.dart';
import 'package:collection/collection.dart';
part 'book.g.dart';
part 'book.serializer.g.dart';

View file

@ -67,6 +67,20 @@ class Book extends _Book {
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() {
return BookSerializer.toMap(this);
}