Default values

This commit is contained in:
Tobe O 2018-12-30 20:18:44 -05:00
parent 7db3ec1a36
commit 133b6a3c41
11 changed files with 173 additions and 10 deletions

View file

@ -67,6 +67,7 @@ part 'book.g.dart';
abstract class _Book extends Model {
String get author;
@DefaultValue('[Untitled]')
String get title;
String get description;

View file

@ -27,6 +27,7 @@ targets:
_book:
sources:
- "test/models/book.dart"
- "test/models/goat.dart"
- "test/models/game_pad_button.dart"
- "test/models/with_enum.dart"
_typescript_definition:

View file

@ -2,7 +2,7 @@ library angel_serialize_generator;
import 'dart:async';
import 'dart:typed_data';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:angel_model/angel_model.dart';
@ -48,6 +48,32 @@ TypeReference convertTypeReference(DartType t) {
});
}
String dartObjectToString(DartObject v) {
if (v.isNull) return 'null';
if (v.toBoolValue() != null) return v.toBoolValue().toString();
if (v.toIntValue() != null) return v.toIntValue().toString();
if (v.toDoubleValue() != null) return v.toDoubleValue().toString();
if (v.toSymbolValue() != null) return '#' + v.toSymbolValue();
if (v.toTypeValue() != null) return v.toTypeValue().name;
if (v.toListValue() != null)
return 'const [' + v.toListValue().map(dartObjectToString).join(', ') + ']';
if (v.toMapValue() != null) {
return 'const {' +
v.toMapValue().entries.map((entry) {
var k = dartObjectToString(entry.key);
var v = dartObjectToString(entry.value);
return '$k: $v';
}).join(', ') +
'}';
}
if (v.toStringValue() != null) {
return literalString(v.toStringValue())
.accept(new DartEmitter())
.toString();
}
throw new ArgumentError(v.toString());
}
/// Determines if a type supports `package:angel_serialize`.
bool isModelClass(DartType t) {
if (t == null) return false;

View file

@ -67,6 +67,14 @@ Future<BuildContext> buildContext(
);
}
// Check for @DefaultValue()
var defAnn =
const TypeChecker.fromRuntime(DefaultValue).firstAnnotationOf(el);
if (defAnn != null) {
var rev = new ConstantReader(defAnn).revive().positionalArguments[0];
ctx.defaults[field.name] = rev;
}
// Check for alias
Alias alias;
var aliasAnn = aliasTypeChecker.firstAnnotationOf(el);

View file

@ -1,3 +1,4 @@
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:angel_serialize/angel_serialize.dart';
import 'package:code_builder/code_builder.dart';
@ -15,6 +16,9 @@ class BuildContext {
/// A map of field names to resolved names from `@Alias()` declarations.
final Map<String, String> aliases = {};
/// A map of field names to their default values.
final Map<String, DartObject> defaults = {};
/// A map of fields that have been marked as to be excluded from serialization.
final Map<String, Exclude> excluded = {};

View file

@ -96,6 +96,12 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
? 'List'
: 'Map';
var defaultValue = typeName == 'List' ? '[]' : '{}';
var existingDefault = ctx.defaults[field.name];
if (existingDefault != null) {
defaultValue = dartObjectToString(existingDefault);
}
constructor.initializers.add(new Code('''
this.${field.name} =
new $typeName.unmodifiable(${field.name} ?? $defaultValue)'''));
@ -109,6 +115,12 @@ class JsonModelGenerator extends GeneratorForAnnotation<Serializable> {
..name = field.name
..named = true;
var existingDefault = ctx.defaults[field.name];
if (existingDefault != null) {
b.defaultTo = new Code(dartObjectToString(existingDefault));
}
if (!isListOrMapType(field.type))
b.toThis = true;
else {

View file

@ -203,18 +203,27 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
String deserializedRepresentation =
"map['$alias'] as ${typeToString(field.type)}";
var defaultValue = 'null';
var existingDefault = ctx.defaults[field.name];
if (existingDefault != null) {
defaultValue = dartObjectToString(existingDefault);
deserializedRepresentation =
'$deserializedRepresentation ?? $defaultValue';
}
// Deserialize dates
if (dateTimeTypeChecker.isAssignableFromType(field.type))
deserializedRepresentation = "map['$alias'] != null ? "
"(map['$alias'] is DateTime ? (map['$alias'] as DateTime) : DateTime.parse(map['$alias'].toString()))"
" : null";
" : $defaultValue";
// Serialize model classes via `XSerializer.toMap`
else if (isModelClass(field.type)) {
var rc = new ReCase(field.type.name);
deserializedRepresentation = "map['$alias'] != null"
" ? ${rc.pascalCase}Serializer.fromMap(map['$alias'] as Map)"
" : null";
" : $defaultValue";
} else if (field.type is InterfaceType) {
var t = field.type as InterfaceType;
@ -224,7 +233,7 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
" ? new List.unmodifiable(((map['$alias'] as Iterable)"
".where((x) => x is Map) as Iterable<Map>)"
".map(${rc.pascalCase}Serializer.fromMap))"
" : null";
" : $defaultValue";
} else if (isMapToModelType(t)) {
var rc = new ReCase(t.typeArguments[1].name);
deserializedRepresentation = '''
@ -233,7 +242,7 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
return out..[key] = ${rc.pascalCase}Serializer
.fromMap(((map['$alias'] as Map)[key]) as Map);
}))
: null
: $defaultValue
''';
} else if (t.element.isEnum) {
deserializedRepresentation = '''
@ -243,7 +252,7 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
(
map['$alias'] is int
? ${t.name}.values[map['$alias'] as int]
: null
: $defaultValue
)
''';
} else if (const TypeChecker.fromRuntime(List)
@ -254,7 +263,7 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
deserializedRepresentation = '''
map['$alias'] is Iterable
? (map['$alias'] as Iterable).cast<$arg>().toList()
: null
: $defaultValue
''';
} else if (const TypeChecker.fromRuntime(Map)
.isAssignableFromType(t) &&
@ -266,7 +275,7 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
deserializedRepresentation = '''
map['$alias'] is Map
? (map['$alias'] as Map).cast<$key, $value>()
: null
: $defaultValue
''';
} else if (const TypeChecker.fromRuntime(Uint8List)
.isAssignableFromType(t)) {
@ -281,7 +290,7 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
(
map['$alias'] is String
? new Uint8List.fromList(base64.decode(map['$alias'] as String))
: null
: $defaultValue
)
)
''';

View file

@ -0,0 +1,24 @@
import 'package:test/test.dart';
import 'models/goat.dart';
void main() {
group('constructor', () {
test('int default', () {
expect(Goat().integer, 34);
});
test('list default', () {
expect(Goat().list, [34, 35]);
});
});
group('from map', () {
test('int default', () {
expect(GoatSerializer.fromMap({}).integer, 34);
});
test('list default', () {
expect(GoatSerializer.fromMap({}).list, [34, 35]);
});
});
}

View file

@ -3,7 +3,6 @@ import 'package:collection/collection.dart';
import 'game_pad_button.dart';
part 'game_pad.g.dart';
@Serializable(autoIdAndDateFields: false)
class _Gamepad {
List<GamepadButton> buttons;

View file

@ -0,0 +1,12 @@
import 'package:angel_serialize/angel_serialize.dart';
import 'package:collection/collection.dart';
part 'goat.g.dart';
@Serializable(autoIdAndDateFields: false)
abstract class _Goat {
@DefaultValue(34)
int get integer;
@DefaultValue([34, 35])
List<int> get list;
}

View file

@ -0,0 +1,67 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'goat.dart';
// **************************************************************************
// JsonModelGenerator
// **************************************************************************
@generatedSerializable
class Goat implements _Goat {
const Goat({this.integer: 34, List<int> this.list: const [34, 35]});
@override
final int integer;
@override
final List<int> list;
Goat copyWith({int integer, List<int> list}) {
return new Goat(integer: integer ?? this.integer, list: list ?? this.list);
}
bool operator ==(other) {
return other is _Goat &&
other.integer == integer &&
const ListEquality<int>(const DefaultEquality<int>())
.equals(other.list, list);
}
@override
int get hashCode {
return hashObjects([integer, list]);
}
Map<String, dynamic> toJson() {
return GoatSerializer.toMap(this);
}
}
// **************************************************************************
// SerializerGenerator
// **************************************************************************
abstract class GoatSerializer {
static Goat fromMap(Map map) {
return new Goat(
integer: map['integer'] as int ?? 34,
list: map['list'] is Iterable
? (map['list'] as Iterable).cast<int>().toList()
: const [34, 35]);
}
static Map<String, dynamic> toMap(_Goat model) {
if (model == null) {
return null;
}
return {'integer': model.integer, 'list': model.list};
}
}
abstract class GoatFields {
static const List<String> allFields = const <String>[integer, list];
static const String integer = 'integer';
static const String list = 'list';
}