2.2.0 and 2.4.0
This commit is contained in:
parent
4dc0153cbf
commit
c29b745f37
15 changed files with 177 additions and 19 deletions
19
README.md
19
README.md
|
@ -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:
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# 2.2.0
|
||||||
|
* Add `@SerializableField`.
|
||||||
|
|
||||||
# 2.1.0
|
# 2.1.0
|
||||||
* Export `hashObjects`.
|
* Export `hashObjects`.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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}.";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
16
angel_serialize_generator/test/models/has_map.dart
Normal file
16
angel_serialize_generator/test/models/has_map.dart
Normal 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;
|
||||||
|
}
|
66
angel_serialize_generator/test/models/has_map.g.dart
Normal file
66
angel_serialize_generator/test/models/has_map.g.dart
Normal 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';
|
||||||
|
}
|
18
angel_serialize_generator/test/serializer_test.dart
Normal file
18
angel_serialize_generator/test/serializer_test.dart
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue