This commit is contained in:
Tobe O 2019-04-04 17:40:36 -04:00
parent 2f15f513a1
commit 560dd03a5a
17 changed files with 276 additions and 102 deletions

View file

@ -1,3 +1,8 @@
# 2.4.2
* Fix bug where enums didn't support default values.
* Stop emitting `@required` on items with default values.
* Create default `@SerializableField` for fields without them.
# 2.4.1+1
* Change `as Iterable<Map>` to `.cast<Map>`.

View file

@ -74,6 +74,7 @@ Expression convertObject(DartObject o) {
}
String dartObjectToString(DartObject v) {
var type = v.type;
if (v.isNull) return 'null';
if (v.toBoolValue() != null) return v.toBoolValue().toString();
if (v.toIntValue() != null) return v.toIntValue().toString();
@ -96,6 +97,18 @@ String dartObjectToString(DartObject v) {
.accept(new DartEmitter())
.toString();
}
if (type is InterfaceType && type.element.isEnum) {
// Find the index of the enum, then find the member.
for (var field in type.element.fields) {
if (field.isEnumConstant && field.isStatic) {
var value = type.element.getField(field.name).constantValue;
if (value == v) {
return '${type.name}.${field.name}';
}
}
}
}
throw new ArgumentError(v.toString());
}

View file

@ -70,21 +70,7 @@ Future<BuildContext> buildContext(ClassElement clazz, ConstantReader annotation,
// Check for @SerializableField
var fieldAnn = serializableFieldTypeChecker.firstAnnotationOf(el);
if (fieldAnn != null) {
var cr = ConstantReader(fieldAnn);
var sField = SerializableFieldMirror(
alias: cr.peek('alias')?.stringValue,
defaultValue: cr.peek('defaultValue')?.objectValue,
serializer: cr.peek('serializer')?.symbolValue,
deserializer: cr.peek('deserializer')?.symbolValue,
errorMessage: cr.peek('errorMessage')?.stringValue,
isNullable: cr.peek('isNullable')?.boolValue ?? true,
canDeserialize: cr.peek('canDeserialize')?.boolValue ?? false,
canSerialize: cr.peek('canSerialize')?.boolValue ?? false,
exclude: cr.peek('exclude')?.boolValue ?? false,
serializesTo: cr.peek('serializesTo')?.typeValue,
);
void handleSerializableField(SerializableFieldMirror sField) {
ctx.fieldInfo[field.name] = sField;
if (sField.defaultValue != null) {
@ -110,14 +96,35 @@ Future<BuildContext> buildContext(ClassElement clazz, ConstantReader annotation,
canDeserialize: sField.canDeserialize,
);
}
}
if (fieldAnn != null) {
var cr = ConstantReader(fieldAnn);
var excluded = cr.peek('exclude')?.boolValue ?? false;
var sField = SerializableFieldMirror(
alias: cr.peek('alias')?.stringValue,
defaultValue: cr.peek('defaultValue')?.objectValue,
serializer: cr.peek('serializer')?.symbolValue,
deserializer: cr.peek('deserializer')?.symbolValue,
errorMessage: cr.peek('errorMessage')?.stringValue,
isNullable: cr.peek('isNullable')?.boolValue ?? !excluded,
canDeserialize: cr.peek('canDeserialize')?.boolValue ?? false,
canSerialize: cr.peek('canSerialize')?.boolValue ?? false,
exclude: excluded,
serializesTo: cr.peek('serializesTo')?.typeValue,
);
handleSerializableField(sField);
// Apply
} else {
var foundNone = true;
// Skip if annotated with @exclude
var excludeAnnotation = excludeTypeChecker.firstAnnotationOf(el);
if (excludeAnnotation != null) {
var cr = new ConstantReader(excludeAnnotation);
foundNone = false;
// ignore: deprecated_member_use
ctx.excluded[field.name] = new Exclude(
@ -133,6 +140,7 @@ Future<BuildContext> buildContext(ClassElement clazz, ConstantReader annotation,
if (defAnn != null) {
var rev = new ConstantReader(defAnn).revive().positionalArguments[0];
ctx.defaults[field.name] = rev;
foundNone = false;
}
// Check for alias
@ -143,6 +151,7 @@ Future<BuildContext> buildContext(ClassElement clazz, ConstantReader annotation,
if (aliasAnn != null) {
// ignore: deprecated_member_use
alias = new Alias(aliasAnn.getField('name').toStringValue());
foundNone = false;
}
if (alias?.name?.isNotEmpty == true) {
@ -162,6 +171,24 @@ Future<BuildContext> buildContext(ClassElement clazz, ConstantReader annotation,
var reason = cr.peek('reason')?.stringValue ??
"Missing required field '${ctx.resolveFieldName(field.name)}' on ${ctx.modelClassName}.";
ctx.requiredFields[field.name] = reason;
foundNone = false;
}
if (foundNone) {
var f = SerializableField();
var sField = SerializableFieldMirror(
alias: f.alias,
defaultValue: null,
serializer: f.serializer,
deserializer: f.deserializer,
errorMessage: f.errorMessage,
isNullable: f.isNullable,
canDeserialize: f.canDeserialize,
canSerialize: f.canSerialize,
exclude: f.exclude,
serializesTo: null,
);
handleSerializableField(sField);
}
}

View file

@ -129,7 +129,8 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
b.type = convertTypeReference(field.type);
}
if (ctx.requiredFields.containsKey(field.name)) {
if (ctx.requiredFields.containsKey(field.name) &&
b.defaultTo == null) {
b.annotations.add(new CodeExpression(new Code('required')));
}
}));

View file

@ -128,8 +128,15 @@ class TypeScriptDefinitionBuilder implements Builder {
return;
}
var elements =
lib.annotatedWith(const TypeChecker.fromRuntime(Serializable));
var elements = <AnnotatedElement>[];
try {
elements = lib
.annotatedWith(const TypeChecker.fromRuntime(Serializable))
.toList();
} catch (_) {
// Ignore error in source_gen/build_runner that has no explanation
}
for (var element in elements) {
if (element.element.kind != ElementKind.CLASS)

View file

@ -1,29 +1,28 @@
/// <reference path="../book.d.ts" />
// GENERATED CODE - DO NOT MODIFY BY HAND
declare module 'angel_serialize_generator' {
interface Author {
id?: string;
name: string;
age: number;
books?: Book[];
newest_book?: Book;
books?: any[];
newest_book?: any;
created_at?: any;
updated_at?: any;
}
interface Library {
id?: string;
collection?: LibraryCollection;
collection: LibraryCollection;
created_at?: any;
updated_at?: any;
}
interface LibraryCollection {
[key: string]: Book;
[key: string]: any;
}
interface Bookmark {
id?: string;
history?: number[];
history: number[];
page: number;
comment?: string;
comment: string;
created_at?: any;
updated_at?: any;
}

View file

@ -18,14 +18,16 @@ abstract class _Author extends Model {
isNullable: false, errorMessage: 'Custom message for missing `age`')
int get age;
@nullable
List<Book> get books;
@nullable
Book get newestBook;
@SerializableField(exclude: true)
@SerializableField(exclude: true, isNullable: true)
String get secret;
@SerializableField(exclude: true, canDeserialize: true)
@SerializableField(exclude: true, canDeserialize: true, isNullable: true)
String get obscured;
}

View file

@ -12,7 +12,7 @@ class Author extends _Author {
{this.id,
@required this.name,
@required this.age,
List<Book> books,
List<dynamic> books,
this.newestBook,
this.secret,
this.obscured,
@ -30,10 +30,10 @@ class Author extends _Author {
final int age;
@override
final List<Book> books;
final List<dynamic> books;
@override
final Book newestBook;
final dynamic newestBook;
@override
final String secret;
@ -51,8 +51,8 @@ class Author extends _Author {
{String id,
String name,
int age,
List<Book> books,
Book newestBook,
List<dynamic> books,
dynamic newestBook,
String secret,
String obscured,
DateTime createdAt,
@ -74,7 +74,7 @@ class Author extends _Author {
other.id == id &&
other.name == name &&
other.age == age &&
const ListEquality<Book>(const DefaultEquality<Book>())
const ListEquality<dynamic>(const DefaultEquality())
.equals(other.books, books) &&
other.newestBook == newestBook &&
other.secret == secret &&
@ -106,14 +106,17 @@ class Author extends _Author {
@generatedSerializable
class Library extends _Library {
Library(
{this.id, Map<String, Book> collection, this.createdAt, this.updatedAt})
{this.id,
@required Map<String, dynamic> collection,
this.createdAt,
this.updatedAt})
: this.collection = new Map.unmodifiable(collection ?? {});
@override
final String id;
@override
final Map<String, Book> collection;
final Map<String, dynamic> collection;
@override
final DateTime createdAt;
@ -123,7 +126,7 @@ class Library extends _Library {
Library copyWith(
{String id,
Map<String, Book> collection,
Map<String, dynamic> collection,
DateTime createdAt,
DateTime updatedAt}) {
return new Library(
@ -136,9 +139,9 @@ class Library extends _Library {
bool operator ==(other) {
return other is _Library &&
other.id == id &&
const MapEquality<String, Book>(
const MapEquality<String, dynamic>(
keys: const DefaultEquality<String>(),
values: const DefaultEquality<Book>())
values: const DefaultEquality())
.equals(other.collection, collection) &&
other.createdAt == createdAt &&
other.updatedAt == updatedAt;
@ -156,11 +159,11 @@ class Library extends _Library {
@generatedSerializable
class Bookmark extends _Bookmark {
Bookmark(Book book,
Bookmark(dynamic book,
{this.id,
List<int> history,
@required List<int> history,
@required this.page,
this.comment,
@required this.comment,
this.createdAt,
this.updatedAt})
: this.history = new List.unmodifiable(history ?? []),
@ -184,7 +187,7 @@ class Bookmark extends _Bookmark {
@override
final DateTime updatedAt;
Bookmark copyWith(Book book,
Bookmark copyWith(dynamic book,
{String id,
List<int> history,
int page,
@ -240,13 +243,9 @@ abstract class AuthorSerializer {
name: map['name'] as String,
age: map['age'] as int,
books: map['books'] is Iterable
? new List.unmodifiable(((map['books'] as Iterable)
.where((x) => x is Map) as Iterable<Map>)
.map(BookSerializer.fromMap))
: null,
newestBook: map['newest_book'] != null
? BookSerializer.fromMap(map['newest_book'] as Map)
? (map['books'] as Iterable).cast<dynamic>().toList()
: null,
newestBook: map['newest_book'] as dynamic,
obscured: map['obscured'] as String,
createdAt: map['created_at'] != null
? (map['created_at'] is DateTime
@ -276,8 +275,8 @@ abstract class AuthorSerializer {
'id': model.id,
'name': model.name,
'age': model.age,
'books': model.books?.map((m) => BookSerializer.toMap(m))?.toList(),
'newest_book': BookSerializer.toMap(model.newestBook),
'books': model.books,
'newest_book': model.newestBook,
'created_at': model.createdAt?.toIso8601String(),
'updated_at': model.updatedAt?.toIso8601String()
};
@ -285,7 +284,7 @@ abstract class AuthorSerializer {
}
abstract class AuthorFields {
static const List<String> allFields = const <String>[
static const List<String> allFields = <String>[
id,
name,
age,
@ -318,15 +317,15 @@ abstract class AuthorFields {
abstract class LibrarySerializer {
static Library fromMap(Map map) {
if (map['collection'] == null) {
throw new FormatException(
"Missing required field 'collection' on Library.");
}
return new Library(
id: map['id'] as String,
collection: map['collection'] is Map
? new Map.unmodifiable(
(map['collection'] as Map).keys.fold({}, (out, key) {
return out
..[key] = BookSerializer.fromMap(
((map['collection'] as Map)[key]) as Map);
}))
? (map['collection'] as Map).cast<String, dynamic>()
: null,
createdAt: map['created_at'] != null
? (map['created_at'] is DateTime
@ -344,11 +343,14 @@ abstract class LibrarySerializer {
if (model == null) {
return null;
}
if (model.collection == null) {
throw new FormatException(
"Missing required field 'collection' on Library.");
}
return {
'id': model.id,
'collection': model.collection.keys?.fold({}, (map, key) {
return map..[key] = BookSerializer.toMap(model.collection[key]);
}),
'collection': model.collection,
'created_at': model.createdAt?.toIso8601String(),
'updated_at': model.updatedAt?.toIso8601String()
};
@ -356,7 +358,7 @@ abstract class LibrarySerializer {
}
abstract class LibraryFields {
static const List<String> allFields = const <String>[
static const List<String> allFields = <String>[
id,
collection,
createdAt,
@ -373,11 +375,21 @@ abstract class LibraryFields {
}
abstract class BookmarkSerializer {
static Bookmark fromMap(Map map, Book book) {
static Bookmark fromMap(Map map, dynamic book) {
if (map['history'] == null) {
throw new FormatException(
"Missing required field 'history' on Bookmark.");
}
if (map['page'] == null) {
throw new FormatException("Missing required field 'page' on Bookmark.");
}
if (map['comment'] == null) {
throw new FormatException(
"Missing required field 'comment' on Bookmark.");
}
return new Bookmark(book,
id: map['id'] as String,
history: map['history'] is Iterable
@ -401,10 +413,20 @@ abstract class BookmarkSerializer {
if (model == null) {
return null;
}
if (model.history == null) {
throw new FormatException(
"Missing required field 'history' on Bookmark.");
}
if (model.page == null) {
throw new FormatException("Missing required field 'page' on Bookmark.");
}
if (model.comment == null) {
throw new FormatException(
"Missing required field 'comment' on Bookmark.");
}
return {
'id': model.id,
'history': model.history,
@ -417,7 +439,7 @@ abstract class BookmarkSerializer {
}
abstract class BookmarkFields {
static const List<String> allFields = const <String>[
static const List<String> allFields = <String>[
id,
history,
page,

View file

@ -2,11 +2,11 @@
declare module 'angel_serialize_generator' {
interface Book {
id?: string;
author?: string;
title?: string;
description?: string;
page_count?: number;
not_models?: number[];
author: string;
title: string;
description: string;
page_count: number;
not_models: number[];
camelCase?: string;
created_at?: any;
updated_at?: any;

View file

@ -17,6 +17,6 @@ abstract class _Book extends Model {
int pageCount;
List<double> notModels;
@SerializableField(alias: 'camelCase')
@SerializableField(alias: 'camelCase', isNullable: true)
String camelCaseString;
}

View file

@ -12,11 +12,11 @@ part of angel_serialize.test.models.book;
class Book extends _Book {
Book(
{this.id,
this.author,
this.title,
this.description,
this.pageCount,
List<double> notModels,
@required this.author,
@required this.title,
@required this.description,
@required this.pageCount,
@required List<double> notModels,
this.camelCaseString,
this.createdAt,
this.updatedAt})
@ -111,6 +111,27 @@ class Book extends _Book {
abstract class BookSerializer {
static Book fromMap(Map map) {
if (map['author'] == null) {
throw new FormatException("Missing required field 'author' on Book.");
}
if (map['title'] == null) {
throw new FormatException("Missing required field 'title' on Book.");
}
if (map['description'] == null) {
throw new FormatException(
"Missing required field 'description' on Book.");
}
if (map['page_count'] == null) {
throw new FormatException("Missing required field 'page_count' on Book.");
}
if (map['not_models'] == null) {
throw new FormatException("Missing required field 'not_models' on Book.");
}
return new Book(
id: map['id'] as String,
author: map['author'] as String,
@ -137,6 +158,27 @@ abstract class BookSerializer {
if (model == null) {
return null;
}
if (model.author == null) {
throw new FormatException("Missing required field 'author' on Book.");
}
if (model.title == null) {
throw new FormatException("Missing required field 'title' on Book.");
}
if (model.description == null) {
throw new FormatException(
"Missing required field 'description' on Book.");
}
if (model.pageCount == null) {
throw new FormatException("Missing required field 'page_count' on Book.");
}
if (model.notModels == null) {
throw new FormatException("Missing required field 'not_models' on Book.");
}
return {
'id': model.id,
'author': model.author,
@ -152,7 +194,7 @@ abstract class BookSerializer {
}
abstract class BookFields {
static const List<String> allFields = const <String>[
static const List<String> allFields = <String>[
id,
author,
title,

View file

@ -8,18 +8,19 @@ part of 'game_pad.dart';
@generatedSerializable
class Gamepad extends _Gamepad {
Gamepad({List<GamepadButton> buttons, Map<String, dynamic> dynamicMap})
Gamepad(
{@required List<dynamic> buttons,
@required Map<String, dynamic> dynamicMap})
: this.buttons = new List.unmodifiable(buttons ?? []),
this.dynamicMap = new Map.unmodifiable(dynamicMap ?? {});
@override
final List<GamepadButton> buttons;
final List<dynamic> buttons;
@override
final Map<String, dynamic> dynamicMap;
Gamepad copyWith(
{List<GamepadButton> buttons, Map<String, dynamic> dynamicMap}) {
Gamepad copyWith({List<dynamic> buttons, Map<String, dynamic> dynamicMap}) {
return new Gamepad(
buttons: buttons ?? this.buttons,
dynamicMap: dynamicMap ?? this.dynamicMap);
@ -27,8 +28,7 @@ class Gamepad extends _Gamepad {
bool operator ==(other) {
return other is _Gamepad &&
const ListEquality<GamepadButton>(
const DefaultEquality<GamepadButton>())
const ListEquality<dynamic>(const DefaultEquality())
.equals(other.buttons, buttons) &&
const MapEquality<String, dynamic>(
keys: const DefaultEquality<String>(),
@ -52,11 +52,18 @@ class Gamepad extends _Gamepad {
abstract class GamepadSerializer {
static Gamepad fromMap(Map map) {
if (map['buttons'] == null) {
throw new FormatException("Missing required field 'buttons' on Gamepad.");
}
if (map['dynamic_map'] == null) {
throw new FormatException(
"Missing required field 'dynamic_map' on Gamepad.");
}
return new Gamepad(
buttons: map['buttons'] is Iterable
? new List.unmodifiable(((map['buttons'] as Iterable)
.where((x) => x is Map) as Iterable<Map>)
.map(GamepadButtonSerializer.fromMap))
? (map['buttons'] as Iterable).cast<dynamic>().toList()
: null,
dynamicMap: map['dynamic_map'] is Map
? (map['dynamic_map'] as Map).cast<String, dynamic>()
@ -67,16 +74,21 @@ abstract class GamepadSerializer {
if (model == null) {
return null;
}
return {
'buttons':
model.buttons?.map((m) => GamepadButtonSerializer.toMap(m))?.toList(),
'dynamic_map': model.dynamicMap
};
if (model.buttons == null) {
throw new FormatException("Missing required field 'buttons' on Gamepad.");
}
if (model.dynamicMap == null) {
throw new FormatException(
"Missing required field 'dynamic_map' on Gamepad.");
}
return {'buttons': model.buttons, 'dynamic_map': model.dynamicMap};
}
}
abstract class GamepadFields {
static const List<String> allFields = const <String>[buttons, dynamicMap];
static const List<String> allFields = <String>[buttons, dynamicMap];
static const String buttons = 'buttons';

View file

@ -8,7 +8,7 @@ part of 'game_pad_button.dart';
@generatedSerializable
class GamepadButton implements _GamepadButton {
const GamepadButton({this.name, this.radius});
const GamepadButton({@required this.name, @required this.radius});
@override
final String name;
@ -43,6 +43,16 @@ class GamepadButton implements _GamepadButton {
abstract class GamepadButtonSerializer {
static GamepadButton fromMap(Map map) {
if (map['name'] == null) {
throw new FormatException(
"Missing required field 'name' on GamepadButton.");
}
if (map['radius'] == null) {
throw new FormatException(
"Missing required field 'radius' on GamepadButton.");
}
return new GamepadButton(
name: map['name'] as String, radius: map['radius'] as int);
}
@ -51,12 +61,22 @@ abstract class GamepadButtonSerializer {
if (model == null) {
return null;
}
if (model.name == null) {
throw new FormatException(
"Missing required field 'name' on GamepadButton.");
}
if (model.radius == null) {
throw new FormatException(
"Missing required field 'radius' on GamepadButton.");
}
return {'name': model.name, 'radius': model.radius};
}
}
abstract class GamepadButtonFields {
static const List<String> allFields = const <String>[name, radius];
static const List<String> allFields = <String>[name, radius];
static const String name = 'name';

View file

@ -8,7 +8,7 @@ part of 'goat.dart';
@generatedSerializable
class Goat implements _Goat {
const Goat({this.integer: 34, List<int> this.list: const [34, 35]});
const Goat({this.integer = 34, List<int> this.list = const [34, 35]});
@override
final int integer;
@ -43,6 +43,14 @@ class Goat implements _Goat {
abstract class GoatSerializer {
static Goat fromMap(Map map) {
if (map['integer'] == null) {
throw new FormatException("Missing required field 'integer' on Goat.");
}
if (map['list'] == null) {
throw new FormatException("Missing required field 'list' on Goat.");
}
return new Goat(
integer: map['integer'] as int ?? 34,
list: map['list'] is Iterable
@ -54,12 +62,20 @@ abstract class GoatSerializer {
if (model == null) {
return null;
}
if (model.integer == null) {
throw new FormatException("Missing required field 'integer' on Goat.");
}
if (model.list == null) {
throw new FormatException("Missing required field 'list' on Goat.");
}
return {'integer': model.integer, 'list': model.list};
}
}
abstract class GoatFields {
static const List<String> allFields = const <String>[integer, list];
static const List<String> allFields = <String>[integer, list];
static const String integer = 'integer';

View file

@ -60,7 +60,7 @@ abstract class HasMapSerializer {
}
abstract class HasMapFields {
static const List<String> allFields = const <String>[value];
static const List<String> allFields = <String>[value];
static const String value = 'value';
}

View file

@ -6,10 +6,13 @@ part 'with_enum.g.dart';
@serializable
abstract class _WithEnum {
@DefaultsTo(WithEnumType.b)
WithEnumType get type;
@nullable
List<int> get finalList;
@nullable
Uint8List get imageBytes;
}

View file

@ -8,7 +8,8 @@ part of 'with_enum.dart';
@generatedSerializable
class WithEnum implements _WithEnum {
const WithEnum({this.type, List<int> this.finalList, this.imageBytes});
const WithEnum(
{this.type = WithEnumType.b, List<int> this.finalList, this.imageBytes});
@override
final WithEnumType type;
@ -51,12 +52,16 @@ class WithEnum implements _WithEnum {
abstract class WithEnumSerializer {
static WithEnum fromMap(Map map) {
if (map['type'] == null) {
throw new FormatException("Missing required field 'type' on WithEnum.");
}
return new WithEnum(
type: map['type'] is WithEnumType
? (map['type'] as WithEnumType)
: (map['type'] is int
? WithEnumType.values[map['type'] as int]
: null),
: WithEnumType.b),
finalList: map['final_list'] is Iterable
? (map['final_list'] as Iterable).cast<int>().toList()
: null,
@ -75,6 +80,10 @@ abstract class WithEnumSerializer {
if (model == null) {
return null;
}
if (model.type == null) {
throw new FormatException("Missing required field 'type' on WithEnum.");
}
return {
'type':
model.type == null ? null : WithEnumType.values.indexOf(model.type),
@ -86,11 +95,7 @@ abstract class WithEnumSerializer {
}
abstract class WithEnumFields {
static const List<String> allFields = const <String>[
type,
finalList,
imageBytes
];
static const List<String> allFields = <String>[type, finalList, imageBytes];
static const String type = 'type';