2.2.0 and 2.4.0

This commit is contained in:
Tobe O 2019-01-06 20:38:04 -05:00
parent 4dc0153cbf
commit c29b745f37
15 changed files with 177 additions and 19 deletions

View file

@ -13,6 +13,7 @@ the time you spend writing boilerplate serialization code for your models.
- [Excluding Keys](#excluding-keys) - [Excluding Keys](#excluding-keys)
- [Required Fields](#required-fields) - [Required Fields](#required-fields)
- [Adding Annotations to Generated Classes](#adding-annotations-to-generated-classes) - [Adding Annotations to Generated Classes](#adding-annotations-to-generated-classes)
- [Custom Serializers](#custom-serializers)
- [Serialization](#serializaition) - [Serialization](#serializaition)
- [Nesting](#nesting) - [Nesting](#nesting)
- [ID and Date Fields](#id-and-dates) - [ID and Date Fields](#id-and-dates)
@ -247,6 +248,24 @@ There are times when you need the generated class to have annotations affixed to
abstract class _Foo extends Model {} abstract class _Foo extends Model {}
``` ```
## Custom Serializers
`package:angel_serialize` does not cover every known Dart data type; you can add support for your own.
Provide `serializer` and `deserializer` arguments to `@SerializableField()` as you see fit.
They are typically used together. Note that the argument to `serializer` will always be
`dynamic`.
```dart
DateTime _dateFromString(s) => s is String ? HttpDate.parse(s) : null;
String _dateToString(v) => v == null ? null : HttpDate.format(v);
@Serializable(autoIdAndDateFields: false)
abstract class _HttpRequest {
@SerializableField(serializer: #_dateToString, deserializer: #_dateFromString)
DateTime date;
}
```
# Nesting # Nesting
`angel_serialize` also supports a few types of nesting of `@serializable` classes: `angel_serialize` also supports a few types of nesting of `@serializable` classes:

View file

@ -1,3 +1,6 @@
# 2.2.0
* Add `@SerializableField`.
# 2.1.0 # 2.1.0
* Export `hashObjects`. * Export `hashObjects`.

View file

@ -1,5 +1,5 @@
name: angel_serialize name: angel_serialize
version: 2.1.0 version: 2.2.0
description: Static annotations powering Angel model serialization. Combine with angel_serialize_generator for flexible modeling. description: Static annotations powering Angel model serialization. Combine with angel_serialize_generator for flexible modeling.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/serialize homepage: https://github.com/angel-dart/serialize

View file

@ -1,3 +1,9 @@
# 2.4.0
* Introduce `@SerializableField`, and say goodbye to annotation hell.
* Support custom (de)serializers.
* Allow passing of annotations to the generated class.
* Fixted TypeScript `ref` generator.
# 2.3.0 # 2.3.0
* Add `@DefaultValue` support. * Add `@DefaultValue` support.

View file

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

View file

@ -1,6 +1,7 @@
library angel_serialize_generator; library angel_serialize_generator;
import 'dart:async'; import 'dart:async';
import 'dart:mirrors';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';

View file

@ -28,6 +28,8 @@ const TypeChecker serializableTypeChecker =
const TypeChecker generatedSerializableTypeChecker = const TypeChecker generatedSerializableTypeChecker =
const TypeChecker.fromRuntime(GeneratedSerializable); const TypeChecker.fromRuntime(GeneratedSerializable);
final Map<String, BuildContext> _cache = {};
/// Create a [BuildContext]. /// Create a [BuildContext].
Future<BuildContext> buildContext( Future<BuildContext> buildContext(
ClassElement clazz, ClassElement clazz,
@ -37,6 +39,11 @@ Future<BuildContext> buildContext(
bool autoSnakeCaseNames, bool autoSnakeCaseNames,
bool autoIdAndDateFields, bool autoIdAndDateFields,
{bool heedExclude: true}) async { {bool heedExclude: true}) async {
var id = clazz.location.components.join('-');
if (_cache.containsKey(id)) {
return _cache[id];
}
// Check for autoIdAndDateFields, autoSnakeCaseNames // Check for autoIdAndDateFields, autoSnakeCaseNames
autoIdAndDateFields = autoIdAndDateFields =
annotation.peek('autoIdAndDateFields')?.boolValue ?? autoIdAndDateFields; annotation.peek('autoIdAndDateFields')?.boolValue ?? autoIdAndDateFields;
@ -155,6 +162,8 @@ Future<BuildContext> buildContext(
const TypeChecker.fromRuntime(Required).firstAnnotationOf(el); const TypeChecker.fromRuntime(Required).firstAnnotationOf(el);
if (required != null) { if (required != null) {
log.warning(
'Using @required on fields (like ${clazz.name}.${field.name}) is now deprecated; use @SerializableField(isNullable: false) instead.');
var cr = new ConstantReader(required); var cr = new ConstantReader(required);
var reason = cr.peek('reason')?.stringValue ?? var reason = cr.peek('reason')?.stringValue ??
"Missing required field '${ctx.resolveFieldName(field.name)}' on ${ctx.modelClassName}."; "Missing required field '${ctx.resolveFieldName(field.name)}' on ${ctx.modelClassName}.";

View file

@ -96,8 +96,13 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
return '${rc.pascalCase}Serializer.toMap($value)'; return '${rc.pascalCase}Serializer.toMap($value)';
} }
if (ctx.fieldInfo[field.name]?.serializer != null) {
var name = MirrorSystem.getName(ctx.fieldInfo[field.name].serializer);
serializedRepresentation = '$name(model.${field.name})';
}
// Serialize dates // Serialize dates
if (dateTimeTypeChecker.isAssignableFromType(field.type)) else if (dateTimeTypeChecker.isAssignableFromType(field.type))
serializedRepresentation = 'model.${field.name}?.toIso8601String()'; serializedRepresentation = 'model.${field.name}?.toIso8601String()';
// Serialize model classes via `XSerializer.toMap` // Serialize model classes via `XSerializer.toMap`
@ -212,8 +217,11 @@ class SerializerGenerator extends GeneratorForAnnotation<Serializable> {
'$deserializedRepresentation ?? $defaultValue'; '$deserializedRepresentation ?? $defaultValue';
} }
// Deserialize dates if (ctx.fieldInfo[field.name]?.deserializer != null) {
if (dateTimeTypeChecker.isAssignableFromType(field.type)) var name =
MirrorSystem.getName(ctx.fieldInfo[field.name].deserializer);
deserializedRepresentation = "$name(map['$alias'])";
} else if (dateTimeTypeChecker.isAssignableFromType(field.type))
deserializedRepresentation = "map['$alias'] != null ? " deserializedRepresentation = "map['$alias'] != null ? "
"(map['$alias'] is DateTime ? (map['$alias'] as DateTime) : DateTime.parse(map['$alias'].toString()))" "(map['$alias'] is DateTime ? (map['$alias'] as DateTime) : DateTime.parse(map['$alias'].toString()))"
" : $defaultValue"; " : $defaultValue";

View file

@ -79,15 +79,26 @@ class TypeScriptDefinitionBuilder implements Builder {
var targetPath = type.element.source.uri.toString(); var targetPath = type.element.source.uri.toString();
if (!p.equals(sourcePath, targetPath)) { if (!p.equals(sourcePath, targetPath)) {
//var relative = p.relative(targetPath, from: sourcePath); var relative = p.relative(targetPath, from: sourcePath);
var relative = (p.dirname(targetPath) == p.dirname(sourcePath)) String ref;
? p.basename(targetPath)
: p.relative(targetPath, from: sourcePath); if (type.element.source.uri.scheme == 'asset') {
var id = AssetId.resolve(type.element.source.uri.toString());
if (id.package != buildStep.inputId.package) {
ref = '/// <reference types="${id.package}" />';
}
}
if (ref == null) {
// var relative = (p.dirname(targetPath) == p.dirname(sourcePath))
// ? p.basename(targetPath)
// : p.relative(targetPath, from: sourcePath);
var parent = p.dirname(relative); var parent = p.dirname(relative);
var filename = var filename =
p.setExtension(p.basenameWithoutExtension(relative), '.d.ts'); p.setExtension(p.basenameWithoutExtension(relative), '.d.ts');
relative = p.joinAll(p.split(parent).toList()..add(filename)); relative = p.joinAll(p.split(parent).toList()..add(filename));
var ref = '/// <reference path="$relative" />'; ref = '/// <reference path="$relative" />';
}
if (!refs.contains(ref)) refs.add(ref); if (!refs.contains(ref)) refs.add(ref);
} }

View file

@ -1,5 +1,5 @@
name: angel_serialize_generator name: angel_serialize_generator
version: 2.3.0 version: 2.4.0
description: Model serialization generators, designed for use with Angel. Combine with angel_serialize for flexible modeling. description: Model serialization generators, designed for use with Angel. Combine with angel_serialize for flexible modeling.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/serialize homepage: https://github.com/angel-dart/serialize

View file

@ -1,4 +1,4 @@
/// <reference path="./book.d.ts" /> /// <reference path="../book.d.ts" />
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
declare module 'angel_serialize_generator' { declare module 'angel_serialize_generator' {
interface Author { interface Author {

View file

@ -9,12 +9,13 @@ part 'author.g.dart';
@Serializable(serializers: Serializers.all) @Serializable(serializers: Serializers.all)
abstract class _Author extends Model { abstract class _Author extends Model {
@required @SerializableField(isNullable: false)
String get name; String get name;
String get customMethod => 'hey!'; String get customMethod => 'hey!';
@Required('Custom message for missing `age`') @SerializableField(
isNullable: false, errorMessage: 'Custom message for missing `age`')
int get age; int get age;
List<Book> get books; List<Book> get books;
@ -35,13 +36,12 @@ abstract class _Library extends Model {
@Serializable(serializers: Serializers.all) @Serializable(serializers: Serializers.all)
abstract class _Bookmark extends Model { abstract class _Bookmark extends Model {
@SerializableField(exclude: true) @SerializableField(exclude: true)
final Book book; final Book book;
List<int> get history; List<int> get history;
@required @SerializableField(isNullable: false)
int get page; int get page;
String get comment; String get comment;

View file

@ -0,0 +1,16 @@
import 'dart:convert';
import 'package:angel_serialize/angel_serialize.dart';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
part 'has_map.g.dart';
Map _fromString(v) => json.decode(v.toString()) as Map;
String _toString(Map v) => json.encode(v);
@Serializable(autoIdAndDateFields: false)
abstract class _HasMap {
@SerializableField(
serializer: #_toString, deserializer: #_fromString, isNullable: false)
Map get value;
}

View file

@ -0,0 +1,66 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'has_map.dart';
// **************************************************************************
// JsonModelGenerator
// **************************************************************************
@generatedSerializable
class HasMap implements _HasMap {
const HasMap({@required Map<dynamic, dynamic> this.value});
@override
final Map<dynamic, dynamic> value;
HasMap copyWith({Map<dynamic, dynamic> value}) {
return new HasMap(value: value ?? this.value);
}
bool operator ==(other) {
return other is _HasMap &&
const MapEquality<dynamic, dynamic>(
keys: const DefaultEquality(), values: const DefaultEquality())
.equals(other.value, value);
}
@override
int get hashCode {
return hashObjects([value]);
}
Map<String, dynamic> toJson() {
return HasMapSerializer.toMap(this);
}
}
// **************************************************************************
// SerializerGenerator
// **************************************************************************
abstract class HasMapSerializer {
static HasMap fromMap(Map map) {
if (map['value'] == null) {
throw new FormatException("Missing required field 'value' on HasMap.");
}
return new HasMap(value: _fromString(map['value']));
}
static Map<String, dynamic> toMap(_HasMap model) {
if (model == null) {
return null;
}
if (model.value == null) {
throw new FormatException("Missing required field 'value' on HasMap.");
}
return {'value': _toString(model.value)};
}
}
abstract class HasMapFields {
static const List<String> allFields = const <String>[value];
static const String value = 'value';
}

View file

@ -0,0 +1,18 @@
import 'dart:convert';
import 'package:test/test.dart';
import 'models/has_map.dart';
void main() {
var m = HasMap(value: {'foo': 'bar'});
print(json.encode(m));
test('json', () {
expect(json.encode(m), r'{"value":"{\"foo\":\"bar\"}"}');
});
test('decode', () {
var mm = json.decode(r'{"value":"{\"foo\":\"bar\"}"}') as Map;
var mmm = HasMapSerializer.fromMap(mm);
expect(mmm, m);
});
}