From 91841c5ec4def2c086f23d043090f569ef1827dc Mon Sep 17 00:00:00 2001 From: David Morgan Date: Sat, 5 Dec 2015 16:05:15 +0100 Subject: [PATCH 1/4] Update documentation. --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a7ec35..2f2f114 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,18 @@ # Built JSON for Dart -JSON serialization for Built Collections, Built Values and Enum Classes. +## Introduction + +Built JSON provides serialization for the +[Libraries for Object Oriented Dart](https://github.com/google/built_value.dart/blob/master/libraries_for_object_oriented_dart.md#libraries-for-object-oriented-dart). + +This allows a complete serializable object oriented data model to built from +[Enum Classes](https://github.com/google/enum_class.dart#enum-classes-for-dart), +[Built Collections](https://github.com/google/built_collection.dart#built-collections-for-dart) +and +[Built Values](https://github.com/google/built_value.dart#built-values-for-dart). + + +## Examples See [this example](https://github.com/google/built_json.dart/tree/master/example) From 1a6607186a33aed3234ea55dc7d6397a84837624 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Sat, 5 Dec 2015 16:49:27 +0100 Subject: [PATCH 2/4] Update documentation. --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f2f114..a92f3a6 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,17 @@ Built JSON provides serialization for the [Libraries for Object Oriented Dart](https://github.com/google/built_value.dart/blob/master/libraries_for_object_oriented_dart.md#libraries-for-object-oriented-dart). -This allows a complete serializable object oriented data model to built from +This allows a complete serializable object oriented data model to be based on [Enum Classes](https://github.com/google/enum_class.dart#enum-classes-for-dart), [Built Collections](https://github.com/google/built_collection.dart#built-collections-for-dart) and [Built Values](https://github.com/google/built_value.dart#built-values-for-dart). +Built JSON for Java, a separate project, will provide compatible serialization +for Java, mapping Enum Classes to standard Java enums, Built Collections to +[Immutable Collections](https://github.com/google/guava/wiki/ImmutableCollectionsExplained) +and Built Values to [AutoValues](https://github.com/google/auto/tree/master/value#autovalue). + ## Examples From 58dbbc92398e8125ca46d552eef308d195c101c6 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Sun, 6 Dec 2015 11:49:13 +0100 Subject: [PATCH 3/4] Update documentation. --- README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a92f3a6..2ef6ebf 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,33 @@ for Java, mapping Enum Classes to standard Java enums, Built Collections to [Immutable Collections](https://github.com/google/guava/wiki/ImmutableCollectionsExplained) and Built Values to [AutoValues](https://github.com/google/auto/tree/master/value#autovalue). +## Status + +Built JSON is not yet ready for use. Watch this space! + + +## Motivation + +There are many ways to serialize data. Why do we need another? + +Built JSON is about serialization of object oriented data. There are many ways to do this, too. Built JSON is different to other libraries for a number of reasons: + +Built JSON _fully supports object oriented design_: any object model that you can design can be serialized, including full use of generics and interfaces. Some other libraries require concrete types or do not fully support generics. + +Built JSON _allows different object oriented models over the same data_. For example, in a client server application, it's likely that the client and server want different functionality from their data model. So, they are allowed to have different classes that map to the same data. Most other libraries enforce a 1:1 mapping between classes and types on the wire. + +Built JSON _requires well behaved types_. They must be immutable, can use interface but not concrete inheritance, must have predictable nullability, `hashCode`, `equals` and `toString`. In fact, they must be Enum Classes, Built Collections or Built Values. Some other libraries allow badly behaved types to be serialized. + +Built JSON _supports changes to the data model_. Optional fields can be added or removed, and fields can be switched from optional to required, allowing your data model to evolve without breaking compatbility. Some other libraries break compatability on any change to any serializable class. + +Built JSON _is modular_. Each endpoint can choose which classes to know about; for example, you can have multiple clients that each know about only a subset of the classes the server knows. Most other libraries are monolithic, requiring all endpoints to know all types. + +Built JSON _is multi language_. Support will be come first for Dart, Java and Java/GWT. Many other libraries support a single language only. + +Built JSON _has first class support for validation_ via Built Values. An important part of a powerful data model is ensuring it's valid, so classes can make guarantees about what they can do. Other libraries also support validation but usually in a less prominent way. + +And, finally, Built JSON _is pluggable_. Arbitrary extensions can be added to give custom JSON serialization for your own types. This could be used to interoperate with other tools or to add hand coded high performance serializers for specific classes. Some other libraries are not so extensible. + ## Examples @@ -23,7 +50,6 @@ See [this example](https://github.com/google/built_json.dart/tree/master/example) for a full project with a `build.dart` and some examples. -This project is not yet ready for use. Watch this space! ## Features and bugs From e5a43a3edf96637a6c5972b1b2ecaf4a14649b5b Mon Sep 17 00:00:00 2001 From: David Morgan Date: Mon, 9 Nov 2015 20:04:06 +0100 Subject: [PATCH 4/4] Rewrite to fully use static type information. --- built_json/lib/built_json.dart | 205 +++++---- built_json/lib/src/bool_serializer.dart | 18 +- .../lib/src/built_json_serializers.dart | 131 ++++++ built_json/lib/src/built_list_serializer.dart | 39 +- built_json/lib/src/built_map_serializer.dart | 57 +-- built_json/lib/src/built_set_serializer.dart | 38 +- built_json/lib/src/double_serializer.dart | 18 +- built_json/lib/src/int_serializer.dart | 18 +- built_json/lib/src/string_serializer.dart | 18 +- built_json/test/bool_serializer_test.dart | 28 +- .../test/built_list_serializer_test.dart | 110 +++-- .../test/built_map_serializer_test.dart | 270 ++++++++++-- .../test/built_set_serializer_test.dart | 75 ++-- built_json/test/double_serializer_test.dart | 32 +- built_json/test/int_serializer_test.dart | 24 +- built_json/test/string_serializer_test.dart | 28 +- .../lib/built_json_generator.dart | 199 +-------- .../lib/src/library_elements.dart | 42 ++ .../lib/src/source_class.dart | 186 ++++++++ .../lib/src/source_class.g.dart | 92 ++++ .../lib/src/source_field.dart | 80 ++++ .../lib/src/source_field.g.dart | 99 +++++ .../lib/src/source_library.dart | 80 ++++ .../lib/src/source_library.g.dart | 87 ++++ built_json_generator/pubspec.yaml | 6 +- .../test/built_json_generator_test.dart | 404 +++++++++++++++++- built_json_generator/tools/build.dart | 12 + example/lib/compound_value.dart | 11 +- example/lib/compound_value.g.dart | 79 ++-- example/lib/test_enum.dart | 14 +- example/lib/test_enum.g.dart | 27 +- example/lib/value.dart | 12 +- example/lib/value.g.dart | 110 +++-- example/pubspec.yaml | 8 +- example/test/compound_value_test.dart | 37 +- example/test/example_built_value_test.dart | 40 -- ...um_class_test.dart => test_enum_test.dart} | 6 +- example/test/value_test.dart | 36 ++ example/tools/build.dart | 8 +- 39 files changed, 2097 insertions(+), 687 deletions(-) create mode 100644 built_json/lib/src/built_json_serializers.dart create mode 100644 built_json_generator/lib/src/library_elements.dart create mode 100644 built_json_generator/lib/src/source_class.dart create mode 100644 built_json_generator/lib/src/source_class.g.dart create mode 100644 built_json_generator/lib/src/source_field.dart create mode 100644 built_json_generator/lib/src/source_field.g.dart create mode 100644 built_json_generator/lib/src/source_library.dart create mode 100644 built_json_generator/lib/src/source_library.g.dart create mode 100644 built_json_generator/tools/build.dart delete mode 100644 example/test/example_built_value_test.dart rename example/test/{example_enum_class_test.dart => test_enum_test.dart} (68%) create mode 100644 example/test/value_test.dart 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/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..8b3f397 100644 --- a/built_json_generator/pubspec.yaml +++ b/built_json_generator/pubspec.yaml @@ -13,9 +13,13 @@ environment: dependencies: analyzer: '>=0.26.1 <1.0.0' built_collection: '^1.0.0' - built_json: '^0.0.1' +# built_json: '^0.0.1' + built_json: + path: ../built_json 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..d30ed1d 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:15:17.703Z part of test_enum; @@ -8,20 +8,23 @@ part of test_enum; // Target: library test_enum // ************************************************************************** -BuiltJsonSerializers _$builtJsonSerializers = new BuiltJsonSerializers() - ..add(new _$TestEnumSerializer()); -BuiltJsonSerializer _$testEnumSerializer = new _$TestEnumSerializer(); - -class _$TestEnumSerializer implements BuiltJsonSerializer { - final Type type = TestEnum; - final String typeName = 'TestEnum'; - Object serialize(BuiltJsonSerializers builtJsonSerializers, TestEnum object, - {String expectedType}) { +Serializers _$serializers = (new Serializers().toBuilder() + ..add(TestEnum.serializer) + ..add(Value.serializer) + ..add(CompoundValue.serializer)).build(); +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; } - 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..bd4e815 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -11,12 +11,16 @@ environment: dependencies: built_collection: '^1.0.0' - built_json: '^0.0.1' +# built_json: '^0.0.1' + built_json: + path: ../built_json built_value: '^0.0.3' enum_class: '^0.0.2' dev_dependencies: - built_json_generator: '^0.0.1' +# built_json_generator: '^0.0.1' + built_json_generator: + path: ../built_json_generator 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(),