Merge `encoding.dart` and `resurrectors.dart` (#30)

This merges the two concepts into one set of `Converter` and
`Codec` types that all live in `codecs.dart`.

Part of #11
diff --git a/lib/src/backends/record_replay/codecs.dart b/lib/src/backends/record_replay/codecs.dart
new file mode 100644
index 0000000..443c337
--- /dev/null
+++ b/lib/src/backends/record_replay/codecs.dart
@@ -0,0 +1,377 @@
+// Copyright (c) 2017, the Dart project authors.  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 'dart:async';
+import 'dart:convert';
+
+import 'package:file/file.dart';
+import 'package:path/path.dart' as path;
+
+import 'common.dart';
+import 'events.dart';
+import 'replay_directory.dart';
+import 'replay_file.dart';
+import 'replay_file_stat.dart';
+import 'replay_file_system.dart';
+import 'replay_link.dart';
+import 'result_reference.dart';
+
+/// Converter that leaves object untouched.
+const Converter<dynamic, dynamic> kPassthrough = const _PassthroughConverter();
+
+/// Converter that will turn an object into a [Future] of that object.
+const Converter<dynamic, dynamic> kFutureReviver =
+    const _FutureDecoder<dynamic>();
+
+/// Converter that will deserialize a [DateTime].
+const Converter<dynamic, dynamic> kDateTimeReviver = _DateTimeCodec.kDecoder;
+
+/// Converter that will deserialize a [FileStat].
+const Converter<dynamic, dynamic> kFileStatReviver = _FileStatCodec.kDecoder;
+
+/// Converter that will deserialize a [FileSystemEntityType].
+const Converter<dynamic, dynamic> kEntityTypeReviver =
+    _EntityTypeCodec.kDecoder;
+
+/// Converter that will deserialize a [path.Context].
+const Converter<dynamic, dynamic> kPathContextReviver =
+    _PathContextCodec.kDecoder;
+
+/// Converter that will deserialize a [ReplayDirectory].
+Converter<dynamic, dynamic> directoryReviver(ReplayFileSystemImpl fileSystem) =>
+    new _DirectoryDecoder(fileSystem);
+
+/// Converter that will deserialize a [ReplayFile].
+Converter<dynamic, dynamic> fileReviver(ReplayFileSystemImpl fileSystem) =>
+    new _FileDecoder(fileSystem);
+
+/// Converter that will deserialize a [ReplayLink].
+Converter<dynamic, dynamic> linkReviver(ReplayFileSystemImpl fileSystem) =>
+    new _LinkDecoder(fileSystem);
+
+/// Encodes an arbitrary [object] into a JSON-ready representation (a number,
+/// boolean, string, null, list, or map).
+///
+/// Returns a value suitable for conversion into JSON using [JsonEncoder]
+/// without the need for a `toEncodable` argument.
+dynamic encode(dynamic object) => const _GenericEncoder().convert(object);
+
+typedef T _ConverterDelegate<S, T>(S input);
+
+class _ForwardingConverter<S, T> extends Converter<S, T> {
+  final _ConverterDelegate<S, T> _delegate;
+
+  const _ForwardingConverter(this._delegate);
+
+  @override
+  T convert(S input) => _delegate(input);
+}
+
+class _GenericEncoder extends Converter<dynamic, dynamic> {
+  const _GenericEncoder();
+
+  /// Known encoders. Types not covered here will be encoded using
+  /// [_encodeDefault].
+  ///
+  /// When encoding an object, we will walk this map in insertion order looking
+  /// for a matching encoder. Thus, when there are two encoders that match an
+  /// object, the first one will win.
+  static const Map<TypeMatcher<dynamic>, Converter<Object, Object>> _encoders =
+      const <TypeMatcher<dynamic>, Converter<Object, Object>>{
+    const TypeMatcher<num>(): const _PassthroughConverter(),
+    const TypeMatcher<bool>(): const _PassthroughConverter(),
+    const TypeMatcher<String>(): const _PassthroughConverter(),
+    const TypeMatcher<Null>(): const _PassthroughConverter(),
+    const TypeMatcher<Iterable<dynamic>>(): const _IterableEncoder(),
+    const TypeMatcher<Map<dynamic, dynamic>>(): const _MapEncoder(),
+    const TypeMatcher<Symbol>(): const _SymbolEncoder(),
+    const TypeMatcher<DateTime>(): _DateTimeCodec.kEncoder,
+    const TypeMatcher<Uri>(): const _ToStringEncoder(),
+    const TypeMatcher<path.Context>(): _PathContextCodec.kEncoder,
+    const TypeMatcher<ResultReference<dynamic>>(): const _ResultEncoder(),
+    const TypeMatcher<LiveInvocationEvent<dynamic>>(): const _EventEncoder(),
+    const TypeMatcher<ReplayAware>(): const _ReplayAwareEncoder(),
+    const TypeMatcher<Encoding>(): const _EncodingEncoder(),
+    const TypeMatcher<FileMode>(): const _FileModeEncoder(),
+    const TypeMatcher<FileStat>(): _FileStatCodec.kEncoder,
+    const TypeMatcher<FileSystemEntityType>(): _EntityTypeCodec.kEncoder,
+    const TypeMatcher<FileSystemEvent>(): const _FileSystemEventEncoder(),
+  };
+
+  /// Default encoder (used for types not covered in [_encoders]).
+  static String _encodeDefault(dynamic object) => object.runtimeType.toString();
+
+  @override
+  dynamic convert(dynamic input) {
+    Converter<dynamic, dynamic> encoder =
+        const _ForwardingConverter<dynamic, String>(_encodeDefault);
+    for (TypeMatcher<dynamic> matcher in _encoders.keys) {
+      if (matcher.matches(input)) {
+        encoder = _encoders[matcher];
+        break;
+      }
+    }
+    return encoder.convert(input);
+  }
+}
+
+class _PassthroughConverter extends Converter<dynamic, dynamic> {
+  const _PassthroughConverter();
+
+  @override
+  dynamic convert(dynamic input) => input;
+}
+
+class _IterableEncoder extends Converter<Iterable<dynamic>, List<dynamic>> {
+  const _IterableEncoder();
+
+  @override
+  List<dynamic> convert(Iterable<dynamic> input) {
+    _GenericEncoder generic = const _GenericEncoder();
+    List<dynamic> encoded = <dynamic>[];
+    for (Object element in input) {
+      encoded.add(generic.convert(element));
+    }
+    return encoded;
+  }
+}
+
+class _MapEncoder
+    extends Converter<Map<dynamic, dynamic>, Map<String, dynamic>> {
+  const _MapEncoder();
+
+  @override
+  Map<String, dynamic> convert(Map<dynamic, dynamic> input) {
+    _GenericEncoder generic = const _GenericEncoder();
+    Map<String, dynamic> encoded = <String, dynamic>{};
+    for (dynamic key in input.keys) {
+      String encodedKey = generic.convert(key);
+      encoded[encodedKey] = generic.convert(input[key]);
+    }
+    return encoded;
+  }
+}
+
+class _SymbolEncoder extends Converter<Symbol, String> {
+  const _SymbolEncoder();
+
+  @override
+  String convert(Symbol input) => getSymbolName(input);
+}
+
+class _DateTimeCodec extends Codec<DateTime, int> {
+  const _DateTimeCodec();
+
+  static int _encode(DateTime input) => input.millisecondsSinceEpoch;
+
+  static DateTime _decode(int input) =>
+      new DateTime.fromMillisecondsSinceEpoch(input);
+
+  static const Converter<DateTime, int> kEncoder =
+      const _ForwardingConverter<DateTime, int>(_encode);
+
+  static const Converter<int, DateTime> kDecoder =
+      const _ForwardingConverter<int, DateTime>(_decode);
+
+  @override
+  Converter<DateTime, int> get encoder => kEncoder;
+
+  @override
+  Converter<int, DateTime> get decoder => kDecoder;
+}
+
+class _ToStringEncoder extends Converter<Object, String> {
+  const _ToStringEncoder();
+
+  @override
+  String convert(Object input) => input.toString();
+}
+
+class _PathContextCodec extends Codec<path.Context, Map<String, String>> {
+  const _PathContextCodec();
+
+  static Map<String, String> _encode(path.Context input) {
+    return <String, String>{
+      'style': input.style.name,
+      'cwd': input.current,
+    };
+  }
+
+  static path.Context _decode(Map<String, String> input) {
+    return new path.Context(
+      style: <String, path.Style>{
+        'posix': path.Style.posix,
+        'windows': path.Style.windows,
+        'url': path.Style.url,
+      }[input['style']],
+      current: input['cwd'],
+    );
+  }
+
+  static const Converter<path.Context, Map<String, String>> kEncoder =
+      const _ForwardingConverter<path.Context, Map<String, String>>(_encode);
+
+  static const Converter<Map<String, String>, path.Context> kDecoder =
+      const _ForwardingConverter<Map<String, String>, path.Context>(_decode);
+
+  @override
+  Converter<path.Context, Map<String, String>> get encoder => kEncoder;
+
+  @override
+  Converter<Map<String, String>, path.Context> get decoder => kDecoder;
+}
+
+class _ResultEncoder extends Converter<ResultReference<dynamic>, Object> {
+  const _ResultEncoder();
+
+  @override
+  Object convert(ResultReference<dynamic> input) => input.serializedValue;
+}
+
+class _EventEncoder
+    extends Converter<LiveInvocationEvent<dynamic>, Map<String, Object>> {
+  const _EventEncoder();
+
+  @override
+  Map<String, Object> convert(LiveInvocationEvent<dynamic> input) {
+    return input.serialize();
+  }
+}
+
+class _ReplayAwareEncoder extends Converter<ReplayAware, String> {
+  const _ReplayAwareEncoder();
+
+  @override
+  String convert(ReplayAware input) => input.identifier;
+}
+
+class _EncodingEncoder extends Converter<Encoding, String> {
+  const _EncodingEncoder();
+
+  @override
+  String convert(Encoding input) => input.name;
+}
+
+class _FileModeEncoder extends Converter<FileMode, String> {
+  const _FileModeEncoder();
+
+  @override
+  String convert(FileMode input) {
+    switch (input) {
+      case FileMode.READ:
+        return 'READ';
+      case FileMode.WRITE:
+        return 'WRITE';
+      case FileMode.APPEND:
+        return 'APPEND';
+      case FileMode.WRITE_ONLY:
+        return 'WRITE_ONLY';
+      case FileMode.WRITE_ONLY_APPEND:
+        return 'WRITE_ONLY_APPEND';
+    }
+    throw new ArgumentError('Invalid value: $input');
+  }
+}
+
+class _FileStatCodec extends Codec<FileStat, Map<String, Object>> {
+  const _FileStatCodec();
+
+  static Map<String, Object> _encode(FileStat input) {
+    return <String, dynamic>{
+      'changed': const _DateTimeCodec().encode(input.changed),
+      'modified': const _DateTimeCodec().encode(input.modified),
+      'accessed': const _DateTimeCodec().encode(input.accessed),
+      'type': const _EntityTypeCodec().encode(input.type),
+      'mode': input.mode,
+      'size': input.size,
+      'modeString': input.modeString(),
+    };
+  }
+
+  static FileStat _decode(Map<String, Object> input) =>
+      new ReplayFileStat(input);
+
+  static const Converter<FileStat, Map<String, Object>> kEncoder =
+      const _ForwardingConverter<FileStat, Map<String, Object>>(_encode);
+
+  static const Converter<Map<String, Object>, FileStat> kDecoder =
+      const _ForwardingConverter<Map<String, Object>, FileStat>(_decode);
+
+  @override
+  Converter<FileStat, Map<String, Object>> get encoder => kEncoder;
+
+  @override
+  Converter<Map<String, Object>, FileStat> get decoder => kDecoder;
+}
+
+class _EntityTypeCodec extends Codec<FileSystemEntityType, String> {
+  const _EntityTypeCodec();
+
+  static String _encode(FileSystemEntityType input) => input.toString();
+
+  static FileSystemEntityType _decode(String input) {
+    return const <String, FileSystemEntityType>{
+      'FILE': FileSystemEntityType.FILE,
+      'DIRECTORY': FileSystemEntityType.DIRECTORY,
+      'LINK': FileSystemEntityType.LINK,
+      'NOT_FOUND': FileSystemEntityType.NOT_FOUND,
+    }[input];
+  }
+
+  static const Converter<FileSystemEntityType, String> kEncoder =
+      const _ForwardingConverter<FileSystemEntityType, String>(_encode);
+
+  static const Converter<String, FileSystemEntityType> kDecoder =
+      const _ForwardingConverter<String, FileSystemEntityType>(_decode);
+
+  @override
+  Converter<FileSystemEntityType, String> get encoder => kEncoder;
+
+  @override
+  Converter<String, FileSystemEntityType> get decoder => kDecoder;
+}
+
+class _FileSystemEventEncoder
+    extends Converter<FileSystemEvent, Map<String, Object>> {
+  const _FileSystemEventEncoder();
+
+  @override
+  Map<String, Object> convert(FileSystemEvent input) {
+    return <String, Object>{
+      'type': input.type,
+      'path': input.path,
+    };
+  }
+}
+
+class _FutureDecoder<T> extends Converter<T, Future<T>> {
+  const _FutureDecoder();
+
+  @override
+  Future<T> convert(T input) async => input;
+}
+
+class _DirectoryDecoder extends Converter<String, Directory> {
+  final ReplayFileSystemImpl fileSystem;
+  const _DirectoryDecoder(this.fileSystem);
+
+  @override
+  Directory convert(String input) => new ReplayDirectory(fileSystem, input);
+}
+
+class _FileDecoder extends Converter<String, File> {
+  final ReplayFileSystemImpl fileSystem;
+  const _FileDecoder(this.fileSystem);
+
+  @override
+  File convert(String input) => new ReplayFile(fileSystem, input);
+}
+
+class _LinkDecoder extends Converter<String, Link> {
+  final ReplayFileSystemImpl fileSystem;
+  const _LinkDecoder(this.fileSystem);
+
+  @override
+  Link convert(String input) => new ReplayLink(fileSystem, input);
+}
diff --git a/lib/src/backends/record_replay/common.dart b/lib/src/backends/record_replay/common.dart
index 2368047..9313f90 100644
--- a/lib/src/backends/record_replay/common.dart
+++ b/lib/src/backends/record_replay/common.dart
@@ -92,11 +92,11 @@
 /// opaque identifier.
 ///
 /// Unlike other objects, objects that are replay-aware don't need to serialize
-/// meaningful metadata about their state for the sake of resurrection. Rather,
-/// they derive all the information they need to operate from the recording.
-/// As such, they are serialized using only an opaque unique identifier. When
-/// they are resurrected during replay, their identifier allows them to find
-/// invocations in the recording for which they are the target.
+/// meaningful metadata about their state for the sake of revival. Rather, they
+/// derive all the information they need to operate from the recording. As such,
+/// they are serialized using only an opaque unique identifier. When they are
+/// revived during replay, their identifier allows them to find invocations in
+/// the recording for which they are the target.
 abstract class ReplayAware {
   /// The identifier of this object, guaranteed to be unique within a single
   /// recording.
diff --git a/lib/src/backends/record_replay/encoding.dart b/lib/src/backends/record_replay/encoding.dart
deleted file mode 100644
index f43c474..0000000
--- a/lib/src/backends/record_replay/encoding.dart
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (c) 2017, the Dart project authors.  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 'dart:convert';
-
-import 'package:file/file.dart';
-import 'package:path/path.dart' as p;
-
-import 'common.dart';
-import 'events.dart';
-import 'result_reference.dart';
-
-/// Encodes an object into a JSON-ready representation.
-///
-/// Must return one of {number, boolean, string, null, list, or map}.
-typedef dynamic _Encoder(dynamic object);
-
-/// Known encoders. Types not covered here will be encoded using
-/// [_encodeDefault].
-///
-/// When encoding an object, we will walk this map in iteration order looking
-/// for a matching encoder. Thus, when there are two encoders that match an
-//  object, the first one will win.
-const Map<TypeMatcher<dynamic>, _Encoder> _kEncoders =
-    const <TypeMatcher<dynamic>, _Encoder>{
-  const TypeMatcher<num>(): _encodeRaw,
-  const TypeMatcher<bool>(): _encodeRaw,
-  const TypeMatcher<String>(): _encodeRaw,
-  const TypeMatcher<Null>(): _encodeRaw,
-  const TypeMatcher<Iterable<dynamic>>(): _encodeIterable,
-  const TypeMatcher<Map<dynamic, dynamic>>(): _encodeMap,
-  const TypeMatcher<Symbol>(): getSymbolName,
-  const TypeMatcher<DateTime>(): _encodeDateTime,
-  const TypeMatcher<Uri>(): _encodeUri,
-  const TypeMatcher<p.Context>(): _encodePathContext,
-  const TypeMatcher<ResultReference<dynamic>>(): _encodeResultReference,
-  const TypeMatcher<LiveInvocationEvent<dynamic>>(): _encodeEvent,
-  const TypeMatcher<ReplayAware>(): _encodeReplayAwareEntity,
-  const TypeMatcher<Encoding>(): _encodeEncoding,
-  const TypeMatcher<FileMode>(): _encodeFileMode,
-  const TypeMatcher<FileStat>(): _encodeFileStat,
-  const TypeMatcher<FileSystemEntityType>(): _encodeFileSystemEntityType,
-  const TypeMatcher<FileSystemEvent>(): _encodeFileSystemEvent,
-};
-
-/// Encodes an arbitrary [object] into a JSON-ready representation (a number,
-/// boolean, string, null, list, or map).
-///
-/// Returns a value suitable for conversion into JSON using [JsonEncoder]
-/// without the need for a `toEncodable` argument.
-dynamic encode(dynamic object) {
-  _Encoder encoder = _encodeDefault;
-  for (TypeMatcher<dynamic> matcher in _kEncoders.keys) {
-    if (matcher.matches(object)) {
-      encoder = _kEncoders[matcher];
-      break;
-    }
-  }
-  return encoder(object);
-}
-
-/// Default encoder (used for types not covered in [_kEncoders]).
-String _encodeDefault(dynamic object) => object.runtimeType.toString();
-
-/// Pass-through encoder (used on `num`, `bool`, `String`, and `Null`).
-dynamic _encodeRaw(dynamic object) => object;
-
-/// Encodes the specified [iterable] into a JSON-ready list of encoded items.
-List<dynamic> _encodeIterable(Iterable<dynamic> iterable) {
-  List<dynamic> encoded = <dynamic>[];
-  for (dynamic element in iterable) {
-    encoded.add(encode(element));
-  }
-  return encoded;
-}
-
-/// Encodes the specified [map] into a JSON-ready map of encoded key/value
-/// pairs.
-Map<String, dynamic> _encodeMap(Map<dynamic, dynamic> map) {
-  Map<String, dynamic> encoded = <String, dynamic>{};
-  for (dynamic key in map.keys) {
-    String encodedKey = encode(key);
-    encoded[encodedKey] = encode(map[key]);
-  }
-  return encoded;
-}
-
-int _encodeDateTime(DateTime dateTime) => dateTime.millisecondsSinceEpoch;
-
-String _encodeUri(Uri uri) => uri.toString();
-
-Map<String, String> _encodePathContext(p.Context context) {
-  return <String, String>{
-    'style': context.style.name,
-    'cwd': context.current,
-  };
-}
-
-dynamic _encodeResultReference(ResultReference<dynamic> reference) =>
-    reference.serializedValue;
-
-Map<String, dynamic> _encodeEvent(LiveInvocationEvent<dynamic> event) =>
-    event.serialize();
-
-String _encodeReplayAwareEntity(ReplayAware entity) => entity.identifier;
-
-String _encodeEncoding(Encoding encoding) => encoding.name;
-
-String _encodeFileMode(FileMode fileMode) {
-  switch (fileMode) {
-    case FileMode.READ:
-      return 'READ';
-    case FileMode.WRITE:
-      return 'WRITE';
-    case FileMode.APPEND:
-      return 'APPEND';
-    case FileMode.WRITE_ONLY:
-      return 'WRITE_ONLY';
-    case FileMode.WRITE_ONLY_APPEND:
-      return 'WRITE_ONLY_APPEND';
-  }
-  throw new ArgumentError('Invalid value: $fileMode');
-}
-
-Map<String, dynamic> _encodeFileStat(FileStat stat) => <String, dynamic>{
-      'changed': _encodeDateTime(stat.changed),
-      'modified': _encodeDateTime(stat.modified),
-      'accessed': _encodeDateTime(stat.accessed),
-      'type': _encodeFileSystemEntityType(stat.type),
-      'mode': stat.mode,
-      'size': stat.size,
-      'modeString': stat.modeString(),
-    };
-
-String _encodeFileSystemEntityType(FileSystemEntityType type) =>
-    type.toString();
-
-Map<String, dynamic> _encodeFileSystemEvent(FileSystemEvent event) =>
-    <String, dynamic>{
-      'type': event.type,
-      'path': event.path,
-    };
diff --git a/lib/src/backends/record_replay/errors.dart b/lib/src/backends/record_replay/errors.dart
index c7892d3..8e199ba 100644
--- a/lib/src/backends/record_replay/errors.dart
+++ b/lib/src/backends/record_replay/errors.dart
@@ -2,8 +2,8 @@
 // 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 'codecs.dart';
 import 'common.dart';
-import 'encoding.dart';
 
 /// Error thrown during replay when there is no matching invocation in the
 /// recording.
diff --git a/lib/src/backends/record_replay/events.dart b/lib/src/backends/record_replay/events.dart
index 75ffd15..65bab49 100644
--- a/lib/src/backends/record_replay/events.dart
+++ b/lib/src/backends/record_replay/events.dart
@@ -4,8 +4,8 @@
 
 import 'dart:async';
 
+import 'codecs.dart';
 import 'common.dart';
-import 'encoding.dart';
 import 'recording.dart';
 import 'result_reference.dart';
 
diff --git a/lib/src/backends/record_replay/mutable_recording.dart b/lib/src/backends/record_replay/mutable_recording.dart
index 60ce88e..2693291 100644
--- a/lib/src/backends/record_replay/mutable_recording.dart
+++ b/lib/src/backends/record_replay/mutable_recording.dart
@@ -8,8 +8,8 @@
 import 'package:file/file.dart';
 import 'package:intl/intl.dart';
 
+import 'codecs.dart';
 import 'common.dart';
-import 'encoding.dart';
 import 'events.dart';
 import 'recording.dart';
 
diff --git a/lib/src/backends/record_replay/replay_directory.dart b/lib/src/backends/record_replay/replay_directory.dart
index 928db67..abac3e1 100644
--- a/lib/src/backends/record_replay/replay_directory.dart
+++ b/lib/src/backends/record_replay/replay_directory.dart
@@ -2,11 +2,12 @@
 // 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 'dart:convert';
+
 import 'package:file/file.dart';
 
 import 'replay_file_system.dart';
 import 'replay_file_system_entity.dart';
-import 'resurrectors.dart';
 
 /// [Directory] implementation that replays all invocation activity from a
 /// prior recording.
@@ -15,7 +16,7 @@
   ReplayDirectory(ReplayFileSystemImpl fileSystem, String identifier)
       : super(fileSystem, identifier) {
     // TODO(tvolkert): fill in resurrectors
-    methods.addAll(<Symbol, Resurrector>{
+    methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
       #create: null,
       #createSync: null,
       #createTemp: null,
diff --git a/lib/src/backends/record_replay/replay_file.dart b/lib/src/backends/record_replay/replay_file.dart
index 5a3eb63..931f5a1 100644
--- a/lib/src/backends/record_replay/replay_file.dart
+++ b/lib/src/backends/record_replay/replay_file.dart
@@ -2,11 +2,12 @@
 // 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 'dart:convert';
+
 import 'package:file/file.dart';
 
 import 'replay_file_system.dart';
 import 'replay_file_system_entity.dart';
-import 'resurrectors.dart';
 
 /// [File] implementation that replays all invocation activity from a prior
 /// recording.
@@ -15,7 +16,7 @@
   ReplayFile(ReplayFileSystemImpl fileSystem, String identifier)
       : super(fileSystem, identifier) {
     // TODO(tvolkert): fill in resurrectors
-    methods.addAll(<Symbol, Resurrector>{
+    methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
       #create: null,
       #createSync: null,
       #copy: null,
diff --git a/lib/src/backends/record_replay/replay_file_stat.dart b/lib/src/backends/record_replay/replay_file_stat.dart
index c0ab022..2e91c75 100644
--- a/lib/src/backends/record_replay/replay_file_stat.dart
+++ b/lib/src/backends/record_replay/replay_file_stat.dart
@@ -4,7 +4,7 @@
 
 import 'package:file/file.dart';
 
-import 'resurrectors.dart';
+import 'codecs.dart';
 
 /// [FileStat] implementation that derives its properties from a recorded
 /// invocation event.
@@ -16,16 +16,16 @@
   ReplayFileStat(Map<String, dynamic> data) : _data = data;
 
   @override
-  DateTime get changed => resurrectDateTime(_data['changed']);
+  DateTime get changed => kDateTimeReviver.convert(_data['changed']);
 
   @override
-  DateTime get modified => resurrectDateTime(_data['modified']);
+  DateTime get modified => kDateTimeReviver.convert(_data['modified']);
 
   @override
-  DateTime get accessed => resurrectDateTime(_data['accessed']);
+  DateTime get accessed => kDateTimeReviver.convert(_data['accessed']);
 
   @override
-  FileSystemEntityType get type => resurrectFileSystemEntityType(_data['type']);
+  FileSystemEntityType get type => kEntityTypeReviver.convert(_data['type']);
 
   @override
   int get mode => _data['mode'];
diff --git a/lib/src/backends/record_replay/replay_file_system.dart b/lib/src/backends/record_replay/replay_file_system.dart
index 5a36354..df77057 100644
--- a/lib/src/backends/record_replay/replay_file_system.dart
+++ b/lib/src/backends/record_replay/replay_file_system.dart
@@ -7,11 +7,11 @@
 import 'package:file/file.dart';
 import 'package:meta/meta.dart';
 
+import 'codecs.dart';
 import 'common.dart';
 import 'errors.dart';
 import 'recording_file_system.dart';
 import 'replay_proxy_mixin.dart';
-import 'resurrectors.dart';
 
 /// A file system that replays invocations from a prior recording for use
 /// in tests.
@@ -70,24 +70,24 @@
     implements ReplayFileSystem, ReplayAware {
   /// Creates a new `ReplayFileSystemImpl`.
   ReplayFileSystemImpl(this.manifest) {
-    methods.addAll(<Symbol, Resurrector>{
-      #directory: resurrectDirectory(this),
-      #file: resurrectFile(this),
-      #link: resurrectLink(this),
-      #stat: resurrectFuture(resurrectFileStat),
-      #statSync: resurrectFileStat,
-      #identical: resurrectFuture(resurrectPassthrough),
-      #identicalSync: resurrectPassthrough,
-      #type: resurrectFuture(resurrectFileSystemEntityType),
-      #typeSync: resurrectFileSystemEntityType,
+    methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
+      #directory: directoryReviver(this),
+      #file: fileReviver(this),
+      #link: linkReviver(this),
+      #stat: kFileStatReviver.fuse(kFutureReviver),
+      #statSync: kFileStatReviver,
+      #identical: kPassthrough.fuse(kFutureReviver),
+      #identicalSync: kPassthrough,
+      #type: kEntityTypeReviver.fuse(kFutureReviver),
+      #typeSync: kEntityTypeReviver,
     });
 
-    properties.addAll(<Symbol, Resurrector>{
-      #path: resurrectPathContext,
-      #systemTempDirectory: resurrectDirectory(this),
-      #currentDirectory: resurrectDirectory(this),
-      const Symbol('currentDirectory='): resurrectPassthrough,
-      #isWatchSupported: resurrectPassthrough,
+    properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
+      #path: kPathContextReviver,
+      #systemTempDirectory: directoryReviver(this),
+      #currentDirectory: directoryReviver(this),
+      const Symbol('currentDirectory='): kPassthrough,
+      #isWatchSupported: kPassthrough,
     });
   }
 
diff --git a/lib/src/backends/record_replay/replay_file_system_entity.dart b/lib/src/backends/record_replay/replay_file_system_entity.dart
index d74ede3..3dfd39d 100644
--- a/lib/src/backends/record_replay/replay_file_system_entity.dart
+++ b/lib/src/backends/record_replay/replay_file_system_entity.dart
@@ -2,11 +2,13 @@
 // 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 'dart:convert';
+
 import 'package:file/file.dart';
 
+import 'codecs.dart';
 import 'replay_file_system.dart';
 import 'replay_proxy_mixin.dart';
-import 'resurrectors.dart';
 
 /// [FileSystemEntity] implementation that replays all invocation activity
 /// from a prior recording.
@@ -16,7 +18,7 @@
   /// Creates a new `ReplayFileSystemEntity`.
   ReplayFileSystemEntity(this.fileSystem, this.identifier) {
     // TODO(tvolkert): fill in resurrectors
-    methods.addAll(<Symbol, Resurrector>{
+    methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
       #exists: null,
       #existsSync: null,
       #rename: null,
@@ -31,8 +33,8 @@
     });
 
     // TODO(tvolkert): fill in resurrectors
-    properties.addAll(<Symbol, Resurrector>{
-      #path: resurrectPassthrough,
+    properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
+      #path: kPassthrough,
       #uri: null,
       #isAbsolute: null,
       #absolute: null,
diff --git a/lib/src/backends/record_replay/replay_link.dart b/lib/src/backends/record_replay/replay_link.dart
index aed0b35..d23e826 100644
--- a/lib/src/backends/record_replay/replay_link.dart
+++ b/lib/src/backends/record_replay/replay_link.dart
@@ -2,11 +2,12 @@
 // 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 'dart:convert';
+
 import 'package:file/file.dart';
 
 import 'replay_file_system.dart';
 import 'replay_file_system_entity.dart';
-import 'resurrectors.dart';
 
 /// [Link] implementation that replays all invocation activity from a prior
 /// recording.
@@ -15,7 +16,7 @@
   ReplayLink(ReplayFileSystemImpl fileSystem, String identifier)
       : super(fileSystem, identifier) {
     // TODO(tvolkert): fill in resurrectors
-    methods.addAll(<Symbol, Resurrector>{
+    methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
       #create: null,
       #createSync: null,
       #update: null,
diff --git a/lib/src/backends/record_replay/replay_proxy_mixin.dart b/lib/src/backends/record_replay/replay_proxy_mixin.dart
index 60a9502..401781b 100644
--- a/lib/src/backends/record_replay/replay_proxy_mixin.dart
+++ b/lib/src/backends/record_replay/replay_proxy_mixin.dart
@@ -2,13 +2,14 @@
 // 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 'dart:convert';
+
 import 'package:meta/meta.dart';
 
+import 'codecs.dart';
 import 'common.dart';
-import 'encoding.dart';
 import 'errors.dart';
 import 'proxy.dart';
-import 'resurrectors.dart';
 
 typedef bool _InvocationMatcher(Map<String, dynamic> entry);
 
@@ -47,37 +48,39 @@
 ///       final String identifier;
 ///
 ///       ReplayFoo(this.manifest, this.identifier) {
-///         methods.addAll(<Symbol, Resurrector>{
-///           #sampleMethod: resurrectComplexObject,
+///         methods.addAll(<Symbol, Converter<dynamic, dynamic>>{
+///           #sampleMethod: complexObjectReviver,
 ///         });
 ///
-///         properties.addAll(<Symbol, Resurrector>{
-///           #sampleParent: resurrectFoo,
-///           const Symbol('sampleParent='): resurrectPassthrough,
+///         properties.addAll(<Symbol, Converter<dynamic, dynamic>>{
+///           #sampleParent: fooReviver,
+///           const Symbol('sampleParent='): passthroughReviver,
 ///         });
 ///       }
 ///     }
 abstract class ReplayProxyMixin implements ProxyObject, ReplayAware {
-  /// Maps method names to [Resurrector] functions.
+  /// Maps method names to [Converter]s that will revive result values.
   ///
   /// Invocations of methods listed in this map will be replayed by looking for
-  /// matching invocations in the [manifest] and resurrecting the invocation
-  /// return value using the [Resurrector] found in this map.
+  /// matching invocations in the [manifest] and reviving the invocation return
+  /// value using the [Converter] found in this map.
   @protected
-  final Map<Symbol, Resurrector> methods = <Symbol, Resurrector>{};
+  final Map<Symbol, Converter<dynamic, dynamic>> methods =
+      <Symbol, Converter<dynamic, dynamic>>{};
 
-  /// Maps property getter and setter names to [Resurrector] functions.
+  /// Maps property getter and setter names to [Converter]s that will revive
+  /// result values.
   ///
   /// Access and mutation of properties listed in this map will be replayed
-  /// by looking for matching property accesses in the [manifest] and
-  /// resurrecting the invocation return value using the [Resurrector] found
-  /// in this map.
+  /// by looking for matching property accesses in the [manifest] and reviving
+  /// the invocation return value using the [Converter] found in this map.
   ///
   /// The keys for property getters are the simple property names, whereas the
   /// keys for property setters are the property names followed by an equals
   /// sign (e.g. `propertyName=`).
   @protected
-  final Map<Symbol, Resurrector> properties = <Symbol, Resurrector>{};
+  final Map<Symbol, Converter<dynamic, dynamic>> properties =
+      <Symbol, Converter<dynamic, dynamic>>{};
 
   /// The manifest of recorded invocation events.
   ///
@@ -92,11 +95,11 @@
   @override
   dynamic noSuchMethod(Invocation invocation) {
     Symbol name = invocation.memberName;
-    Resurrector resurrector =
+    Converter<dynamic, dynamic> reviver =
         invocation.isAccessor ? properties[name] : methods[name];
 
-    if (resurrector == null) {
-      // No resurrector generally means that there truly is no such method on
+    if (reviver == null) {
+      // No reviver generally means that there truly is no such method on
       // this object. The exception is when the invocation represents a getter
       // on a method, in which case we return a method proxy that, when
       // invoked, will replay the desired invocation.
@@ -111,7 +114,7 @@
     }
     entry[kManifestOrdinalKey] = _nextOrdinal++;
 
-    return resurrector(entry[kManifestResultKey]);
+    return reviver.convert(entry[kManifestResultKey]);
   }
 
   /// Finds the next available invocation event in the [manifest] that matches
diff --git a/lib/src/backends/record_replay/result_reference.dart b/lib/src/backends/record_replay/result_reference.dart
index 211d006..fa6e9c4 100644
--- a/lib/src/backends/record_replay/result_reference.dart
+++ b/lib/src/backends/record_replay/result_reference.dart
@@ -6,7 +6,7 @@
 
 import 'package:meta/meta.dart';
 
-import 'encoding.dart';
+import 'codecs.dart';
 import 'events.dart';
 import 'recording_proxy_mixin.dart';
 
diff --git a/lib/src/backends/record_replay/resurrectors.dart b/lib/src/backends/record_replay/resurrectors.dart
deleted file mode 100644
index a221cc7..0000000
--- a/lib/src/backends/record_replay/resurrectors.dart
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2017, the Dart project authors.  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 'dart:async';
-
-import 'package:file/file.dart';
-import 'package:path/path.dart' as path;
-
-import 'replay_directory.dart';
-import 'replay_file.dart';
-import 'replay_file_stat.dart';
-import 'replay_file_system.dart';
-import 'replay_link.dart';
-
-/// Resurrects an invocation result (return value) from the specified
-/// serialized [data].
-typedef Object Resurrector(dynamic data);
-
-/// Returns a [Resurrector] that will wrap the return value of the specified
-/// [delegate] in a [Future].
-Resurrector resurrectFuture(Resurrector delegate) {
-  return (dynamic serializedResult) async => delegate(serializedResult);
-}
-
-/// Returns a [Resurrector] that will resurrect a [ReplayDirectory] that is
-/// tied to the specified [fileSystem].
-Resurrector resurrectDirectory(ReplayFileSystemImpl fileSystem) {
-  return (String identifier) {
-    return new ReplayDirectory(fileSystem, identifier);
-  };
-}
-
-/// Returns a [Resurrector] that will resurrect a [ReplayFile] that is tied to
-/// the specified [fileSystem].
-Resurrector resurrectFile(ReplayFileSystemImpl fileSystem) {
-  return (String identifier) {
-    return new ReplayFile(fileSystem, identifier);
-  };
-}
-
-/// Returns a [Resurrector] that will resurrect a [ReplayLink] that is tied to
-/// the specified [fileSystem].
-Resurrector resurrectLink(ReplayFileSystemImpl fileSystem) {
-  return (String identifier) {
-    return new ReplayLink(fileSystem, identifier);
-  };
-}
-
-/// Resurrects a [FileStat] from the specified serialized [data].
-FileStat resurrectFileStat(Map<String, dynamic> data) {
-  return new ReplayFileStat(data);
-}
-
-/// Resurrects a [DateTime] from the specified [milliseconds] since the epoch.
-DateTime resurrectDateTime(int milliseconds) {
-  return new DateTime.fromMillisecondsSinceEpoch(milliseconds);
-}
-
-/// Resurrects a [FileSystemEntityType] from the specified string
-/// representation.
-FileSystemEntityType resurrectFileSystemEntityType(String type) {
-  return const <String, FileSystemEntityType>{
-    'FILE': FileSystemEntityType.FILE,
-    'DIRECTORY': FileSystemEntityType.DIRECTORY,
-    'LINK': FileSystemEntityType.LINK,
-    'NOT_FOUND': FileSystemEntityType.NOT_FOUND,
-  }[type];
-}
-
-/// Resurrects a value whose serialized representation is the same the real
-/// value.
-dynamic resurrectPassthrough(dynamic value) => value;
-
-/// Resurrects a [path.Context] from the specified serialized [data]
-path.Context resurrectPathContext(Map<String, String> data) {
-  return new path.Context(
-    style: <String, path.Style>{
-      'posix': path.Style.posix,
-      'windows': path.Style.windows,
-      'url': path.Style.url,
-    }[data['style']],
-    current: data['cwd'],
-  );
-}
diff --git a/test/recording_test.dart b/test/recording_test.dart
index 545b146..7efcf2f 100644
--- a/test/recording_test.dart
+++ b/test/recording_test.dart
@@ -9,8 +9,8 @@
 import 'package:file/memory.dart';
 import 'package:file/record_replay.dart';
 import 'package:file/testing.dart';
+import 'package:file/src/backends/record_replay/codecs.dart';
 import 'package:file/src/backends/record_replay/common.dart';
-import 'package:file/src/backends/record_replay/encoding.dart';
 import 'package:file/src/backends/record_replay/events.dart';
 import 'package:file/src/backends/record_replay/mutable_recording.dart';
 import 'package:file/src/backends/record_replay/recording_proxy_mixin.dart';