blob: f54f95ec78bcdb2a43800a8b55abb1079dd801ac [file] [log] [blame]
// 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_value/src/big_int_serializer.dart';
import 'package:built_value/src/date_time_serializer.dart';
import 'package:built_value/src/duration_serializer.dart';
import 'package:built_value/src/int64_serializer.dart';
import 'package:built_value/src/json_object_serializer.dart';
import 'package:built_value/src/num_serializer.dart';
import 'package:built_value/src/uri_serializer.dart';
import 'package:quiver/core.dart';
import 'src/bool_serializer.dart';
import 'src/built_json_serializers.dart';
import 'src/built_list_multimap_serializer.dart';
import 'src/built_list_serializer.dart';
import 'src/built_map_serializer.dart';
import 'src/built_set_multimap_serializer.dart';
import 'src/built_set_serializer.dart';
import 'src/double_serializer.dart';
import 'src/int_serializer.dart';
import 'src/regexp_serializer.dart';
import 'src/string_serializer.dart';
/// Annotation to trigger code generation of a [Serializers] instance.
///
/// Use like this:
///
/// ```
/// @SerializersFor(const [
/// MySerializableClass,
/// MyOtherSerializableClass,
/// ])
/// final Serializers serializers = _$serializers;
/// ```
///
/// The `_$serializers` value will be generated for you in a part file next
/// to the current source file. It will hold serializers for the types
/// specified plus any types used in their fields, transitively.
class SerializersFor {
final List<Type> types;
const SerializersFor(this.types);
}
/// Serializes all known classes.
///
/// See: https://github.com/google/built_value.dart/tree/master/example
abstract class Serializers {
/// Default [Serializers] that can serialize primitives and collections.
///
/// Use [toBuilder] to add more serializers.
factory Serializers() {
return (SerializersBuilder()
..add(BigIntSerializer())
..add(BoolSerializer())
..add(BuiltListSerializer())
..add(BuiltListMultimapSerializer())
..add(BuiltMapSerializer())
..add(BuiltSetSerializer())
..add(BuiltSetMultimapSerializer())
..add(DateTimeSerializer())
..add(DoubleSerializer())
..add(DurationSerializer())
..add(IntSerializer())
..add(Int64Serializer())
..add(JsonObjectSerializer())
..add(NumSerializer())
..add(RegExpSerializer())
..add(StringSerializer())
..add(UriSerializer())
..addBuilderFactory(const FullType(BuiltList, [FullType.object]),
() => ListBuilder<Object>())
..addBuilderFactory(
const FullType(
BuiltListMultimap, [FullType.object, FullType.object]),
() => ListMultimapBuilder<Object, Object>())
..addBuilderFactory(
const FullType(BuiltMap, [FullType.object, FullType.object]),
() => MapBuilder<Object, Object>())
..addBuilderFactory(const FullType(BuiltSet, [FullType.object]),
() => SetBuilder<Object>())
..addBuilderFactory(
const FullType(
BuiltSetMultimap, [FullType.object, FullType.object]),
() => SetMultimapBuilder<Object, Object>()))
.build();
}
/// The installed [Serializer]s.
Iterable<Serializer> get serializers;
/// Serializes [object].
///
/// A [Serializer] must have been provided for every type the object uses.
///
/// Types that are known statically can be provided via [specifiedType]. This
/// will reduce the amount of data needed on the wire. The exact same
/// [specifiedType] will be needed to deserialize.
///
/// Create one using [SerializersBuilder].
///
/// TODO(davidmorgan): document the wire format.
Object serialize(Object object,
{FullType specifiedType = FullType.unspecified});
/// Convenience method for when you know the type you're serializing.
/// Specify the type by specifying its [Serializer] class. Equivalent to
/// calling [serialize] with a `specifiedType`.
Object serializeWith<T>(Serializer<T> serializer, T object);
/// Deserializes [serialized].
///
/// A [Serializer] must have been provided for every type the object uses.
///
/// If [serialized] was produced by calling [serialize] with [specifiedType],
/// the exact same [specifiedType] must be provided to deserialize.
Object deserialize(Object serialized,
{FullType specifiedType = FullType.unspecified});
/// Convenience method for when you know the type you're deserializing.
/// Specify the type by specifying its [Serializer] class. Equivalent to
/// calling [deserialize] with a `specifiedType`.
T deserializeWith<T>(Serializer<T> serializer, Object serialized);
/// Gets a serializer; returns `null` if none is found. For use in plugins
/// and other extension code.
Serializer serializerForType(Type type);
/// Gets a serializer; returns `null` if none is found. For use in plugins
/// and other extension code.
Serializer serializerForWireName(String wireName);
/// Creates a new builder for the type represented by [fullType].
///
/// For example, if [fullType] is `BuiltList<int, String>`, returns a
/// `ListBuilder<int, String>`. This helps serializers to instantiate with
/// correct generic type parameters.
///
/// Throws a [StateError] if no matching builder factory has been added.
Object newBuilder(FullType fullType);
/// Whether a builder for [fullType] is available via [newBuilder].
bool hasBuilder(FullType fullType);
/// Throws if a builder for [fullType] is not available via [newBuilder].
void expectBuilder(FullType fullType);
/// The installed builder factories.
BuiltMap<FullType, Function> get builderFactories;
SerializersBuilder toBuilder();
}
/// Note: this is an experimental feature. API may change without a major
/// version increase.
abstract class SerializerPlugin {
Object beforeSerialize(Object object, FullType specifiedType);
Object afterSerialize(Object object, FullType specifiedType);
Object beforeDeserialize(Object object, FullType specifiedType);
Object afterDeserialize(Object object, FullType specifiedType);
}
/// Builder for [Serializers].
abstract class SerializersBuilder {
factory SerializersBuilder() = BuiltJsonSerializersBuilder;
/// Adds a [Serializer]. It will be used to handle the type(s) it declares
/// via its `types` property.
void add(Serializer serializer);
/// Adds an iterable of [Serializer].
void addAll(Iterable<Serializer> serializers);
/// Adds a builder factory.
///
/// Builder factories are needed when deserializing to types that use
/// generics. For example, to deserialize a `BuiltList<Foo>`, `built_value`
/// needs a builder factory for `BuiltList<Foo>`.
///
/// `built_value` tries to generate code that will install all the builder
/// factories you need, but this support is incomplete. So you may need to
/// add your own. For example:
///
/// ```dart
/// serializers = (serializers.toBuilder()
/// ..addBuilderFactory(
/// const FullType(BuiltList, [FullType(Foo)]),
/// () => ListBuilder<Foo>(),
/// ))
/// .build();
/// ```
void addBuilderFactory(FullType specifiedType, Function function);
/// Installs a [SerializerPlugin] that applies to all serialization and
/// deserialization.
void addPlugin(SerializerPlugin plugin);
Serializers build();
}
/// A [Type] with, optionally, [FullType] generic type parameters.
///
/// May also be [unspecified], indicating that no type information is
/// available.
class FullType {
/// An unspecified type.
static const FullType unspecified = FullType(null);
/// The [Object] type.
static const FullType object = FullType(Object);
/// The root of the type.
final Type root;
/// Type parameters of the type.
final List<FullType> parameters;
const FullType(this.root, [this.parameters = const []]);
bool get isUnspecified => identical(root, null);
@override
bool operator ==(dynamic other) {
if (identical(other, this)) return true;
if (other is! FullType) return false;
if (root != other.root) return false;
if (parameters.length != other.parameters.length) return false;
for (var i = 0; i != parameters.length; ++i) {
if (parameters[i] != other.parameters[i]) return false;
}
return true;
}
@override
int get hashCode {
return hash2(root, hashObjects(parameters));
}
@override
String toString() => isUnspecified
? 'unspecified'
: parameters.isEmpty
? _getRawName(root)
: '${_getRawName(root)}<${parameters.join(", ")}>';
static String _getRawName(Type type) {
var name = type.toString();
var genericsStart = name.indexOf('<');
return genericsStart == -1 ? name : name.substring(0, genericsStart);
}
}
/// 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`.
///
/// Implementations must extend either [PrimitiveSerializer] or
/// [StructuredSerializer].
abstract class Serializer<T> {
/// The [Type]s that can be serialized.
///
/// They must all be equal to T or a subclass of T. Subclasses are used when
/// T is an abstract class, which is the case with built_value generated
/// serializers.
Iterable<Type> 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;
}
/// A [Serializer] that serializes to and from primitive JSON values.
abstract class PrimitiveSerializer<T> implements Serializer<T> {
/// Serializes [object].
///
/// Use [serializers] as needed for nested serialization. Information about
/// the type being serialized is provided in [specifiedType].
///
/// Returns a value that can be represented as a JSON primitive: a boolean,
/// an integer, a double, or a String.
///
/// TODO(davidmorgan): document the wire format.
Object serialize(Serializers serializers, T object,
{FullType specifiedType = FullType.unspecified});
/// Deserializes [serialized].
///
/// [serialized] is a boolean, an integer, a double or a String.
///
/// Use [serializers] as needed for nested deserialization. Information about
/// the type being deserialized is provided in [specifiedType].
T deserialize(Serializers serializers, Object serialized,
{FullType specifiedType = FullType.unspecified});
}
/// A [Serializer] that serializes to and from an [Iterable] of primitive JSON
/// values.
abstract class StructuredSerializer<T> implements Serializer<T> {
/// Serializes [object].
///
/// Use [serializers] as needed for nested serialization. Information about
/// the type being serialized is provided in [specifiedType].
///
/// Returns an [Iterable] of values that can be represented as structured
/// JSON: booleans, integers, doubles, Strings and [Iterable]s.
///
/// TODO(davidmorgan): document the wire format.
Iterable serialize(Serializers serializers, T object,
{FullType specifiedType = FullType.unspecified});
/// Deserializes [serialized].
///
/// [serialized] is an [Iterable] that may contain booleans, integers,
/// doubles, Strings and/or [Iterable]s.
///
/// Use [serializers] as needed for nested deserialization. Information about
/// the type being deserialized is provided in [specifiedType].
T deserialize(Serializers serializers, Iterable serialized,
{FullType specifiedType = FullType.unspecified});
}
/// [Error] conveying why deserialization failed.
class DeserializationError extends Error {
final String json;
final FullType type;
final Error error;
factory DeserializationError(Object json, FullType type, Error error) {
var limitedJson = json.toString();
if (limitedJson.length > 80) {
limitedJson = limitedJson.replaceRange(77, limitedJson.length, '...');
}
return DeserializationError._(limitedJson, type, error);
}
DeserializationError._(this.json, this.type, this.error);
@override
String toString() => "Deserializing '$json' to '$type' failed due to: $error";
}