diff --git a/README.md b/README.md index eb611ef2..13973811 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,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'; @serializable diff --git a/angel_serialize_generator/CHANGELOG.md b/angel_serialize_generator/CHANGELOG.md index 0574301a..2d180d8e 100644 --- a/angel_serialize_generator/CHANGELOG.md +++ b/angel_serialize_generator/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.0.7 +* Create unmodifiable Lists and Maps. + # 2.0.6 * Support for using `abstract` to create immutable model classes. * Add support for custom constructor parameters. diff --git a/angel_serialize_generator/lib/angel_serialize_generator.dart b/angel_serialize_generator/lib/angel_serialize_generator.dart index 35021637..e3f2b2a1 100644 --- a/angel_serialize_generator/lib/angel_serialize_generator.dart +++ b/angel_serialize_generator/lib/angel_serialize_generator.dart @@ -60,16 +60,21 @@ bool isModelClass(DartType t) { } } +bool isListOrMapType(DartType t) { + return const TypeChecker.fromRuntime(List).isAssignableFromType(t) || + const TypeChecker.fromRuntime(Map).isAssignableFromType(t); +} + /// Determines if a [DartType] is a `List` with the first type argument being a `Model`. bool isListModelType(InterfaceType t) { - return t.name == 'List' && + return const TypeChecker.fromRuntime(List).isAssignableFromType(t) && t.typeArguments.length == 1 && isModelClass(t.typeArguments[0]); } /// Determines if a [DartType] is a `Map` with the second type argument being a `Model`. bool isMapToModelType(InterfaceType t) { - return t.name == 'Map' && + return const TypeChecker.fromRuntime(Map).isAssignableFromType(t) && t.typeArguments.length == 2 && isModelClass(t.typeArguments[1]); } diff --git a/angel_serialize_generator/lib/model.dart b/angel_serialize_generator/lib/model.dart index 60e9957a..6bd676f9 100644 --- a/angel_serialize_generator/lib/model.dart +++ b/angel_serialize_generator/lib/model.dart @@ -63,23 +63,43 @@ class JsonModelGenerator extends GeneratorForAnnotation { BuildContext ctx, ClassBuilder clazz, LibraryBuilder file) { clazz.constructors.add(new Constructor((constructor) { // Add all `super` params - if (ctx.constructorParameters.isNotEmpty) { - for (var param in ctx.constructorParameters) { - constructor.requiredParameters.add(new Parameter((b) => b - ..name = param.name - ..type = convertTypeReference(param.type))); - } - constructor.initializers.add(new Code( - 'super(${ctx.constructorParameters.map((p) => p.name).join(',')})')); + for (var param in ctx.constructorParameters) { + constructor.requiredParameters.add(new Parameter((b) => b + ..name = param.name + ..type = convertTypeReference(param.type))); + } + + for (var field in ctx.fields) { + if (isListOrMapType(field.type)) { + String typeName = const TypeChecker.fromRuntime(List) + .isAssignableFromType(field.type) + ? 'List' + : 'Map'; + var defaultValue = typeName == 'List' ? '[]' : '{}'; + constructor.initializers.add(new Code(''' + this.${field.name} = + new $typeName.unmodifiable(${field.name} ?? $defaultValue)''')); + } + } + + if (ctx.constructorParameters.isNotEmpty) { + constructor.initializers.add( + new Code('super(${ctx.constructorParameters.map((p) => p.name).join( + ',')})')); } for (var field in ctx.fields) { constructor.optionalParameters.add(new Parameter((b) { b ..name = field.name - ..named = true - ..toThis = true; + ..named = true; + + if (!isListOrMapType(field.type)) + b.toThis = true; + else { + b.type = convertTypeReference(field.type); + } })); } })); @@ -141,9 +161,11 @@ class JsonModelGenerator extends GeneratorForAnnotation { 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)'; + 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 'const MapEquality()<${it.typeArguments[0].name}, ${it + .typeArguments[1].name}>'; } return nullable ? null : 'const DefaultEquality<${type.name}>()'; diff --git a/angel_serialize_generator/lib/serialize.dart b/angel_serialize_generator/lib/serialize.dart index d0abab99..6c04d39f 100644 --- a/angel_serialize_generator/lib/serialize.dart +++ b/angel_serialize_generator/lib/serialize.dart @@ -169,17 +169,17 @@ class SerializerGenerator extends GeneratorForAnnotation { if (isListModelType(t)) { var rc = new ReCase(t.typeArguments[0].name); deserializedRepresentation = "map['$alias'] is Iterable" - " ? map['$alias'].map(${rc - .pascalCase}Serializer.fromMap).toList()" + " ? new List.unmodifiable(map['$alias'].map(${rc + .pascalCase}Serializer.fromMap))" " : null"; } else if (isMapToModelType(t)) { var rc = new ReCase(t.typeArguments[1].name); deserializedRepresentation = ''' map['$alias'] is Map - ? map['$alias'].keys.fold({}, (out, key) { + ? new Map.unmodifiable(map['$alias'].keys.fold({}, (out, key) { return out..[key] = ${rc .pascalCase}Serializer.fromMap(map['$alias'][key]); - }) + })) : null '''; } diff --git a/angel_serialize_generator/test/models/author.g.dart b/angel_serialize_generator/test/models/author.g.dart index 24dcf596..a14db13e 100644 --- a/angel_serialize_generator/test/models/author.g.dart +++ b/angel_serialize_generator/test/models/author.g.dart @@ -11,12 +11,13 @@ class Author extends _Author { {this.id, this.name, this.age, - this.books, + List books, this.newestBook, this.secret, this.obscured, this.createdAt, - this.updatedAt}); + this.updatedAt}) + : this.books = new List.unmodifiable(books ?? []); @override final String id; @@ -87,7 +88,9 @@ class Author extends _Author { } class Library extends _Library { - Library({this.id, this.collection, this.createdAt, this.updatedAt}); + Library( + {this.id, Map collection, this.createdAt, this.updatedAt}) + : this.collection = new Map.unmodifiable(collection ?? {}); @override final String id; @@ -132,12 +135,13 @@ class Library extends _Library { class Bookmark extends _Bookmark { Bookmark(Book book, {this.id, - this.history, + List history, this.page, this.comment, this.createdAt, this.updatedAt}) - : super(book); + : this.history = new List.unmodifiable(history ?? []), + super(book); @override final String id; diff --git a/angel_serialize_generator/test/models/author.serializer.g.dart b/angel_serialize_generator/test/models/author.serializer.g.dart index 2aadeddf..5747c076 100644 --- a/angel_serialize_generator/test/models/author.serializer.g.dart +++ b/angel_serialize_generator/test/models/author.serializer.g.dart @@ -13,7 +13,7 @@ abstract class AuthorSerializer { name: map['name'], age: map['age'], books: map['books'] is Iterable - ? map['books'].map(BookSerializer.fromMap).toList() + ? new List.unmodifiable(map['books'].map(BookSerializer.fromMap)) : null, newestBook: map['newest_book'] != null ? BookSerializer.fromMap(map['newest_book']) @@ -69,10 +69,10 @@ abstract class LibrarySerializer { return new Library( id: map['id'], collection: map['collection'] is Map - ? map['collection'].keys.fold({}, (out, key) { + ? new Map.unmodifiable(map['collection'].keys.fold({}, (out, key) { return out ..[key] = BookSerializer.fromMap(map['collection'][key]); - }) + })) : null, createdAt: map['created_at'] != null ? (map['created_at'] is DateTime diff --git a/angel_serialize_generator/test/models/book.g.dart b/angel_serialize_generator/test/models/book.g.dart index 5c5bfbe9..ff9a997c 100644 --- a/angel_serialize_generator/test/models/book.g.dart +++ b/angel_serialize_generator/test/models/book.g.dart @@ -13,10 +13,11 @@ class Book extends _Book { this.title, this.description, this.pageCount, - this.notModels, + List notModels, this.camelCaseString, this.createdAt, - this.updatedAt}); + this.updatedAt}) + : this.notModels = new List.unmodifiable(notModels ?? []); @override final String id;