diff --git a/CHANGELOG.md b/CHANGELOG.md index ca3ae24..3d03358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.0.2 + +- Rewrite to fully use static type information. + ## 0.0.1 - Generator, tests and example. diff --git a/built_json/lib/built_json.dart b/built_json/lib/built_json.dart index d580693..10a463a 100644 --- a/built_json/lib/built_json.dart +++ b/built_json/lib/built_json.dart @@ -5,6 +5,7 @@ library built_json; import 'src/bool_serializer.dart'; +import 'src/built_json_serializers.dart'; import 'src/built_list_serializer.dart'; import 'src/built_map_serializer.dart'; import 'src/built_set_serializer.dart'; @@ -12,121 +13,115 @@ import 'src/double_serializer.dart'; import 'src/int_serializer.dart'; import 'src/string_serializer.dart'; -/// Serializes a single class. -/// -/// See -abstract class BuiltJsonSerializer { - Type get type; - String get typeName; - - Object serialize(BuiltJsonSerializers builtJsonSerializers, T object, - {String expectedType}); - T deserialize(BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}); -} +export 'package:built_collection/built_collection.dart' show BuiltList; -/// Serializes all transitive dependencies of a class. +/// Serializes all known classes. /// /// See -// TODO(davidmorgan): make immutable. -class BuiltJsonSerializers { - Map _deobfuscatedNames = {}; - Map _serializersByName = - {}; - - BuiltJsonSerializers() { - add(new BoolSerializer()); - add(new DoubleSerializer()); - add(new IntSerializer()); - add(new StringSerializer()); - - add(new BuiltListSerializer()); - add(new BuiltMapSerializer()); - add(new BuiltSetSerializer()); +abstract class Serializers { + /// Default [Serializers] that can serialize primitives and collections. + /// + /// Use [toBuilder] to add more serializers. + factory Serializers() { + return (new SerializersBuilder() + ..add(new BoolSerializer()) + ..add(new BuiltListSerializer()) + ..add(new BuiltMapSerializer()) + ..add(new BuiltSetSerializer()) + ..add(new DoubleSerializer()) + ..add(new IntSerializer()) + ..add(new StringSerializer())).build(); } - void addAll(BuiltJsonSerializers builtJsonSerializers) { - for (final serializer in builtJsonSerializers._serializersByName.values) { - add(serializer); - } - } + /// Serializes [object]. + /// + /// A [Serializer] must have been provided for every the object uses. + /// + /// Types that are known statically can be provided via [genericType]. This + /// will reduce the amount of data needed on the wire. The exact same + /// [genericType] will be needed to deserialize. + /// + /// Create one using [SerializersBuilder]. + Object serialize(Object object, + {GenericType genericType: const GenericType()}); + + /// Deserializes [serialized]. + /// + /// A [Serializer] must have been provided for every the object uses. + /// + /// If [serialized] was produced by calling [serialize] with [genericType], + /// the exact same [genericType] must be provided to deserialize. + Object deserialize(Object serialized, + {GenericType genericType: const GenericType()}); + + /// Creates a new builder for the type represented by [genericType]. + /// + /// For example, if [genericType] is `BuiltList`, returns a + /// `ListBuilder`. This helps serializers to instantiate with + /// correct generic type parameters. + /// + /// May return null if no matching builder factory has been added. In this + /// case the serializer should fall back to `Object`. + Object newBuilder(GenericType genericType); + + SerializersBuilder toBuilder(); +} - void add(BuiltJsonSerializer builtJsonSerializer) { - _deobfuscatedNames[_getName(builtJsonSerializer.type)] = - builtJsonSerializer.typeName; - _serializersByName[builtJsonSerializer.typeName] = builtJsonSerializer; - } +/// Builder for [Serializers]. +abstract class SerializersBuilder { + factory SerializersBuilder() = BuiltJsonSerializersBuilder; - Object serialize(Object object, {String expectedType}) { - final rawName = _deobfuscatedNames[_getName(object.runtimeType)]; - if (rawName == null) throw new StateError( - "No serializer for '${object.runtimeType}'."); - - var genericType = _getGenericName(object.runtimeType); - - // TODO(davidmorgan): handle this generically. - if (genericType == 'BuiltList') { - genericType = 'List'; - } - if (genericType == 'BuiltSet') { - genericType = 'Set'; - } - - final genericName = - genericType == null ? rawName : '$rawName<$genericType>'; - - if (genericName == expectedType) { - return _serializersByName[rawName] - .serialize(this, object, expectedType: genericType); - } else { - return { - genericName: _serializersByName[rawName] - .serialize(this, object, expectedType: genericType) - }; - } - } + void add(Serializer serializer); - Object deserialize(Object object, {String expectedType}) { - if (object is Map) { - if (object.keys.length > 1) { - // Must be expectedType. - // TODO(davidmorgan): distinguish in the one field case. - if (expectedType == null) { - throw new StateError('Need an expected type here.'); - } - final typeName = _makeRaw(expectedType); - final genericName = _getGeneric(expectedType); - return _serializersByName[typeName] - .deserialize(this, object, expectedType: genericName); - } else { - final typeName = _makeRaw(object.keys.single); - final genericName = _getGeneric(object.keys.single); - return _serializersByName[typeName] - .deserialize(this, object.values.single, expectedType: genericName); - } - } else { - final serializer = _serializersByName[_makeRaw(expectedType)]; - if (serializer == null) { - throw new StateError('No serializer for $expectedType'); - } - return serializer.deserialize(this, object, - expectedType: _getGeneric(expectedType)); - } - } + void addBuilderFactory(GenericType genericType, Function function); - String _getName(Type type) => _makeRaw(type.toString()); + Serializers build(); +} - String _makeRaw(String name) { - final genericsStart = name.indexOf('<'); - return genericsStart == -1 ? name : name.substring(0, genericsStart); - } +/// A tree of [Type] instances. +class GenericType { + /// The root of the type. + final Type root; - String _getGenericName(Type type) => _getGeneric(type.toString()); + /// Type parameters of the type. + final List leaves; - String _getGeneric(String name) { - final genericsStart = name.indexOf('<'); - return genericsStart == -1 - ? null - : name.substring(genericsStart + 1, name.length - 1); - } + const GenericType([this.root = Object, this.leaves = const []]); + + bool get isObject => root == Object; +} + +/// Serializes a single type. +/// +/// You should not usually need to implement this interface. Implementations +/// are provided for collections and primitives in `built_json`. Classes using +/// `built_value` and enums using `EnumClass` can have implementations +/// generated using `built_json_generator`. +abstract class Serializer { + /// Whether the serialized format for this type is structured or primitive. + bool get structured; + + /// The [Type]s that can be serialized. + /// + /// They must all be equal to T or subclasses of T. + Iterable get types; + + /// The wire name of the serializable type. For most classes, the class name. + /// For primitives and collections a lower-case name is defined as part of + /// the `built_json` wire format. + String get wireName; + + /// Serializes [object]. + /// + /// Use [serializers] as needed for nested serialization. Information about + /// the type being serialized is provided in [genericType]. + Object serialize(Serializers serializers, T object, + {GenericType genericType: const GenericType()}); + + /// Deserializes [serialized]. + /// + /// Use [serializers] as needed for nested deserialization. Information about + /// the type being deserialized is provided in [genericType]. + T deserialize(Serializers serializers, Object serialized, + {GenericType genericType: const GenericType()}); } diff --git a/built_json/lib/src/bool_serializer.dart b/built_json/lib/src/bool_serializer.dart index 4882602..473cd5f 100644 --- a/built_json/lib/src/bool_serializer.dart +++ b/built_json/lib/src/bool_serializer.dart @@ -2,19 +2,23 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +import 'package:built_collection/built_collection.dart'; import 'package:built_json/built_json.dart'; -class BoolSerializer implements BuiltJsonSerializer { - final Type type = bool; - final String typeName = 'bool'; +class BoolSerializer implements Serializer { + final bool structured = false; + final Iterable types = new BuiltList([bool]); + final String wireName = 'bool'; - Object serialize(BuiltJsonSerializers builtJsonSerializers, bool object, - {String expectedType}) { + @override + Object serialize(Serializers serializers, bool object, + {GenericType genericType: const GenericType()}) { return object; } - bool deserialize(BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { + @override + bool deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { return object as bool; } } diff --git a/built_json/lib/src/built_json_serializers.dart b/built_json/lib/src/built_json_serializers.dart new file mode 100644 index 0000000..31ff4af --- /dev/null +++ b/built_json/lib/src/built_json_serializers.dart @@ -0,0 +1,131 @@ +// Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import 'package:built_collection/built_collection.dart'; +import 'package:built_json/built_json.dart'; + +/// Default implementation of [Serializers]. +class BuiltJsonSerializers implements Serializers { + final BuiltMap _typeToSerializer; + final BuiltMap _wireNameToSerializer; + final BuiltMap _typeNameToSerializer; + + final BuiltMap _builderFactories; + + BuiltJsonSerializers._(this._typeToSerializer, this._wireNameToSerializer, + this._typeNameToSerializer, this._builderFactories); + + @override + Object serialize(Object object, + {GenericType genericType: const GenericType()}) { + if (genericType.isObject) { + final serializer = _getSerializerByType(object.runtimeType); + if (serializer == null) throw new StateError( + "No serializer for '${object.runtimeType}'."); + final serialized = serializer.serialize(this, object); + + if (serializer.structured) { + final result = [serializer.wireName]; + return result..addAll(serialized as Iterable); + } else { + return [serializer.wireName, serialized]; + } + } else { + final serializer = _getSerializerByType(genericType.root); + if (serializer == null) throw new StateError( + "No serializer for '${genericType.root}'."); + final result = + serializer.serialize(this, object, genericType: genericType); + return serializer.structured ? (result as Iterable).toList() : result; + } + } + + @override + Object deserialize(Object object, + {GenericType genericType: const GenericType()}) { + if (genericType.isObject) { + final wireName = (object as List).first; + + final serializer = _wireNameToSerializer[wireName]; + if (serializer == + null) throw new StateError("No serializer for '${wireName}'."); + final json = serializer.structured + ? (object as List).sublist(1) + : (object as List)[1]; + return serializer.deserialize(this, json); + } else { + final serializer = _getSerializerByType(genericType.root); + if (serializer == null) throw new StateError( + "No serializer for '${genericType.root}'."); + return serializer.deserialize(this, object, genericType: genericType); + } + } + + @override + Object newBuilder(types) { + final builderFactory = _builderFactories[types]; + return builderFactory == null ? null : builderFactory(); + } + + @override + SerializersBuilder toBuilder() { + return new BuiltJsonSerializersBuilder._( + _typeToSerializer.toBuilder(), + _wireNameToSerializer.toBuilder(), + _typeNameToSerializer.toBuilder(), + _builderFactories.toBuilder()); + } + + Serializer _getSerializerByType(Type type) { + return _typeToSerializer[type] ?? _typeNameToSerializer[_getName(type)]; + } +} + +/// Default implementation of [SerializersBuilder]. +class BuiltJsonSerializersBuilder implements SerializersBuilder { + MapBuilder _typeToSerializer = + new MapBuilder(); + MapBuilder _wireNameToSerializer = + new MapBuilder(); + MapBuilder _typeNameToSerializer = + new MapBuilder(); + + MapBuilder _builderFactories = + new MapBuilder(); + + BuiltJsonSerializersBuilder(); + + BuiltJsonSerializersBuilder._( + this._typeToSerializer, + this._wireNameToSerializer, + this._typeNameToSerializer, + this._builderFactories); + + void add(Serializer serializer) { + _wireNameToSerializer[serializer.wireName] = serializer; + for (final type in serializer.types) { + _typeToSerializer[type] = serializer; + _typeNameToSerializer[_getName(type)] = serializer; + } + } + + void addBuilderFactory(GenericType types, Function function) { + _builderFactories[types] = function; + } + + Serializers build() { + return new BuiltJsonSerializers._( + _typeToSerializer.build(), + _wireNameToSerializer.build(), + _typeNameToSerializer.build(), + _builderFactories.build()); + } +} + +String _getName(Type type) => _makeRaw(type.toString()); + +String _makeRaw(String name) { + final genericsStart = name.indexOf('<'); + return genericsStart == -1 ? name : name.substring(0, genericsStart); +} diff --git a/built_json/lib/src/built_list_serializer.dart b/built_json/lib/src/built_list_serializer.dart index 0ed14b2..3efa995 100644 --- a/built_json/lib/src/built_list_serializer.dart +++ b/built_json/lib/src/built_list_serializer.dart @@ -5,22 +5,33 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_json/built_json.dart'; -class BuiltListSerializer implements BuiltJsonSerializer { - final Type type = BuiltList; - final String typeName = 'List'; +class BuiltListSerializer implements Serializer { + final bool structured = true; + final Iterable types = new BuiltList([BuiltList]); + final String wireName = 'list'; - Object serialize(BuiltJsonSerializers builtJsonSerializers, BuiltList object, - {String expectedType}) { - return object - .map((item) => - builtJsonSerializers.serialize(item, expectedType: expectedType)) - .toList(); + @override + Object serialize(Serializers serializers, BuiltList object, + {GenericType genericType: const GenericType()}) { + final valueGenericType = genericType.leaves.isEmpty + ? const GenericType() + : genericType.leaves[0]; + + return object.map( + (item) => serializers.serialize(item, genericType: valueGenericType)); } - BuiltList deserialize( - BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { - return new BuiltList((object as Iterable).map((item) => - builtJsonSerializers.deserialize(item, expectedType: expectedType))); + @override + BuiltList deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + final valueGenericType = genericType.leaves.isEmpty + ? const GenericType() + : genericType.leaves[0]; + + final result = serializers.newBuilder(genericType) as ListBuilder ?? + new ListBuilder(); + result.addAll((object as Iterable).map((item) => + serializers.deserialize(item, genericType: valueGenericType))); + return result.build(); } } diff --git a/built_json/lib/src/built_map_serializer.dart b/built_json/lib/src/built_map_serializer.dart index 9075d3d..ec9b91b 100644 --- a/built_json/lib/src/built_map_serializer.dart +++ b/built_json/lib/src/built_map_serializer.dart @@ -5,36 +5,42 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_json/built_json.dart'; -class BuiltMapSerializer implements BuiltJsonSerializer { - final Type type = BuiltMap; - final String typeName = 'Map'; - - Object serialize(BuiltJsonSerializers builtJsonSerializers, BuiltMap object, - {String expectedType}) { - final expectedKeyType = - expectedType.substring(0, expectedType.indexOf(', ')); - final expectedValueType = - expectedType.substring(expectedType.indexOf(', ') + 2); +class BuiltMapSerializer implements Serializer { + final bool structured = true; + final Iterable types = new BuiltList([BuiltMap]); + final String wireName = 'map'; + + @override + Object serialize(Serializers serializers, BuiltMap object, + {GenericType genericType: const GenericType()}) { + final keyTypes = genericType.leaves.isEmpty + ? const GenericType() + : genericType.leaves[0]; + final valueTypes = genericType.leaves.isEmpty + ? const GenericType() + : genericType.leaves[1]; final result = []; for (final key in object.keys) { - result.add( - builtJsonSerializers.serialize(key, expectedType: expectedKeyType)); + result.add(serializers.serialize(key, genericType: keyTypes)); final value = object[key]; - result.add(builtJsonSerializers.serialize(value, - expectedType: expectedValueType)); + result.add(serializers.serialize(value, genericType: valueTypes)); } return result; } - BuiltMap deserialize(BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { - final expectedKeyType = - expectedType.substring(0, expectedType.indexOf(', ')); - final expectedValueType = - expectedType.substring(expectedType.indexOf(', ') + 2); - - final result = new MapBuilder(); + @override + BuiltMap deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + final keyTypes = genericType.leaves.isEmpty + ? const GenericType() + : genericType.leaves[0]; + final valueTypes = genericType.leaves.isEmpty + ? const GenericType() + : genericType.leaves[1]; + + final result = serializers.newBuilder(genericType) as MapBuilder ?? + new MapBuilder(); final list = object as List; if (list.length & 1 == 1) { @@ -42,10 +48,9 @@ class BuiltMapSerializer implements BuiltJsonSerializer { } for (int i = 0; i != list.length; i += 2) { - final key = builtJsonSerializers.deserialize(list[i], - expectedType: expectedKeyType); - final value = builtJsonSerializers.deserialize(list[i + 1], - expectedType: expectedValueType); + final key = serializers.deserialize(list[i], genericType: keyTypes); + final value = + serializers.deserialize(list[i + 1], genericType: valueTypes); result[key] = value; } diff --git a/built_json/lib/src/built_set_serializer.dart b/built_json/lib/src/built_set_serializer.dart index b8658ea..0174c4a 100644 --- a/built_json/lib/src/built_set_serializer.dart +++ b/built_json/lib/src/built_set_serializer.dart @@ -5,21 +5,33 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_json/built_json.dart'; -class BuiltSetSerializer implements BuiltJsonSerializer { - final Type type = BuiltSet; - final String typeName = 'Set'; +class BuiltSetSerializer implements Serializer { + final bool structured = true; + final Iterable types = new BuiltList([BuiltSet]); + final String wireName = 'set'; - Object serialize(BuiltJsonSerializers builtJsonSerializers, BuiltSet object, - {String expectedType}) { - return object - .map((item) => - builtJsonSerializers.serialize(item, expectedType: expectedType)) - .toList(); + @override + Object serialize(Serializers serializers, BuiltSet object, + {GenericType genericType: const GenericType()}) { + final valueGenericType = genericType.leaves.isEmpty + ? const GenericType() + : genericType.leaves[0]; + + return object.map( + (item) => serializers.serialize(item, genericType: valueGenericType)); } - BuiltSet deserialize(BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { - return new BuiltSet((object as Iterable).map((item) => - builtJsonSerializers.deserialize(item, expectedType: expectedType))); + @override + BuiltSet deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + final valueGenericType = genericType.leaves.isEmpty + ? const GenericType() + : genericType.leaves[0]; + + final result = serializers.newBuilder(genericType) as SetBuilder ?? + new SetBuilder(); + result.addAll((object as Iterable).map((item) => + serializers.deserialize(item, genericType: valueGenericType))); + return result.build(); } } diff --git a/built_json/lib/src/double_serializer.dart b/built_json/lib/src/double_serializer.dart index fb0fc6c..d84d54c 100644 --- a/built_json/lib/src/double_serializer.dart +++ b/built_json/lib/src/double_serializer.dart @@ -2,20 +2,24 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +import 'package:built_collection/built_collection.dart'; import 'package:built_json/built_json.dart'; // TODO(davidmorgan): support special values. -class DoubleSerializer implements BuiltJsonSerializer { - final Type type = double; - final String typeName = 'double'; +class DoubleSerializer implements Serializer { + final bool structured = false; + final Iterable types = new BuiltList([double]); + final String wireName = 'double'; - Object serialize(BuiltJsonSerializers builtJsonSerializers, double object, - {String expectedType}) { + @override + Object serialize(Serializers serializers, double object, + {GenericType genericType: const GenericType()}) { return object.toString(); } - double deserialize(BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { + @override + double deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { return double.parse(object as String); } } diff --git a/built_json/lib/src/int_serializer.dart b/built_json/lib/src/int_serializer.dart index c6efc94..a3ad828 100644 --- a/built_json/lib/src/int_serializer.dart +++ b/built_json/lib/src/int_serializer.dart @@ -2,19 +2,23 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +import 'package:built_collection/built_collection.dart'; import 'package:built_json/built_json.dart'; -class IntSerializer implements BuiltJsonSerializer { - final Type type = int; - final String typeName = 'int'; +class IntSerializer implements Serializer { + final bool structured = false; + final Iterable types = new BuiltList([int]); + final String wireName = 'int'; - Object serialize(BuiltJsonSerializers builtJsonSerializers, int object, - {String expectedType}) { + @override + Object serialize(Serializers serializers, int object, + {GenericType genericType: const GenericType()}) { return object; } - int deserialize(BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { + @override + int deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { return object as int; } } diff --git a/built_json/lib/src/string_serializer.dart b/built_json/lib/src/string_serializer.dart index 1920f5e..e305e3f 100644 --- a/built_json/lib/src/string_serializer.dart +++ b/built_json/lib/src/string_serializer.dart @@ -2,19 +2,23 @@ // All rights reserved. Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +import 'package:built_collection/built_collection.dart'; import 'package:built_json/built_json.dart'; -class StringSerializer implements BuiltJsonSerializer { - final Type type = String; - final String typeName = 'String'; +class StringSerializer implements Serializer { + final bool structured = false; + final Iterable types = new BuiltList([String]); + final String wireName = 'String'; - Object serialize(BuiltJsonSerializers builtJsonSerializers, String object, - {String expectedType}) { + @override + Object serialize(Serializers serializers, String object, + {GenericType genericType: const GenericType()}) { return object; } - String deserialize(BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { + @override + String deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { return object as String; } } diff --git a/built_json/pubspec.yaml b/built_json/pubspec.yaml index 2ce26da..8c5fe17 100644 --- a/built_json/pubspec.yaml +++ b/built_json/pubspec.yaml @@ -1,5 +1,5 @@ name: built_json -version: 0.0.1 +version: 0.0.2 description: > JSON serialization for Built Collections, Built Values and Enum Classes. This library is the runtime dependency. diff --git a/built_json/test/bool_serializer_test.dart b/built_json/test/bool_serializer_test.dart index 18b8068..36af69f 100644 --- a/built_json/test/bool_serializer_test.dart +++ b/built_json/test/bool_serializer_test.dart @@ -6,15 +6,35 @@ import 'package:built_json/built_json.dart'; import 'package:test/test.dart'; void main() { - final serializer = new BuiltJsonSerializers(); + final serializers = new Serializers(); + + group('bool with known genericType', () { + final data = true; + final serialized = true; + final genericType = const GenericType(bool); + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + }); + + group('bool with unknown genericType', () { + final data = true; + final serialized = ['bool', true]; + final genericType = const GenericType(); - group('bool', () { test('can be serialized', () { - expect(serializer.serialize(true), {'bool': true}); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - expect(serializer.deserialize(serializer.serialize(true)), true); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); } diff --git a/built_json/test/built_list_serializer_test.dart b/built_json/test/built_list_serializer_test.dart index 276c802..75ed78c 100644 --- a/built_json/test/built_list_serializer_test.dart +++ b/built_json/test/built_list_serializer_test.dart @@ -7,45 +7,87 @@ import 'package:built_json/built_json.dart'; import 'package:test/test.dart'; void main() { - final serializer = new BuiltJsonSerializers(); + final serializers = new Serializers(); + + group('BuiltList with known genericType', () { + final data = new BuiltList([1, 2, 3]); + final genericType = + const GenericType(BuiltList, const [const GenericType(int)]); + final serialized = [1, 2, 3]; + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + + test('loses generic type without builder', () { + expect( + serializers + .deserialize(serialized, genericType: genericType) + .runtimeType + .toString(), + 'BuiltList'); + }); + + test('keeps generic type with builder', () { + final genericSerializer = (serializers.toBuilder() + ..addBuilderFactory(genericType, () => new ListBuilder())).build(); + + expect( + genericSerializer + .deserialize(serialized, genericType: genericType) + .runtimeType + .toString(), + 'BuiltList'); + }); + }); + + group('BuiltList nested with known genericType', () { + final data = new BuiltList>([ + new BuiltList([1, 2, 3]), + new BuiltList([4, 5, 6]), + new BuiltList([7, 8, 9]) + ]); + final genericType = const GenericType(BuiltList, const [ + const GenericType(BuiltList, const [const GenericType(int)]) + ]); + final serialized = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ]; + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + }); + + group('BuiltList with unknown genericType', () { + final data = new BuiltList([1, 2, 3]); + final genericType = const GenericType(); + final serialized = [ + 'list', + ['int', 1], + ['int', 2], + ['int', 3] + ]; - group('BuiltList', () { test('can be serialized', () { - final list = new BuiltList([1, 2, 3]); - expect(serializer.serialize(list), { - 'List': [1, 2, 3] - }); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - final list = new BuiltList([1, 2, 3]); - expect(serializer.deserialize(serializer.serialize(list)), list); - }); - - test('can be serialized when nested', () { - final list = new BuiltList>([ - new BuiltList([1, 2, 3]), - new BuiltList([2, 3, 4]), - new BuiltList([3, 4, 5]) - ]); - - expect(serializer.serialize(list), { - 'List>': [ - [1, 2, 3], - [2, 3, 4], - [3, 4, 5] - ] - }); - }); - - test('can be deserialized when nested', () { - final list = new BuiltList>([ - new BuiltList([1, 2, 3]), - new BuiltList([2, 3, 4]), - new BuiltList([3, 4, 5]) - ]); - - expect(serializer.deserialize(serializer.serialize(list)), list); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); } diff --git a/built_json/test/built_map_serializer_test.dart b/built_json/test/built_map_serializer_test.dart index ae9713f..d66878c 100644 --- a/built_json/test/built_map_serializer_test.dart +++ b/built_json/test/built_map_serializer_test.dart @@ -7,59 +7,247 @@ import 'package:built_json/built_json.dart'; import 'package:test/test.dart'; void main() { - final serializer = new BuiltJsonSerializers(); + final serializers = new Serializers(); + + group('BuiltMap with known genericType', () { + final data = new BuiltMap({1: 'one', 2: 'two', 3: 'three'}); + final genericType = const GenericType( + BuiltMap, const [const GenericType(int), const GenericType(String)]); + final serialized = [1, 'one', 2, 'two', 3, 'three']; + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + + test('loses generic type without builder', () { + expect( + serializers + .deserialize(serialized, genericType: genericType) + .runtimeType + .toString(), + 'BuiltMap'); + }); + + test('keeps generic type with builder', () { + final genericSerializer = (serializers.toBuilder() + ..addBuilderFactory( + genericType, () => new MapBuilder())).build(); + + expect( + genericSerializer + .deserialize(serialized, genericType: genericType) + .runtimeType + .toString(), + 'BuiltMap'); + }); + }); + + group('BuiltMap nested left with known genericType', () { + final data = new BuiltMap, String>({ + new BuiltMap({1: 'one'}): 'one!', + new BuiltMap({2: 'two'}): 'two!' + }); + final genericType = const GenericType(BuiltMap, const [ + const GenericType( + BuiltMap, const [const GenericType(int), const GenericType(String)]), + const GenericType(String) + ]); + final serialized = [ + [1, 'one'], + 'one!', + [2, 'two'], + 'two!' + ]; + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + }); + + group('BuiltMap nested right with known genericType', () { + final data = new BuiltMap>({ + 1: new BuiltMap({'one': 'one!'}), + 2: new BuiltMap({'two': 'two!'}) + }); + final genericType = const GenericType(BuiltMap, const [ + const GenericType(int), + const GenericType(BuiltMap, + const [const GenericType(String), const GenericType(String)]) + ]); + final serialized = [ + 1, + ['one', 'one!'], + 2, + ['two', 'two!'] + ]; - group('BuiltMap', () { test('can be serialized', () { - final map = new BuiltMap({1: 'one', 2: 'two', 3: 'three'}); - expect(serializer.serialize(map), { - 'Map': [1, 'one', 2, 'two', 3, 'three'] - }); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - final map = new BuiltMap({1: 'one', 2: 'two', 3: 'three'}); - expect(serializer.deserialize(serializer.serialize(map)), map); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); - group('BuiltSet', () { + group('BuiltMap nested both with known genericType', () { + final data = new BuiltMap, BuiltMap>({ + new BuiltMap({1: 1}): + new BuiltMap({'one': 'one!'}), + new BuiltMap({2: 2}): + new BuiltMap({'two': 'two!'}) + }); + const builtMapOfIntIntGenericType = const GenericType( + BuiltMap, const [const GenericType(int), const GenericType(int)]); + const builtMapOfStringStringGenericType = const GenericType( + BuiltMap, const [const GenericType(String), const GenericType(String)]); + final genericType = const GenericType(BuiltMap, + const [builtMapOfIntIntGenericType, builtMapOfStringStringGenericType]); + final serialized = [ + [1, 1], + ['one', 'one!'], + [2, 2], + ['two', 'two!'] + ]; + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + + test('loses generic type without builder', () { + expect( + serializers + .deserialize(serialized, genericType: genericType) + .runtimeType + .toString(), + 'BuiltMap'); + }); + + test('keeps generic type with builder', () { + final genericSerializer = (serializers.toBuilder() + ..addBuilderFactory( + genericType, + () => + new MapBuilder, BuiltMap>()) + ..addBuilderFactory( + builtMapOfIntIntGenericType, () => new MapBuilder()) + ..addBuilderFactory(builtMapOfStringStringGenericType, + () => new MapBuilder())).build(); + + expect( + genericSerializer + .deserialize(serialized, genericType: genericType) + .runtimeType + .toString(), + 'BuiltMap, BuiltMap>'); + }); + }); + + group('BuiltMap with Object values', () { + final data = new BuiltMap({1: 'one', 2: 2, 3: 'three'}); + final genericType = const GenericType( + BuiltMap, const [const GenericType(int), const GenericType()]); + final serialized = [ + 1, + ['String', 'one'], + 2, + ['int', 2], + 3, + ['String', 'three'] + ]; + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + }); + + group('BuiltMap with Object keys', () { + final data = + new BuiltMap({1: 'one', 'two': 'two', 3: 'three'}); + final genericType = const GenericType( + BuiltMap, const [const GenericType(), const GenericType(String)]); + final serialized = [ + ['int', 1], + 'one', + ['String', 'two'], + 'two', + ['int', 3], + 'three' + ]; + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + }); + + group('BuiltMap with Object keys and values', () { + final data = new BuiltMap({1: 'one', 'two': 2, 3: 'three'}); + final genericType = const GenericType(BuiltMap); + final serialized = [ + ['int', 1], + ['String', 'one'], + ['String', 'two'], + ['int', 2], + ['int', 3], + ['String', 'three'] + ]; + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + }); + + group('BuiltMap with unknown genericType', () { + final data = new BuiltMap({1: 'one', 'two': 2, 3: 'three'}); + final genericType = const GenericType(); + final serialized = [ + 'map', + ['int', 1], + ['String', 'one'], + ['String', 'two'], + ['int', 2], + ['int', 3], + ['String', 'three'] + ]; + test('can be serialized', () { - final list = new BuiltSet([1, 2, 3]); - expect(serializer.serialize(list), { - 'Set': [1, 2, 3] - }); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - final list = new BuiltSet([1, 2, 3]); - expect(serializer.deserialize(serializer.serialize(list)), list); - }); - - test('can be serialized when nested', () { - final list = new BuiltSet>([ - new BuiltSet([1, 2, 3]), - new BuiltSet([2, 3, 4]), - new BuiltSet([3, 4, 5]) - ]); - - expect(serializer.serialize(list), { - 'Set>': [ - [1, 2, 3], - [2, 3, 4], - [3, 4, 5] - ] - }); - }); - - test('can be deserialized when nested', () { - final list = new BuiltSet>([ - new BuiltSet([1, 2, 3]), - new BuiltSet([2, 3, 4]), - new BuiltSet([3, 4, 5]) - ]); - - expect(serializer.deserialize(serializer.serialize(list)), list); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); } diff --git a/built_json/test/built_set_serializer_test.dart b/built_json/test/built_set_serializer_test.dart index 2e78c56..dc63d3c 100644 --- a/built_json/test/built_set_serializer_test.dart +++ b/built_json/test/built_set_serializer_test.dart @@ -7,45 +7,62 @@ import 'package:built_json/built_json.dart'; import 'package:test/test.dart'; void main() { - final serializer = new BuiltJsonSerializers(); + final serializers = new Serializers(); + + group('BuiltSet with known genericType', () { + final data = new BuiltSet([1, 2, 3]); + final genericType = + const GenericType(BuiltSet, const [const GenericType(int)]); + final serialized = [1, 2, 3]; - group('BuiltSet', () { test('can be serialized', () { - final list = new BuiltSet([1, 2, 3]); - expect(serializer.serialize(list), { - 'Set': [1, 2, 3] - }); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - final list = new BuiltSet([1, 2, 3]); - expect(serializer.deserialize(serializer.serialize(list)), list); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); - test('can be serialized when nested', () { - final list = new BuiltSet>([ - new BuiltSet([1, 2, 3]), - new BuiltSet([2, 3, 4]), - new BuiltSet([3, 4, 5]) - ]); - - expect(serializer.serialize(list), { - 'Set>': [ - [1, 2, 3], - [2, 3, 4], - [3, 4, 5] - ] - }); + test('loses generic type without builder', () { + expect( + serializers + .deserialize(serialized, genericType: genericType) + .runtimeType + .toString(), + 'BuiltSet'); }); - test('can be deserialized when nested', () { - final list = new BuiltSet>([ - new BuiltSet([1, 2, 3]), - new BuiltSet([2, 3, 4]), - new BuiltSet([3, 4, 5]) - ]); + test('keeps generic type with builder', () { + final genericSerializer = (serializers.toBuilder() + ..addBuilderFactory(genericType, () => new SetBuilder())).build(); + + expect( + genericSerializer + .deserialize(serialized, genericType: genericType) + .runtimeType + .toString(), + 'BuiltSet'); + }); + }); + + group('BuiltSet with unknown genericType', () { + final data = new BuiltSet([1, 2, 3]); + final genericType = const GenericType(); + final serialized = [ + 'set', + ['int', 1], + ['int', 2], + ['int', 3] + ]; - expect(serializer.deserialize(serializer.serialize(list)), list); + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); } diff --git a/built_json/test/double_serializer_test.dart b/built_json/test/double_serializer_test.dart index 5236999..25be280 100644 --- a/built_json/test/double_serializer_test.dart +++ b/built_json/test/double_serializer_test.dart @@ -6,35 +6,35 @@ import 'package:built_json/built_json.dart'; import 'package:test/test.dart'; void main() { - final serializer = new BuiltJsonSerializers(); + final serializers = new Serializers(); - group('double', () { - test('can be serialized', () { - expect(serializer.serialize(3.0), {'double': '3.0'}); - }); + group('double with known genericType', () { + final data = 3.141592653589793; + final serialized = '3.141592653589793'; + final genericType = const GenericType(double); - test('can be deserialized', () { - expect(serializer.deserialize(serializer.serialize(3.0)), 3.0); - }); - }); - - group('int', () { test('can be serialized', () { - expect(serializer.serialize(3), {'int': 3}); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - expect(serializer.deserialize(serializer.serialize(3)), 3); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); - group('String', () { + group('double with unknown genericType', () { + final data = 3.141592653589793; + final serialized = ['double', '3.141592653589793']; + final genericType = const GenericType(); + test('can be serialized', () { - expect(serializer.serialize('hello'), {'String': 'hello'}); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - expect(serializer.deserialize(serializer.serialize('hello')), 'hello'); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); } diff --git a/built_json/test/int_serializer_test.dart b/built_json/test/int_serializer_test.dart index 210110b..5d551e7 100644 --- a/built_json/test/int_serializer_test.dart +++ b/built_json/test/int_serializer_test.dart @@ -6,25 +6,35 @@ import 'package:built_json/built_json.dart'; import 'package:test/test.dart'; void main() { - final serializer = new BuiltJsonSerializers(); + final serializers = new Serializers(); + + group('int with known genericType', () { + final data = 42; + final serialized = 42; + final genericType = const GenericType(int); - group('int', () { test('can be serialized', () { - expect(serializer.serialize(3), {'int': 3}); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - expect(serializer.deserialize(serializer.serialize(3)), 3); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); - group('String', () { + group('int with unknown genericType', () { + final data = 42; + final serialized = ['int', 42]; + final genericType = const GenericType(); + test('can be serialized', () { - expect(serializer.serialize('hello'), {'String': 'hello'}); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - expect(serializer.deserialize(serializer.serialize('hello')), 'hello'); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); } diff --git a/built_json/test/string_serializer_test.dart b/built_json/test/string_serializer_test.dart index 0aafcc9..0371b37 100644 --- a/built_json/test/string_serializer_test.dart +++ b/built_json/test/string_serializer_test.dart @@ -6,15 +6,35 @@ import 'package:built_json/built_json.dart'; import 'package:test/test.dart'; void main() { - final serializer = new BuiltJsonSerializers(); + final serializers = new Serializers(); + + group('String with known genericType', () { + final data = 'testing, testing'; + final serialized = 'testing, testing'; + final genericType = const GenericType(String); + + test('can be serialized', () { + expect(serializers.serialize(data, genericType: genericType), serialized); + }); + + test('can be deserialized', () { + expect( + serializers.deserialize(serialized, genericType: genericType), data); + }); + }); + + group('String with unknown genericType', () { + final data = 'testing, testing'; + final serialized = ['String', 'testing, testing']; + final genericType = const GenericType(); - group('String', () { test('can be serialized', () { - expect(serializer.serialize('hello'), {'String': 'hello'}); + expect(serializers.serialize(data, genericType: genericType), serialized); }); test('can be deserialized', () { - expect(serializer.deserialize(serializer.serialize('hello')), 'hello'); + expect( + serializers.deserialize(serialized, genericType: genericType), data); }); }); } diff --git a/built_json_generator/lib/built_json_generator.dart b/built_json_generator/lib/built_json_generator.dart index 52e0a15..0eb9f8f 100644 --- a/built_json_generator/lib/built_json_generator.dart +++ b/built_json_generator/lib/built_json_generator.dart @@ -7,6 +7,7 @@ library built_json_generator; import 'dart:async'; import 'package:analyzer/src/generated/element.dart'; +import 'package:built_json_generator/src/source_library.dart'; import 'package:source_gen/source_gen.dart'; /// Generator for Built JSON. @@ -16,201 +17,9 @@ class BuiltJsonGenerator extends Generator { Future generate(Element element) async { if (element is! LibraryElement) return null; - final libraryElement = element as LibraryElement; + final sourceLibrary = SourceLibrary.fromLibraryElement(element as LibraryElement); + if (!sourceLibrary.needsBuiltJson && !sourceLibrary.hasSerializers) return null; - final serializableClasses = []; - final classElementVisitor = new _ClassExtractorVisitor(); - libraryElement.visitChildren(classElementVisitor); - - for (final classElement in classElementVisitor.classElements) { - if (isEnumClass(classElement) || isBuiltValue(classElement)) { - serializableClasses.add(classElement); - } - } - - // TODO(davidmorgan): better way of checking for top level declaration. - final needsBuiltJsonSerializers = libraryElement.exportNamespace - ?.definedNames?.containsKey('builtJsonSerializers') ?? - false; - - if (serializableClasses.isEmpty && !needsBuiltJsonSerializers) { - return null; - } - - // Try to also find field types that need to be serialized. - // TODO(davidmorgan): go deeper. Or, insist on generated serializers? - final dependencyClasses = []; - for (final classElement in serializableClasses) { - for (final field in classElement.fields) { - if (!field.isStatic && - field.getter != null && - field.getter.isAbstract) { - if (field.getter.returnType.element is ClassElement) { - final nestedClassElement = field.getter.returnType.element; - if (isEnumClass(nestedClassElement) || - isBuiltValue(nestedClassElement)) { - dependencyClasses.add(nestedClassElement); - } - } - } - } - } - - return (needsBuiltJsonSerializers - ? 'BuiltJsonSerializers _\$builtJsonSerializers = new BuiltJsonSerializers()' + - serializableClasses.map(generateSerializerAdder).join('') + - dependencyClasses - .map(generateTransitiveSerializerAdder) - .join('') + - ';' - : '') + - serializableClasses.map(generateSerializerDeclaration).join('') + - serializableClasses.map(generateSerializer).join(''); - } - - static String generateSerializerAdder(ClassElement classElement) { - return '..add(new _\$${classElement.displayName}Serializer())'; - } - - static String generateTransitiveSerializerAdder(ClassElement classElement) { - return '..add(${classElement.displayName}.serializer)'; - } - - static String toCamelCase(String name) { - var result = ''; - var upperCase = false; - var firstCharacter = true; - for (final char in name.split('')) { - if (char == '_') { - upperCase = true; - } else { - result += firstCharacter - ? char.toLowerCase() - : (upperCase ? char.toUpperCase() : char); - upperCase = false; - firstCharacter = false; - } - } - return result; - } - - static bool isEnumClass(ClassElement classElement) { - // TODO(davidmorgan): better test for serializer field. - return classElement.allSupertypes - .map((type) => type.displayName) - .any((displayName) => displayName == 'EnumClass') && - !classElement.displayName.startsWith(r'_$') && - classElement.fields.any((field) => field.displayName == 'serializer'); - } - - static bool isBuiltValue(ClassElement classElement) { - // TODO(davidmorgan): better test for serializer field. - return classElement.allSupertypes - .map((type) => type.displayName) - .any((displayName) => displayName.startsWith('Built')) && - !classElement.displayName.startsWith(r'_$') && - classElement.fields.any((field) => field.displayName == 'serializer'); - } - - static String generateSerializerDeclaration(ClassElement classElement) { - final camelCaseName = toCamelCase(classElement.displayName); - return 'BuiltJsonSerializer<${classElement.displayName}> ' - '_\$${camelCaseName}Serializer = ' - 'new _\$${classElement.displayName}Serializer();'; - } - - static String generateSerializer(ClassElement classElement) { - final className = classElement.displayName; - - if (isBuiltValue(classElement)) { - return 'class _\$${className}Serializer implements BuiltJsonSerializer<$className> {' - 'final Type type = _\$$className;' - "final String typeName = '$className';" - 'Object serialize(BuiltJsonSerializers builtJsonSerializers, ' - '$className object,' - '{String expectedType}) {' - 'return {' + - generateFieldSerializers(classElement) + - '};' - '}' - '$className deserialize(BuiltJsonSerializers builtJsonSerializers,' - 'Object object,' - '{String expectedType}) {' - 'return new $className((b) => b' + - generateFieldDeserializers(classElement) + - ');' + - '}' - '}'; - } else if (isEnumClass(classElement)) { - return 'class _\$${className}Serializer implements BuiltJsonSerializer<$className> {' - 'final Type type = $className;' - "final String typeName = '$className';" - 'Object serialize(BuiltJsonSerializers builtJsonSerializers, ' - '$className object,' - '{String expectedType}) {' - "return object.name;" - '}' - '$className deserialize(BuiltJsonSerializers builtJsonSerializers,' - 'Object object,' - '{String expectedType}) {' - 'return ${className}.valueOf(object);' - '}' - '}'; - } else { - throw new UnsupportedError(''); - } - } - - static String generateFieldSerializers(ClassElement classElement) { - return classElement.fields - .where((field) => - field.getter != null && field.getter.isAbstract && !field.isStatic) - .map((field) => "'${field.displayName}': " - "builtJsonSerializers.serialize(object.${field.displayName}, " - "expectedType: '${field.getter.returnType.displayName}'),") - .join(''); - } - - static String generateFieldDeserializers(ClassElement classElement) { - return classElement.fields - .where((field) => - field.getter != null && field.getter.isAbstract && !field.isStatic) - .map((field) { - final builderClassElement = - classElement.library.getType(classElement.displayName + 'Builder'); - // TODO(davidmorgan): need a better way to detect if field uses a builder. - final usesBuilder = builderClassElement?.fields - .where( - (builderField) => builderField.displayName == field.displayName) - .firstWhere((_) => true, orElse: null) - ?.getter - ?.returnType - .displayName - .contains('Builder'); - - if (usesBuilder) { - return '..${field.displayName}.replace(' - "builtJsonSerializers.deserialize(object['${field.displayName}'], " - "expectedType: '${field.getter.returnType.displayName}'))"; - } else { - return '..${field.displayName} = ' - "builtJsonSerializers.deserialize(object['${field.displayName}'], " - "expectedType: '${field.getter.returnType.displayName}')"; - } - }).join(''); - } -} - -class _ClassExtractorVisitor extends SimpleElementVisitor { - final List classElements = new List(); - - @override - visitClassElement(ClassElement element) { - classElements.add(element); - } - - @override - visitCompilationUnitElement(CompilationUnitElement element) { - element.visitChildren(this); + return sourceLibrary.generate(); } } diff --git a/built_json_generator/lib/src/library_elements.dart b/built_json_generator/lib/src/library_elements.dart new file mode 100644 index 0000000..d330007 --- /dev/null +++ b/built_json_generator/lib/src/library_elements.dart @@ -0,0 +1,42 @@ +library built_json_generator.library_elements; + +import 'package:analyzer/src/generated/element.dart'; +import 'package:built_collection/built_collection.dart'; + +/// Tools for [LibraryElement]s. +class LibraryElements { + static BuiltList getClassElements( + LibraryElement libraryElement) { + final result = new _GetClassesVisitor(); + libraryElement.visitChildren(result); + return new BuiltList(result.classElements); + } + + static BuiltList getTransitiveClassElements( + LibraryElement libraryElement) { + final result = new ListBuilder(); + for (final source in libraryElement.context.librarySources) { + final otherLibraryElement = + libraryElement.context.getLibraryElement(source); + if (otherLibraryElement != null) { + result.addAll(getClassElements(otherLibraryElement)); + } + } + return result.build(); + } +} + +/// Visitor that gets all [ClassElement]s. +class _GetClassesVisitor extends SimpleElementVisitor { + final List classElements = new List(); + + @override + visitClassElement(ClassElement element) { + classElements.add(element); + } + + @override + visitCompilationUnitElement(CompilationUnitElement element) { + element.visitChildren(this); + } +} diff --git a/built_json_generator/lib/src/source_class.dart b/built_json_generator/lib/src/source_class.dart new file mode 100644 index 0000000..0e5d816 --- /dev/null +++ b/built_json_generator/lib/src/source_class.dart @@ -0,0 +1,186 @@ +library built_json_generator.source_class; + +import 'package:analyzer/src/generated/element.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:built_json_generator/src/source_field.dart'; +import 'package:built_value/built_value.dart'; + +part 'source_class.g.dart'; + +abstract class SourceClass implements Built { + String get name; + bool get isBuiltValue; + bool get isEnumClass; + BuiltList get fields; + + factory SourceClass([updates(SourceClassBuilder b)]) = _$SourceClass; + SourceClass._(); + + static SourceClass fromClassElements( + ClassElement classElement, ClassElement builderClassElement) { + final result = new SourceClassBuilder(); + + result.name = classElement.name; + + // TODO(davidmorgan): better check. + result.isBuiltValue = classElement.allSupertypes + .map((type) => type.name) + .any((name) => name.startsWith('Built')) && + !classElement.name.startsWith(r'_$') && + classElement.fields.any((field) => field.name == 'serializer'); + + // TODO(davidmorgan): better check. + result.isEnumClass = classElement.allSupertypes + .map((type) => type.name) + .any((name) => name == 'EnumClass') && + !classElement.name.startsWith(r'_$') && + classElement.fields.any((field) => field.name == 'serializer'); + + for (final fieldElement in classElement.fields) { + final builderFieldElement = + builderClassElement?.getField(fieldElement.displayName); + final sourceField = + SourceField.fromFieldElements(fieldElement, builderFieldElement); + if (sourceField.isSerializable) { + result.fields.add(sourceField); + } + } + + return result.build(); + } + + bool get needsBuiltJson => isBuiltValue || isEnumClass; + + String generateTransitiveSerializerAdder() { + return '..add(${name}.serializer)'; + } + + String generateSerializerDeclaration() { + final camelCaseName = _toCamelCase(name); + return 'Serializer<$name> ' + '_\$${camelCaseName}Serializer = ' + 'new _\$${name}Serializer();'; + } + + String generateSerializer() { + if (isBuiltValue) { + return ''' +class _\$${name}Serializer implements Serializer<$name> { + final bool structured = true; + final Iterable types = new BuiltList([$name, _\$$name]); + final String wireName = '$name'; + + @override + Object serialize(Serializers serializers, $name object, + {GenericType genericType: const GenericType()}) { + return [${_generateFieldSerializers()}]; + } + + @override + $name deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + final result = new ${name}Builder(); + + var key; + var value; + var expectingKey = true; + for (final item in object as List) { + if (expectingKey) { + key = item; + expectingKey = false; + } else { + value = item; + expectingKey = true; + + switch (key as String) { + ${_generateFieldDeserializers()} + } + } + } + + return result.build(); + } +} +'''; + } else if (isEnumClass) { + return ''' +class _\$${name}Serializer implements Serializer<$name> { + final bool structured = false; + final Iterable types = new BuiltList([$name]); + final String wireName = '$name'; + + @override + Object serialize(Serializers serializers, $name object, + {GenericType genericType: const GenericType()}) { + return object.name; + } + + @override + $name deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + return ${name}.valueOf(object); + } +} +'''; + } else { + throw new UnsupportedError('not serializable'); + } + } + + String _generateFieldSerializers() { + return fields + .map((field) => "'${field.name}', " + "serializers.serialize(object.${field.name}, " + "genericType: ${field.generateGenericType()}),") + .join(''); + } + + String _generateFieldDeserializers() { + return fields.map((field) { + if (field.builderFieldUsesNestedBuilder) { + return ''' +case '${field.name}': + result.${field.name}.replace(serializers.deserialize( + value, genericType: ${field.generateGenericType()})); + break; +'''; + } else { + return ''' +case '${field.name}': + result.${field.name} = serializers.deserialize( + value, genericType: ${field.generateGenericType()}); + break; +'''; + } + }).join(''); + } + + static String _toCamelCase(String name) { + var result = ''; + var upperCase = false; + var firstCharacter = true; + for (final char in name.split('')) { + if (char == '_') { + upperCase = true; + } else { + result += firstCharacter + ? char.toLowerCase() + : (upperCase ? char.toUpperCase() : char); + upperCase = false; + firstCharacter = false; + } + } + return result; + } +} + +abstract class SourceClassBuilder + implements Builder { + String name; + bool isBuiltValue; + bool isEnumClass; + ListBuilder fields = new ListBuilder(); + + factory SourceClassBuilder() = _$SourceClassBuilder; + SourceClassBuilder._(); +} diff --git a/built_json_generator/lib/src/source_class.g.dart b/built_json_generator/lib/src/source_class.g.dart new file mode 100644 index 0000000..7976096 --- /dev/null +++ b/built_json_generator/lib/src/source_class.g.dart @@ -0,0 +1,92 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// 2015-12-04T10:20:21.611Z + +part of built_json_generator.source_class; + +// ************************************************************************** +// Generator: BuiltValueGenerator +// Target: abstract class SourceClass +// ************************************************************************** + +class _$SourceClass extends SourceClass { + final String name; + final bool isBuiltValue; + final bool isEnumClass; + final BuiltList fields; + _$SourceClass._({this.name, this.isBuiltValue, this.isEnumClass, this.fields}) + : super._() { + if (name == null) throw new ArgumentError('null name'); + if (isBuiltValue == null) throw new ArgumentError('null isBuiltValue'); + if (isEnumClass == null) throw new ArgumentError('null isEnumClass'); + if (fields == null) throw new ArgumentError('null fields'); + } + factory _$SourceClass([updates(SourceClassBuilder b)]) => + (new SourceClassBuilder()..update(updates)).build(); + SourceClass rebuild(updates(SourceClassBuilder b)) => + (toBuilder()..update(updates)).build(); + _$SourceClassBuilder toBuilder() => new _$SourceClassBuilder()..replace(this); + bool operator ==(other) { + if (other is! SourceClass) return false; + return name == other.name && + isBuiltValue == other.isBuiltValue && + isEnumClass == other.isEnumClass && + fields == other.fields; + } + + int get hashCode { + return hashObjects([name, isBuiltValue, isEnumClass, fields]); + } + + String toString() { + return 'SourceClass {' + 'name=${name.toString()}\n' + 'isBuiltValue=${isBuiltValue.toString()}\n' + 'isEnumClass=${isEnumClass.toString()}\n' + 'fields=${fields.toString()}\n' + '}'; + } +} + +class _$SourceClassBuilder extends SourceClassBuilder { + _$SourceClassBuilder() : super._(); + String get name => super.name; + void set name(String name) { + if (name == null) throw new ArgumentError('null name'); + super.name = name; + } + + bool get isBuiltValue => super.isBuiltValue; + void set isBuiltValue(bool isBuiltValue) { + if (isBuiltValue == null) throw new ArgumentError('null isBuiltValue'); + super.isBuiltValue = isBuiltValue; + } + + bool get isEnumClass => super.isEnumClass; + void set isEnumClass(bool isEnumClass) { + if (isEnumClass == null) throw new ArgumentError('null isEnumClass'); + super.isEnumClass = isEnumClass; + } + + ListBuilder get fields => super.fields; + void set fields(ListBuilder fields) { + if (fields == null) throw new ArgumentError('null fields'); + super.fields = fields; + } + + void replace(SourceClass other) { + super.name = other.name; + super.isBuiltValue = other.isBuiltValue; + super.isEnumClass = other.isEnumClass; + super.fields = other.fields?.toBuilder(); + } + + void update(updates(SourceClassBuilder b)) { + if (updates != null) updates(this); + } + + SourceClass build() => new _$SourceClass._( + name: name, + isBuiltValue: isBuiltValue, + isEnumClass: isEnumClass, + fields: fields?.build()); +} diff --git a/built_json_generator/lib/src/source_field.dart b/built_json_generator/lib/src/source_field.dart new file mode 100644 index 0000000..966381a --- /dev/null +++ b/built_json_generator/lib/src/source_field.dart @@ -0,0 +1,80 @@ +library built_json_generator.source_field; + +import 'package:analyzer/src/generated/element.dart'; +import 'package:built_value/built_value.dart'; + +part 'source_field.g.dart'; + +abstract class SourceField implements Built { + bool get isSerializable; + String get name; + String get type; + bool get builderFieldUsesNestedBuilder; + + factory SourceField([updates(SourceFieldBuilder b)]) = _$SourceField; + SourceField._(); + + String get rawType => _getBareType(type); + + static SourceField fromFieldElements( + FieldElement fieldElement, FieldElement builderFieldElement) { + final result = new SourceFieldBuilder(); + final isSerializable = fieldElement.getter != null && + fieldElement.getter.isAbstract && + !fieldElement.isStatic; + + result.isSerializable = isSerializable; + + if (isSerializable) { + result.name = fieldElement.displayName; + result.type = fieldElement.getter.returnType.displayName; + result.builderFieldUsesNestedBuilder = builderFieldElement != null && + fieldElement.getter.returnType.displayName != + builderFieldElement.getter.returnType.displayName; + } + + return result.build(); + } + + String generateGenericType() { + return _generateGenericType(type); + } + + static String _generateGenericType(String type) { + // TODO(davidmorgan): support more than one level of nesting. + final bareType = _getBareType(type); + final generics = _getGenerics(type); + final genericItems = generics.split(', '); + + if (generics.isEmpty) { + return 'const GenericType($bareType)'; + } else { + return 'const GenericType($bareType, const [${genericItems.map(_generateGenericType).join(', ')}])'; + } + } + + static String _getBareType(String name) { + final genericsStart = name.indexOf('<'); + return genericsStart == -1 ? name : name.substring(0, genericsStart); + } + + static String _getGenerics(String name) { + final genericsStart = name.indexOf('<'); + return genericsStart == -1 + ? '' + : name + .substring(genericsStart + 1) + .substring(0, name.length - genericsStart - 2); + } +} + +abstract class SourceFieldBuilder + implements Builder { + bool isSerializable; + String name = ''; + String type = ''; + bool builderFieldUsesNestedBuilder = false; + + factory SourceFieldBuilder() = _$SourceFieldBuilder; + SourceFieldBuilder._(); +} diff --git a/built_json_generator/lib/src/source_field.g.dart b/built_json_generator/lib/src/source_field.g.dart new file mode 100644 index 0000000..fe81b6a --- /dev/null +++ b/built_json_generator/lib/src/source_field.g.dart @@ -0,0 +1,99 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// 2015-12-04T12:55:13.981Z + +part of built_json_generator.source_field; + +// ************************************************************************** +// Generator: BuiltValueGenerator +// Target: abstract class SourceField +// ************************************************************************** + +class _$SourceField extends SourceField { + final bool isSerializable; + final String name; + final String type; + final bool builderFieldUsesNestedBuilder; + _$SourceField._( + {this.isSerializable, + this.name, + this.type, + this.builderFieldUsesNestedBuilder}) + : super._() { + if (isSerializable == null) throw new ArgumentError('null isSerializable'); + if (name == null) throw new ArgumentError('null name'); + if (type == null) throw new ArgumentError('null type'); + if (builderFieldUsesNestedBuilder == + null) throw new ArgumentError('null builderFieldUsesNestedBuilder'); + } + factory _$SourceField([updates(SourceFieldBuilder b)]) => + (new SourceFieldBuilder()..update(updates)).build(); + SourceField rebuild(updates(SourceFieldBuilder b)) => + (toBuilder()..update(updates)).build(); + _$SourceFieldBuilder toBuilder() => new _$SourceFieldBuilder()..replace(this); + bool operator ==(other) { + if (other is! SourceField) return false; + return isSerializable == other.isSerializable && + name == other.name && + type == other.type && + builderFieldUsesNestedBuilder == other.builderFieldUsesNestedBuilder; + } + + int get hashCode { + return hashObjects( + [isSerializable, name, type, builderFieldUsesNestedBuilder]); + } + + String toString() { + return 'SourceField {' + 'isSerializable=${isSerializable.toString()}\n' + 'name=${name.toString()}\n' + 'type=${type.toString()}\n' + 'builderFieldUsesNestedBuilder=${builderFieldUsesNestedBuilder.toString()}\n' + '}'; + } +} + +class _$SourceFieldBuilder extends SourceFieldBuilder { + _$SourceFieldBuilder() : super._(); + bool get isSerializable => super.isSerializable; + void set isSerializable(bool isSerializable) { + if (isSerializable == null) throw new ArgumentError('null isSerializable'); + super.isSerializable = isSerializable; + } + + String get name => super.name; + void set name(String name) { + if (name == null) throw new ArgumentError('null name'); + super.name = name; + } + + String get type => super.type; + void set type(String type) { + if (type == null) throw new ArgumentError('null type'); + super.type = type; + } + + bool get builderFieldUsesNestedBuilder => super.builderFieldUsesNestedBuilder; + void set builderFieldUsesNestedBuilder(bool builderFieldUsesNestedBuilder) { + if (builderFieldUsesNestedBuilder == + null) throw new ArgumentError('null builderFieldUsesNestedBuilder'); + super.builderFieldUsesNestedBuilder = builderFieldUsesNestedBuilder; + } + + void replace(SourceField other) { + super.isSerializable = other.isSerializable; + super.name = other.name; + super.type = other.type; + super.builderFieldUsesNestedBuilder = other.builderFieldUsesNestedBuilder; + } + + void update(updates(SourceFieldBuilder b)) { + if (updates != null) updates(this); + } + + SourceField build() => new _$SourceField._( + isSerializable: isSerializable, + name: name, + type: type, + builderFieldUsesNestedBuilder: builderFieldUsesNestedBuilder); +} diff --git a/built_json_generator/lib/src/source_library.dart b/built_json_generator/lib/src/source_library.dart new file mode 100644 index 0000000..5a219bf --- /dev/null +++ b/built_json_generator/lib/src/source_library.dart @@ -0,0 +1,80 @@ +library built_json_generator.source_library; + +import 'package:analyzer/src/generated/element.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:built_json_generator/src/library_elements.dart'; +import 'package:built_json_generator/src/source_class.dart'; +import 'package:built_value/built_value.dart'; + +part 'source_library.g.dart'; + +abstract class SourceLibrary + implements Built { + bool get hasSerializers; + BuiltSet get sourceClasses; + BuiltSet get transitiveSourceClasses; + + factory SourceLibrary([updates(SourceLibraryBuilder b)]) = _$SourceLibrary; + SourceLibrary._(); + + static SourceLibrary fromLibraryElement(LibraryElement libraryElement) { + final result = new SourceLibraryBuilder(); + + // TODO(davidmorgan): better way of checking for top level declaration. + result.hasSerializers = libraryElement.definingCompilationUnit.accessors + .any((element) => element.displayName == 'serializers'); + + final classElements = LibraryElements.getClassElements(libraryElement); + for (final classElement in classElements) { + final builderClassElement = + libraryElement.getType(classElement.displayName + 'Builder'); + final sourceClass = + SourceClass.fromClassElements(classElement, builderClassElement); + if (sourceClass.needsBuiltJson) { + result.sourceClasses.add(sourceClass); + } + } + + final transitiveClassElements = + LibraryElements.getTransitiveClassElements(libraryElement); + for (final classElement in transitiveClassElements) { + final sourceClass = SourceClass.fromClassElements(classElement, null); + if (sourceClass.needsBuiltJson) { + result.transitiveSourceClasses.add(sourceClass); + } + } + + return result.build(); + } + + bool get needsBuiltJson => sourceClasses.isNotEmpty; + + /// Generates serializer source for this library. + String generate() { + return (hasSerializers + ? 'Serializers _\$serializers = (new Serializers().toBuilder()' + + transitiveSourceClasses + .map((sourceClass) => + sourceClass.generateTransitiveSerializerAdder()) + .join('\n') + + ').build();' + : '') + + sourceClasses + .map((sourceClass) => sourceClass.generateSerializerDeclaration()) + .join('\n') + + sourceClasses + .map((sourceClass) => sourceClass.generateSerializer()) + .join('\n'); + } +} + +abstract class SourceLibraryBuilder + implements Builder { + bool hasSerializers = false; + SetBuilder sourceClasses = new SetBuilder(); + SetBuilder transitiveSourceClasses = + new SetBuilder(); + + factory SourceLibraryBuilder() = _$SourceLibraryBuilder; + SourceLibraryBuilder._(); +} diff --git a/built_json_generator/lib/src/source_library.g.dart b/built_json_generator/lib/src/source_library.g.dart new file mode 100644 index 0000000..040e6d8 --- /dev/null +++ b/built_json_generator/lib/src/source_library.g.dart @@ -0,0 +1,87 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// 2015-12-04T15:56:21.652Z + +part of built_json_generator.source_library; + +// ************************************************************************** +// Generator: BuiltValueGenerator +// Target: abstract class SourceLibrary +// ************************************************************************** + +class _$SourceLibrary extends SourceLibrary { + final bool hasSerializers; + final BuiltSet sourceClasses; + final BuiltSet transitiveSourceClasses; + _$SourceLibrary._( + {this.hasSerializers, this.sourceClasses, this.transitiveSourceClasses}) + : super._() { + if (hasSerializers == null) throw new ArgumentError('null hasSerializers'); + if (sourceClasses == null) throw new ArgumentError('null sourceClasses'); + if (transitiveSourceClasses == + null) throw new ArgumentError('null transitiveSourceClasses'); + } + factory _$SourceLibrary([updates(SourceLibraryBuilder b)]) => + (new SourceLibraryBuilder()..update(updates)).build(); + SourceLibrary rebuild(updates(SourceLibraryBuilder b)) => + (toBuilder()..update(updates)).build(); + _$SourceLibraryBuilder toBuilder() => + new _$SourceLibraryBuilder()..replace(this); + bool operator ==(other) { + if (other is! SourceLibrary) return false; + return hasSerializers == other.hasSerializers && + sourceClasses == other.sourceClasses && + transitiveSourceClasses == other.transitiveSourceClasses; + } + + int get hashCode { + return hashObjects( + [hasSerializers, sourceClasses, transitiveSourceClasses]); + } + + String toString() { + return 'SourceLibrary {' + 'hasSerializers=${hasSerializers.toString()}\n' + 'sourceClasses=${sourceClasses.toString()}\n' + 'transitiveSourceClasses=${transitiveSourceClasses.toString()}\n' + '}'; + } +} + +class _$SourceLibraryBuilder extends SourceLibraryBuilder { + _$SourceLibraryBuilder() : super._(); + bool get hasSerializers => super.hasSerializers; + void set hasSerializers(bool hasSerializers) { + if (hasSerializers == null) throw new ArgumentError('null hasSerializers'); + super.hasSerializers = hasSerializers; + } + + SetBuilder get sourceClasses => super.sourceClasses; + void set sourceClasses(SetBuilder sourceClasses) { + if (sourceClasses == null) throw new ArgumentError('null sourceClasses'); + super.sourceClasses = sourceClasses; + } + + SetBuilder get transitiveSourceClasses => + super.transitiveSourceClasses; + void set transitiveSourceClasses( + SetBuilder transitiveSourceClasses) { + if (transitiveSourceClasses == + null) throw new ArgumentError('null transitiveSourceClasses'); + super.transitiveSourceClasses = transitiveSourceClasses; + } + + void replace(SourceLibrary other) { + super.hasSerializers = other.hasSerializers; + super.sourceClasses = other.sourceClasses?.toBuilder(); + super.transitiveSourceClasses = other.transitiveSourceClasses?.toBuilder(); + } + + void update(updates(SourceLibraryBuilder b)) { + if (updates != null) updates(this); + } + + SourceLibrary build() => new _$SourceLibrary._( + hasSerializers: hasSerializers, + sourceClasses: sourceClasses?.build(), + transitiveSourceClasses: transitiveSourceClasses?.build()); +} diff --git a/built_json_generator/pubspec.yaml b/built_json_generator/pubspec.yaml index 9bcadca..5bacdbd 100644 --- a/built_json_generator/pubspec.yaml +++ b/built_json_generator/pubspec.yaml @@ -1,5 +1,5 @@ name: built_json_generator -version: 0.0.1 +version: 0.0.2 description: > JSON serialization for Built Collections, Built Values and Enum Classes. This library is the dev dependency. @@ -13,9 +13,11 @@ environment: dependencies: analyzer: '>=0.26.1 <1.0.0' built_collection: '^1.0.0' - built_json: '^0.0.1' + built_json: '^0.0.2' source_gen: '>=0.4.3 <0.5.0' quiver: '>=0.21.0 <0.22.0' dev_dependencies: + built_value: '^0.0.4' + built_value_generator: '^0.0.4' test: any diff --git a/built_json_generator/test/built_json_generator_test.dart b/built_json_generator/test/built_json_generator_test.dart index 1fd054c..2219d4f 100644 --- a/built_json_generator/test/built_json_generator_test.dart +++ b/built_json_generator/test/built_json_generator_test.dart @@ -6,14 +6,383 @@ import 'dart:async'; import 'dart:io'; import 'package:built_json_generator/built_json_generator.dart'; -import 'package:source_gen/source_gen.dart'; +import 'package:source_gen/source_gen.dart' as source_gen; import 'package:test/test.dart'; void main() { - group('generator', () { - test('works', () async { - // TODO(davidmorgan): unit tests. For now, see end to end tests under - // `example` and unit tests under `built_json`. + group('generator', () async { + test('ignores empty library', () async { + expect(await generate('library value;'), isEmpty); + }); + + test('ignores normal class', () async { + expect(await generate(r''' +library value; + +class EmptyClass {} +'''), isEmpty); + }); + + test('ignores built_value class without serializer', () async { + expect(await generate(r''' +library value; + +import 'package:test_support/test_support.dart'; + +abstract class Value implements Built {} +'''), isEmpty); + }); + + test('generates for built_value class with serializer', () async { + expect(await generate(r''' +library value; + +import 'package:test_support/test_support.dart'; + +abstract class Value implements Built { + static final Serializer serializer = _$serializer; +} +'''), isNotEmpty); + }); + + test('ignores EnumClass without serializer', () async { + expect(await generate(r''' +library value; + +import 'package:test_support/test_support.dart'; + +class Enum extends EnumClass {} +'''), isEmpty); + }); + + test('generates for EnumClass with serializer', () async { + expect(await generate(r''' +library value; + +import 'package:test_support/test_support.dart'; + +class Enum extends EnumClass { + static final Serializer serializer = _$serializer; +} +'''), isNotEmpty); + }); + + test('generates for serializers', () async { + expect(await generate(r''' +library value; + +import 'package:test_support/test_support.dart'; + +final Serializers serializers = _$serializers; +'''), isNotEmpty); + }); + + test('generates correct serializer for built_value with primitives', + () async { + expect( + await generate(r''' +library value; + +import 'package:test_support/test_support.dart'; + +abstract class Value implements Built { + static final Serializer serializer = _$serializer; + bool get aBool; + double get aDouble; + int get anInt; + String get aString; +} + +abstract class ValueBuilder implements Builder { + bool aBool; + double aDouble; + int anInt; + String aString; +} +'''), + r'''// GENERATED CODE - DO NOT MODIFY BY HAND + +part of value; + +// ************************************************************************** +// Generator: BuiltJsonGenerator +// Target: library value +// ************************************************************************** + +Serializer _$valueSerializer = new _$ValueSerializer(); + +class _$ValueSerializer implements Serializer { + final bool structured = true; + final Iterable types = new BuiltList([Value, _$Value]); + final String wireName = 'Value'; + + @override + Object serialize(Serializers serializers, Value object, + {GenericType genericType: const GenericType()}) { + return [ + 'aBool', + serializers.serialize(object.aBool, genericType: const GenericType(bool)), + 'aDouble', + serializers.serialize(object.aDouble, + genericType: const GenericType(double)), + 'anInt', + serializers.serialize(object.anInt, genericType: const GenericType(int)), + 'aString', + serializers.serialize(object.aString, + genericType: const GenericType(String)), + ]; + } + + @override + Value deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + final result = new ValueBuilder(); + + var key; + var value; + var expectingKey = true; + for (final item in object as List) { + if (expectingKey) { + key = item; + expectingKey = false; + } else { + value = item; + expectingKey = true; + + switch (key as String) { + case 'aBool': + result.aBool = serializers.deserialize(value, + genericType: const GenericType(bool)); + break; + case 'aDouble': + result.aDouble = serializers.deserialize(value, + genericType: const GenericType(double)); + break; + case 'anInt': + result.anInt = serializers.deserialize(value, + genericType: const GenericType(int)); + break; + case 'aString': + result.aString = serializers.deserialize(value, + genericType: const GenericType(String)); + break; + } + } + } + + return result.build(); + } +} +'''); + }); + + test('generates correct serializer for built_value with collections', + () async { + expect( + await generate(r''' +library value; + +import 'package:test_support/test_support.dart'; + +abstract class Value implements Built { + static final Serializer serializer = _$serializer; + BuiltList get aList; + BuiltMap get aMap; +} + +abstract class ValueBuilder implements Builder { + ListBuilder aList; + MapBuilder aMap; +} +'''), + r''' +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of value; + +// ************************************************************************** +// Generator: BuiltJsonGenerator +// Target: library value +// ************************************************************************** + +Serializer _$valueSerializer = new _$ValueSerializer(); + +class _$ValueSerializer implements Serializer { + final bool structured = true; + final Iterable types = new BuiltList([Value, _$Value]); + final String wireName = 'Value'; + + @override + Object serialize(Serializers serializers, Value object, + {GenericType genericType: const GenericType()}) { + return [ + 'aList', + serializers.serialize(object.aList, + genericType: + const GenericType(BuiltList, const [const GenericType(String)])), + 'aMap', + serializers.serialize(object.aMap, + genericType: const GenericType(BuiltMap, + const [const GenericType(String), const GenericType(int)])), + ]; + } + + @override + Value deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + final result = new ValueBuilder(); + + var key; + var value; + var expectingKey = true; + for (final item in object as List) { + if (expectingKey) { + key = item; + expectingKey = false; + } else { + value = item; + expectingKey = true; + + switch (key as String) { + case 'aList': + result.aList.replace(serializers.deserialize(value, + genericType: const GenericType( + BuiltList, const [const GenericType(String)]))); + break; + case 'aMap': + result.aMap.replace(serializers.deserialize(value, + genericType: const GenericType(BuiltMap, const [ + const GenericType(String), + const GenericType(int) + ]))); + break; + } + } + } + + return result.build(); + } +} +'''); + }); + + test('generates correct serializer for nested built_value', () async { + expect( + await generate(r''' +library value; + +import 'package:test_support/test_support.dart'; + +abstract class Value implements Built { + static final Serializer serializer = _$serializer; + Value get value; +} + +abstract class ValueBuilder implements Builder { + ValueBuilder value; +} +'''), + r''' +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of value; + +// ************************************************************************** +// Generator: BuiltJsonGenerator +// Target: library value +// ************************************************************************** + +Serializer _$valueSerializer = new _$ValueSerializer(); + +class _$ValueSerializer implements Serializer { + final bool structured = true; + final Iterable types = new BuiltList([Value, _$Value]); + final String wireName = 'Value'; + + @override + Object serialize(Serializers serializers, Value object, + {GenericType genericType: const GenericType()}) { + return [ + 'value', + serializers.serialize(object.value, + genericType: const GenericType(Value)), + ]; + } + + @override + Value deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + final result = new ValueBuilder(); + + var key; + var value; + var expectingKey = true; + for (final item in object as List) { + if (expectingKey) { + key = item; + expectingKey = false; + } else { + value = item; + expectingKey = true; + + switch (key as String) { + case 'value': + result.value.replace(serializers.deserialize(value, + genericType: const GenericType(Value))); + break; + } + } + } + + return result.build(); + } +} +'''); + }); + + test('generates correct serializer for EnumClass', () async { + expect(await generate(r''' +library value; + +import 'package:test_support/test_support.dart'; + +abstract class TestEnum extends EnumClass { + static final Serializer serializer = _$serializer; + + static const TestEnum yes = _$yes; + static const TestEnum no = _$no; + static const TestEnum maybe = _$maybe; +} +'''), r''' +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of value; + +// ************************************************************************** +// Generator: BuiltJsonGenerator +// Target: library value +// ************************************************************************** + +Serializer _$testEnumSerializer = new _$TestEnumSerializer(); + +class _$TestEnumSerializer implements Serializer { + final bool structured = false; + final Iterable types = new BuiltList([TestEnum]); + final String wireName = 'TestEnum'; + + @override + Object serialize(Serializers serializers, TestEnum object, + {GenericType genericType: const GenericType()}) { + return object.name; + } + + @override + TestEnum deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + return TestEnum.valueOf(object); + } +} +'''); }); }); } @@ -24,21 +393,34 @@ Future generate(String source) async { final tempDir = Directory.systemTemp.createTempSync('built_json_generator.dart.'); final packageDir = new Directory(tempDir.path + '/packages')..createSync(); - final builtJsonDir = new Directory(packageDir.path + '/built_json') + final builtJsonDir = new Directory(packageDir.path + '/test_support') ..createSync(); - final builtJsonFile = new File(builtJsonDir.path + '/built_json.dart') + final builtJsonFile = new File(builtJsonDir.path + '/test_support.dart') ..createSync(); - builtJsonFile.writeAsStringSync(builtValueSource); + builtJsonFile.writeAsStringSync(testSupportSource); final libDir = new Directory(tempDir.path + '/lib')..createSync(); final sourceFile = new File(libDir.path + '/value.dart'); sourceFile.writeAsStringSync(source); - await build([], [new BuiltJsonGenerator()], - projectPath: tempDir.path, librarySearchPaths: ['lib']); + await source_gen.generate(tempDir.path, [new BuiltJsonGenerator()], + librarySearchPaths: ['lib'], omitGenerateTimestamp: true); final outputFile = new File(libDir.path + '/value.g.dart'); return outputFile.existsSync() ? outputFile.readAsStringSync() : ''; } -const String builtValueSource = r''' +// Classes mentioned in the test input need to exist, but we don't need the +// real versions. So just use minimal ones. +const String testSupportSource = r''' +class Built {} + +class BuiltList {} + +class BuiltMap {} + +class EnumClass {} + +class Serializer {} + +class Serializers {} '''; diff --git a/built_json_generator/tools/build.dart b/built_json_generator/tools/build.dart new file mode 100644 index 0000000..c46ed46 --- /dev/null +++ b/built_json_generator/tools/build.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import 'package:built_value_generator/built_value_generator.dart'; +import 'package:source_gen/source_gen.dart'; + +void main(List args) { + build(args, [ + new BuiltValueGenerator(), + ]).then((result) => print(result)); +} diff --git a/example/lib/compound_value.dart b/example/lib/compound_value.dart index a2766d2..bea3ace 100644 --- a/example/lib/compound_value.dart +++ b/example/lib/compound_value.dart @@ -13,21 +13,20 @@ part 'compound_value.g.dart'; /// Example of how to use built_json. /// -/// Declare a top level [BuiltJsonSerializers] field called -/// builtJsonSerializers. The built_json code generator will provide the +/// Declare a top level [Serializers] field called +/// serializers. The built_json code generator will provide the /// implementation. You usually only need to do this once per project. -BuiltJsonSerializers builtJsonSerializers = _$builtJsonSerializers; +Serializers serializers = _$serializers; /// Example built_value type. abstract class CompoundValue implements Built { /// Example of how to make a built_value type serializable. /// - /// Declare a static final [BuiltJsonSerializer] field called `serializer`. + /// Declare a static final [Serializers] field called `serializer`. /// The built_json code generator will provide the implementation. You need to /// do this for every type you want to serialize. - static final BuiltJsonSerializer serializer = - _$compoundValueSerializer; + static final Serializer serializer = _$compoundValueSerializer; Value get aValue; TestEnum get aTestEnum; diff --git a/example/lib/compound_value.g.dart b/example/lib/compound_value.g.dart index 134731c..5dce8e2 100644 --- a/example/lib/compound_value.g.dart +++ b/example/lib/compound_value.g.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -// 2015-11-08T12:38:01.349Z +// 2015-12-08T16:15:17.813Z part of compound_value; @@ -8,35 +8,62 @@ part of compound_value; // Target: library compound_value // ************************************************************************** -BuiltJsonSerializers _$builtJsonSerializers = new BuiltJsonSerializers() - ..add(new _$CompoundValueSerializer()) - ..add(Value.serializer) - ..add(TestEnum.serializer); -BuiltJsonSerializer _$compoundValueSerializer = +Serializers _$serializers = (new Serializers().toBuilder() + ..add(TestEnum.serializer) + ..add(CompoundValue.serializer) + ..add(Value.serializer)).build(); +Serializer _$compoundValueSerializer = new _$CompoundValueSerializer(); -class _$CompoundValueSerializer implements BuiltJsonSerializer { - final Type type = _$CompoundValue; - final String typeName = 'CompoundValue'; - Object serialize( - BuiltJsonSerializers builtJsonSerializers, CompoundValue object, - {String expectedType}) { - return { - 'aValue': - builtJsonSerializers.serialize(object.aValue, expectedType: 'Value'), - 'aTestEnum': builtJsonSerializers.serialize(object.aTestEnum, - expectedType: 'TestEnum'), - }; +class _$CompoundValueSerializer implements Serializer { + final bool structured = true; + final Iterable types = + new BuiltList([CompoundValue, _$CompoundValue]); + final String wireName = 'CompoundValue'; + + @override + Object serialize(Serializers serializers, CompoundValue object, + {GenericType genericType: const GenericType()}) { + return [ + 'aValue', + serializers.serialize(object.aValue, + genericType: const GenericType(Value)), + 'aTestEnum', + serializers.serialize(object.aTestEnum, + genericType: const GenericType(TestEnum)), + ]; } - CompoundValue deserialize( - BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { - return new CompoundValue((b) => b - ..aValue.replace(builtJsonSerializers.deserialize(object['aValue'], - expectedType: 'Value')) - ..aTestEnum = builtJsonSerializers.deserialize(object['aTestEnum'], - expectedType: 'TestEnum')); + @override + CompoundValue deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + final result = new CompoundValueBuilder(); + + var key; + var value; + var expectingKey = true; + for (final item in object as List) { + if (expectingKey) { + key = item; + expectingKey = false; + } else { + value = item; + expectingKey = true; + + switch (key as String) { + case 'aValue': + result.aValue.replace(serializers.deserialize(value, + genericType: const GenericType(Value))); + break; + case 'aTestEnum': + result.aTestEnum = serializers.deserialize(value, + genericType: const GenericType(TestEnum)); + break; + } + } + } + + return result.build(); } } diff --git a/example/lib/test_enum.dart b/example/lib/test_enum.dart index 646a498..feebddf 100644 --- a/example/lib/test_enum.dart +++ b/example/lib/test_enum.dart @@ -6,25 +6,26 @@ library test_enum; import 'package:built_json/built_json.dart'; import 'package:enum_class/enum_class.dart'; +import 'package:example/compound_value.dart'; +import 'package:example/value.dart'; part 'test_enum.g.dart'; /// Example of how to use built_json. /// -/// Declare a top level [BuiltJsonSerializers] field called -/// builtJsonSerializers. The built_json code generator will provide the +/// Declare a top level [Serializers] field called +/// serializers. The built_json code generator will provide the /// implementation. You usually only need to do this once per project. -BuiltJsonSerializers builtJsonSerializers = _$builtJsonSerializers; - +Serializers serializers = _$serializers; /// Example [EnumClass]. class TestEnum extends EnumClass { /// Example of how to make an [EnumClass] serializable. /// - /// Declare a static final [BuiltJsonSerializer] field called `serializer`. + /// Declare a static final [Serializers] field called `serializer`. /// The built_json code generator will provide the implementation. You need to /// do this for every type you want to serialize. - static final BuiltJsonSerializer serializer = _$testEnumSerializer; + static final Serializer serializer = _$testEnumSerializer; static const TestEnum yes = _$yes; static const TestEnum no = _$no; @@ -35,4 +36,3 @@ class TestEnum extends EnumClass { static BuiltSet get values => _$values; static TestEnum valueOf(String name) => _$valueOf(name); } - diff --git a/example/lib/test_enum.g.dart b/example/lib/test_enum.g.dart index 1cecd9f..618b539 100644 --- a/example/lib/test_enum.g.dart +++ b/example/lib/test_enum.g.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -// 2015-11-08T11:58:53.606Z +// 2015-12-08T16:56:15.642Z part of test_enum; @@ -8,20 +8,26 @@ part of test_enum; // Target: library test_enum // ************************************************************************** -BuiltJsonSerializers _$builtJsonSerializers = new BuiltJsonSerializers() - ..add(new _$TestEnumSerializer()); -BuiltJsonSerializer _$testEnumSerializer = new _$TestEnumSerializer(); +Serializers _$serializers = (new Serializers().toBuilder() + ..add(TestEnum.serializer) + ..add(Value.serializer) + ..add(CompoundValue.serializer)).build(); +Serializer _$testEnumSerializer = new _$TestEnumSerializer(); -class _$TestEnumSerializer implements BuiltJsonSerializer { - final Type type = TestEnum; - final String typeName = 'TestEnum'; - Object serialize(BuiltJsonSerializers builtJsonSerializers, TestEnum object, - {String expectedType}) { +class _$TestEnumSerializer implements Serializer { + final bool structured = false; + final Iterable types = new BuiltList([TestEnum]); + final String wireName = 'TestEnum'; + + @override + Object serialize(Serializers serializers, TestEnum object, + {GenericType genericType: const GenericType()}) { return object.name; } - TestEnum deserialize(BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { + @override + TestEnum deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { return TestEnum.valueOf(object); } } diff --git a/example/lib/value.dart b/example/lib/value.dart index 8806f00..01e7293 100644 --- a/example/lib/value.dart +++ b/example/lib/value.dart @@ -7,24 +7,26 @@ library value; import 'package:built_collection/built_collection.dart'; import 'package:built_json/built_json.dart'; import 'package:built_value/built_value.dart'; +import 'package:example/compound_value.dart'; +import 'package:example/test_enum.dart'; part 'value.g.dart'; /// Example of how to use built_json. /// -/// Declare a top level [BuiltJsonSerializers] field called -/// builtJsonSerializers. The built_json code generator will provide the +/// Declare a top level [Serializers] field called +/// serializers. The built_json code generator will provide the /// implementation. You usually only need to do this once per project. -BuiltJsonSerializers builtJsonSerializers = _$builtJsonSerializers; +Serializers serializers = _$serializers; /// Example built_value type. abstract class Value implements Built { /// Example of how to make a built_value type serializable. /// - /// Declare a static final [BuiltJsonSerializer] field called `serializer`. + /// Declare a static final [Serializer] field called `serializer`. /// The built_json code generator will provide the implementation. You need to /// do this for every type you want to serialize. - static final BuiltJsonSerializer serializer = _$valueSerializer; + static final Serializer serializer = _$valueSerializer; static final int youCanHaveStaticFields = 3; int get anInt; diff --git a/example/lib/value.g.dart b/example/lib/value.g.dart index 7d8cd45..fd9cff8 100644 --- a/example/lib/value.g.dart +++ b/example/lib/value.g.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -// 2015-11-08T12:39:28.012Z +// 2015-12-08T16:15:17.773Z part of value; @@ -8,42 +8,82 @@ part of value; // Target: library value // ************************************************************************** -BuiltJsonSerializers _$builtJsonSerializers = new BuiltJsonSerializers() - ..add(new _$ValueSerializer()); -BuiltJsonSerializer _$valueSerializer = new _$ValueSerializer(); - -class _$ValueSerializer implements BuiltJsonSerializer { - final Type type = _$Value; - final String typeName = 'Value'; - Object serialize(BuiltJsonSerializers builtJsonSerializers, Value object, - {String expectedType}) { - return { - 'anInt': - builtJsonSerializers.serialize(object.anInt, expectedType: 'int'), - 'aString': builtJsonSerializers.serialize(object.aString, - expectedType: 'String'), - 'anObject': builtJsonSerializers.serialize(object.anObject, - expectedType: 'Object'), - 'aDefaultInt': builtJsonSerializers.serialize(object.aDefaultInt, - expectedType: 'int'), - 'listOfInt': builtJsonSerializers.serialize(object.listOfInt, - expectedType: 'BuiltList'), - }; +Serializers _$serializers = (new Serializers().toBuilder() + ..add(TestEnum.serializer) + ..add(CompoundValue.serializer) + ..add(Value.serializer)).build(); +Serializer _$valueSerializer = new _$ValueSerializer(); + +class _$ValueSerializer implements Serializer { + final bool structured = true; + final Iterable types = new BuiltList([Value, _$Value]); + final String wireName = 'Value'; + + @override + Object serialize(Serializers serializers, Value object, + {GenericType genericType: const GenericType()}) { + return [ + 'anInt', + serializers.serialize(object.anInt, genericType: const GenericType(int)), + 'aString', + serializers.serialize(object.aString, + genericType: const GenericType(String)), + 'anObject', + serializers.serialize(object.anObject, + genericType: const GenericType(Object)), + 'aDefaultInt', + serializers.serialize(object.aDefaultInt, + genericType: const GenericType(int)), + 'listOfInt', + serializers.serialize(object.listOfInt, + genericType: + const GenericType(BuiltList, const [const GenericType(int)])), + ]; } - Value deserialize(BuiltJsonSerializers builtJsonSerializers, Object object, - {String expectedType}) { - return new Value((b) => b - ..anInt = - builtJsonSerializers.deserialize(object['anInt'], expectedType: 'int') - ..aString = builtJsonSerializers.deserialize(object['aString'], - expectedType: 'String') - ..anObject = builtJsonSerializers.deserialize(object['anObject'], - expectedType: 'Object') - ..aDefaultInt = builtJsonSerializers.deserialize(object['aDefaultInt'], - expectedType: 'int') - ..listOfInt.replace(builtJsonSerializers.deserialize(object['listOfInt'], - expectedType: 'BuiltList'))); + @override + Value deserialize(Serializers serializers, Object object, + {GenericType genericType: const GenericType()}) { + final result = new ValueBuilder(); + + var key; + var value; + var expectingKey = true; + for (final item in object as List) { + if (expectingKey) { + key = item; + expectingKey = false; + } else { + value = item; + expectingKey = true; + + switch (key as String) { + case 'anInt': + result.anInt = serializers.deserialize(value, + genericType: const GenericType(int)); + break; + case 'aString': + result.aString = serializers.deserialize(value, + genericType: const GenericType(String)); + break; + case 'anObject': + result.anObject = serializers.deserialize(value, + genericType: const GenericType(Object)); + break; + case 'aDefaultInt': + result.aDefaultInt = serializers.deserialize(value, + genericType: const GenericType(int)); + break; + case 'listOfInt': + result.listOfInt.replace(serializers.deserialize(value, + genericType: const GenericType( + BuiltList, const [const GenericType(int)]))); + break; + } + } + } + + return result.build(); } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 94225fd..1f8a793 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,5 +1,5 @@ name: example -version: 0.0.1 +version: 0.0.2 description: > Just an example, not for publishing. authors: @@ -11,12 +11,12 @@ environment: dependencies: built_collection: '^1.0.0' - built_json: '^0.0.1' + built_json: '^0.0.2' built_value: '^0.0.3' enum_class: '^0.0.2' dev_dependencies: - built_json_generator: '^0.0.1' + built_json_generator: '^0.0.2' built_value_generator: '^0.0.3' enum_class_generator: '^0.0.2' test: any diff --git a/example/test/compound_value_test.dart b/example/test/compound_value_test.dart index e1938d7..ee68e32 100644 --- a/example/test/compound_value_test.dart +++ b/example/test/compound_value_test.dart @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. import 'package:example/compound_value.dart'; -import 'package:example/test_enum.dart' hide builtJsonSerializers; +import 'package:example/test_enum.dart' hide serializers; import 'package:test/test.dart'; void main() { @@ -15,19 +15,24 @@ void main() { ..aValue.anObject = 3 ..aTestEnum = TestEnum.no); - // TODO(davidmorgan): distinguish value of expected type from class with one field. - expect(builtJsonSerializers.serialize(compoundValue), { - 'CompoundValue': { - 'aValue': { - 'anInt': 1, - 'aString': 'two', - 'anObject': {'int': 3}, - 'aDefaultInt': 7, - 'listOfInt': {'List': []}, - }, - 'aTestEnum': 'no' - } - }); + expect(serializers.serialize(compoundValue), [ + 'CompoundValue', + 'aValue', + [ + 'anInt', + 1, + 'aString', + 'two', + 'anObject', + ['int', 3], + 'aDefaultInt', + 7, + 'listOfInt', + [], + ], + 'aTestEnum', + 'no', + ]); }); test('can be deserialized', () { @@ -37,9 +42,7 @@ void main() { ..aValue.anObject = 3 ..aTestEnum = TestEnum.no); - expect( - builtJsonSerializers - .deserialize(builtJsonSerializers.serialize(compoundValue)), + expect(serializers.deserialize(serializers.serialize(compoundValue)), compoundValue); }); }); diff --git a/example/test/example_built_value_test.dart b/example/test/example_built_value_test.dart deleted file mode 100644 index 92500f4..0000000 --- a/example/test/example_built_value_test.dart +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details. -// All rights reserved. Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -import 'package:example/value.dart'; -import 'package:test/test.dart'; - -void main() { - group('Value', () { - test('can be serialized', () { - final value = new Value((b) => b - ..anInt = 1 - ..aString = 'two' - ..anObject = 3); - - // TODO(davidmorgan): why is List explicit here? - expect(builtJsonSerializers.serialize(value), { - 'Value': { - 'anInt': 1, - 'aString': 'two', - 'anObject': {'int': 3}, - 'aDefaultInt': 7, - 'listOfInt': {'List': []}, - } - }); - }); - - test('can be deserialized', () { - final value = new Value((b) => b - ..anInt = 1 - ..aString = 'two' - ..anObject = 3); - - expect( - builtJsonSerializers - .deserialize(builtJsonSerializers.serialize(value)), - value); - }); - }); -} diff --git a/example/test/example_enum_class_test.dart b/example/test/test_enum_test.dart similarity index 68% rename from example/test/example_enum_class_test.dart rename to example/test/test_enum_test.dart index 7e76477..d875fd4 100644 --- a/example/test/example_enum_class_test.dart +++ b/example/test/test_enum_test.dart @@ -8,13 +8,11 @@ import 'package:test/test.dart'; void main() { group('TestEnum', () { test('can be serialized', () { - expect(builtJsonSerializers.serialize(TestEnum.yes), {'TestEnum': 'yes'}); + expect(serializers.serialize(TestEnum.yes), ['TestEnum', 'yes']); }); test('can be deserialized', () { - expect( - builtJsonSerializers - .deserialize(builtJsonSerializers.serialize(TestEnum.maybe)), + expect(serializers.deserialize(serializers.serialize(TestEnum.maybe)), TestEnum.maybe); }); }); diff --git a/example/test/value_test.dart b/example/test/value_test.dart new file mode 100644 index 0000000..4d1bc74 --- /dev/null +++ b/example/test/value_test.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import 'package:example/value.dart'; +import 'package:test/test.dart'; + +void main() { + group('Value', () { + final data = new Value((b) => b + ..anInt = 1 + ..aString = 'two' + ..anObject = 3); + final serialized = [ + 'Value', + 'anInt', + 1, + 'aString', + 'two', + 'anObject', + ['int', 3], + 'aDefaultInt', + 7, + 'listOfInt', + [] + ]; + + test('can be serialized', () { + expect(serializers.serialize(data), serialized); + }); + + test('can be deserialized', () { + expect(serializers.deserialize(serialized), data); + }); + }); +} diff --git a/example/tools/build.dart b/example/tools/build.dart index 28407f1..f86a295 100644 --- a/example/tools/build.dart +++ b/example/tools/build.dart @@ -9,11 +9,9 @@ import 'package:source_gen/source_gen.dart'; /// Example of how to use source_gen with [BuiltJsonGenerator]. /// -/// You will need either [BuiltValueGenerator], [EnumClassGenerator] or both, -/// in addition to [BuiltJsonGenerator], because these are the types you can -/// serialize. -/// -/// All you need is to import the generators you want and call [build]. +/// [BuiltJsonGenerator] is usually used with [BuiltValueGenerator] and +/// [EnumClassGenerator] because types generated by these generators are +/// automatically serializable. void main(List args) { build(args, [ new BuiltJsonGenerator(),