Repository: OpenMined/PySyft

Issue:
Dinamically generate simplifiers and detailers list
**Describe the bug**

Serde file is not manageable in the current format.

It' very easy to unintentionally breaking `serde.py` by updating simplifiers and detailers list incorrectly. 

A fix was discussed at #2230 which consists of dynamically generating the lists during init.

**Pros**: easier to maintain code.

**Cons**: overhead during initialization.


Discussion:

Resolving patch:
diff --git a/syft/serde/native_serde.py b/syft/serde/native_serde.py
--- a/syft/serde/native_serde.py
+++ b/syft/serde/native_serde.py
@@ -2,10 +2,206 @@
 This file exists to provide one common place for all serialisation and simplify_ and _detail
 for all native python objects.
 """
-from syft.workers import AbstractWorker
+from collections import OrderedDict
+from typing import Collection
+from typing import Dict
 from typing import Tuple
+
 import numpy
 
+from syft.workers import AbstractWorker
+from syft import serde
+
+
+# Simplify/Detail Collections (list, set, tuple, etc.)
+
+
+def _simplify_collection(my_collection: Collection) -> Collection:
+    """
+    This function is designed to search a collection for any objects
+    which may need to be simplified (i.e., torch tensors). It iterates
+    through each object in the collection and calls _simplify on it. Finally,
+    it returns the output collection as the same type as the input collection
+    so that the consuming serialization step knows the correct type info. The
+    reverse function to this function is _detail_collection, which undoes
+    the functionality of this function.
+
+    Args:
+        my_collection (Collection): a collection of python objects
+
+    Returns:
+        Collection: a collection of the same type as the input of simplified
+            objects.
+
+    """
+
+    # Step 0: get collection type for later use and itialize empty list
+    my_type = type(my_collection)
+    pieces = list()
+
+    # Step 1: serialize each part of the collection
+    for part in my_collection:
+        pieces.append(serde._simplify(part))
+
+    # Step 2: convert back to original type and return serialization
+    if my_type == set:
+        return pieces
+    return my_type(pieces)
+
+
+def _detail_collection_list(worker: AbstractWorker, my_collection: Collection) -> Collection:
+    """
+    This function is designed to operate in the opposite direction of
+    _simplify_collection. It takes a collection of simple python objects
+    and iterates through it to determine whether objects in the collection
+    need to be converted into more advanced types. In particular, it
+    converts binary objects into torch Tensors where appropriate.
+
+    Args:
+        worker: the worker doing the deserialization
+        my_collection (Collection): a collection of simple python objects (including binary).
+
+    Returns:
+        Collection: a collection of the same type as the input where the objects
+            in the collection have been detailed.
+    """
+
+    pieces = list()
+
+    # Step 1: deserialize each part of the collection
+    for part in my_collection:
+        try:
+            pieces.append(
+                serde._detail(worker, part).decode("utf-8")
+            )  # transform bytes back to string
+        except AttributeError:
+            pieces.append(serde._detail(worker, part))
+
+    return pieces
+
+
+def _detail_collection_set(worker: AbstractWorker, my_collection: Collection) -> Collection:
+    """
+    This function is designed to operate in the opposite direction of
+    _simplify_collection. It takes a collection of simple python objects
+    and iterates through it to determine whether objects in the collection
+    need to be converted into more advanced types. In particular, it
+    converts binary objects into torch Tensors where appropriate.
+
+    Args:
+        worker: the worker doing the deserialization
+        my_collection (Collection): a collection of simple python objects (including binary).
+
+    Returns:
+        Collection: a collection of the same type as the input where the objects
+            in the collection have been detailed.
+    """
+
+    pieces = list()
+
+    # Step 1: deserialize each part of the collection
+    for part in my_collection:
+        try:
+            pieces.append(
+                serde._detail(worker, part).decode("utf-8")
+            )  # transform bytes back to string
+        except AttributeError:
+            pieces.append(serde._detail(worker, part))
+    return set(pieces)
+
+
+def _detail_collection_tuple(worker: AbstractWorker, my_tuple: Tuple) -> Tuple:
+    """
+    This function is designed to operate in the opposite direction of
+    _simplify_collection. It takes a tuple of simple python objects
+    and iterates through it to determine whether objects in the collection
+    need to be converted into more advanced types. In particular, it
+    converts binary objects into torch Tensors where appropriate.
+    This is only applicable to tuples. They need special handling because
+    `msgpack` is encoding a tuple as a list.
+
+    Args:
+        worker: the worker doing the deserialization
+        my_tuple (Tuple): a collection of simple python objects (including binary).
+
+    Returns:
+        tuple: a collection of the same type as the input where the objects
+            in the collection have been detailed.
+    """
+
+    pieces = list()
+
+    # Step 1: deserialize each part of the collection
+    for part in my_tuple:
+        pieces.append(serde._detail(worker, part))
+
+    return tuple(pieces)
+
+
+def _simplify_dictionary(my_dict: Dict) -> Dict:
+    """
+    This function is designed to search a dict for any objects
+    which may need to be simplified (i.e., torch tensors). It iterates
+    through each key, value in the dict and calls _simplify on it. Finally,
+    it returns the output dict as the same type as the input dict
+    so that the consuming serialization step knows the correct type info. The
+    reverse function to this function is _detail_dictionary, which undoes
+    the functionality of this function.
+
+    Args:
+        my_dict: A dictionary of python objects.
+
+    Returns:
+        Dict: A dictionary of the same type as the input of simplified
+            objects.
+
+    """
+    pieces = list()
+    # for dictionaries we want to simplify both the key and the value
+    for key, value in my_dict.items():
+        pieces.append((serde._simplify(key), serde._simplify(value)))
+
+    return pieces
+
+
+def _detail_dictionary(worker: AbstractWorker, my_dict: Dict) -> Dict:
+    """
+    This function is designed to operate in the opposite direction of
+    _simplify_dictionary. It takes a dictionary of simple python objects
+    and iterates through it to determine whether objects in the collection
+    need to be converted into more advanced types. In particular, it
+    converts binary objects into torch Tensors where appropriate.
+
+    Args:
+        worker: the worker doing the deserialization
+        my_dict (Dict): a dictionary of simple python objects (including binary).
+
+    Returns:
+        tuple: a collection of the same type as the input where the objects
+            in the collection have been detailed.
+    """
+    pieces = {}
+    # for dictionaries we want to detail both the key and the value
+    for key, value in my_dict:
+        detailed_key = serde._detail(worker, key)
+        try:
+            detailed_key = detailed_key.decode("utf-8")
+        except AttributeError:
+            pass
+
+        detailed_value = serde._detail(worker, value)
+        try:
+            detailed_value = detailed_value.decode("utf-8")
+        except AttributeError:
+            pass
+
+        pieces[detailed_key] = detailed_value
+
+    return pieces
+
+
+# Simplify/Detail native types
+
 
 def _simplify_str(obj: str) -> tuple:
     return (obj.encode("utf-8"),)
@@ -15,9 +211,6 @@ def _detail_str(worker: AbstractWorker, str_tuple: tuple) -> str:
     return str_tuple[0].decode("utf-8")
 
 
-# Range
-
-
 def _simplify_range(my_range: range) -> Tuple[int, int, int]:
     """
     This function extracts the start, stop and step from the range.
@@ -107,53 +300,17 @@ def _detail_slice(worker: AbstractWorker, my_slice: Tuple[int, int, int]) -> sli
     return slice(my_slice[0], my_slice[1], my_slice[2])
 
 
-def _simplify_ndarray(my_array: numpy.ndarray) -> Tuple[bin, Tuple, str]:
-    """
-    This function gets the byte representation of the array
-        and stores the dtype and shape for reconstruction
-
-    Args:
-        my_array (numpy.ndarray): a numpy array
-
-    Returns:
-        list: a list holding the byte representation, shape and dtype of the array
-
-    Examples:
-
-        arr_representation = _simplify_ndarray(numpy.random.random([1000, 1000])))
-
-    """
-    arr_bytes = my_array.tobytes()
-    arr_shape = my_array.shape
-    arr_dtype = my_array.dtype.name
-
-    return (arr_bytes, arr_shape, arr_dtype)
-
-
-def _detail_ndarray(
-    worker: AbstractWorker, arr_representation: Tuple[bin, Tuple, str]
-) -> numpy.ndarray:
-    """
-    This function reconstruct a numpy array from it's byte data, the shape and the dtype
-        by first loading the byte data with the appropiate dtype and then reshaping it into the
-        original shape
-
-    Args:
-        worker: the worker doing the deserialization
-        arr_representation (tuple): a tuple holding the byte representation, shape
-        and dtype of the array
-
-    Returns:
-        numpy.ndarray: a numpy array
-
-    Examples:
-        arr = _detail_ndarray(arr_representation)
-
-    """
-    res = numpy.frombuffer(arr_representation[0], dtype=arr_representation[2]).reshape(
-        arr_representation[1]
-    )
-
-    assert type(res) == numpy.ndarray
-
-    return res
+# Maps a type to a tuple containing its simplifier and detailer function
+# IMPORTANT: keep this structure sorted A-Z (by type name)
+MAP_NATIVE_SIMPLIFIERS_AND_DETAILERS = OrderedDict(
+    {
+        dict: (_simplify_dictionary, _detail_dictionary),
+        list: (_simplify_collection, _detail_collection_list),
+        range: (_simplify_range, _detail_range),
+        set: (_simplify_collection, _detail_collection_set),
+        slice: (_simplify_slice, _detail_slice),
+        str: (_simplify_str, _detail_str),
+        tuple: (_simplify_collection, _detail_collection_tuple),
+        type(Ellipsis): (_simplify_ellipsis, _detail_ellipsis),
+    }
+)
diff --git a/syft/serde/serde.py b/syft/serde/serde.py
--- a/syft/serde/serde.py
+++ b/syft/serde/serde.py
@@ -29,8 +29,9 @@
 serialization process, it can override the functions _serialize_tensor and _deserialize_tensor
 
 By default, we serialize using msgpack and compress using lz4.
-If different compressions are required, the worker can override the function _apply_compress_scheme
+If different compressions are required, the worker can override the function apply_compress_scheme
 """
+from collections import OrderedDict
 import torch
 import msgpack
 import lz4
@@ -44,7 +45,7 @@
 
 from syft.federated import TrainConfig
 
-from syft.workers import AbstractWorker  #
+from syft.workers import AbstractWorker
 from syft.workers import VirtualWorker
 
 from syft.federated import Plan
@@ -59,47 +60,113 @@
 from syft.frameworks.torch.tensors.interpreters import MultiPointerTensor
 from syft.frameworks.torch import pointers
 
+from syft.serde.native_serde import MAP_NATIVE_SIMPLIFIERS_AND_DETAILERS
+from syft.serde.torch_serde import MAP_TORCH_SIMPLIFIERS_AND_DETAILERS
 
-from syft.serde.native_serde import (
-    _simplify_str,
-    _simplify_range,
-    _simplify_ellipsis,
-    _simplify_slice,
-    _detail_str,
-    _detail_range,
-    _detail_ellipsis,
-    _detail_slice,
+# Maps a type to a tuple containing its simplifier and detailer function
+MAP_TO_SIMPLIFIERS_AND_DETAILERS = OrderedDict(
+    list(MAP_NATIVE_SIMPLIFIERS_AND_DETAILERS.items())
+    + list(MAP_TORCH_SIMPLIFIERS_AND_DETAILERS.items())
 )
 
-from syft.serde.torch_serde import (
-    _detail_torch_tensor,
-    _detail_torch_parameter,
-    _detail_collection_tuple,
-    _detail_collection_list,
-    _detail_collection_set,
-    _detail_dictionary,
-    _detail_ndarray,
-    _detail_torch_device,
-    _force_full_detail,
-    _detail_script_module,
-    _simplify_torch_tensor,
-    _simplify_torch_parameter,
-    _simplify_collection,
-    _simplify_dictionary,
-    _simplify_ndarray,
-    _simplify_torch_device,
-    _force_full_simplify,
-    _simplify_script_module,
-)
+# Objects that we can run force full simplilfy on
+MAP_TO_FORCE_FULL_SIMPLIFY = [VirtualWorker]
+
+# If a object implements its own simplifer and detailer function it should be stored in this list
+OBJ_SIMPLIFIER_AND_DETAILERS = [
+    AdditiveSharingTensor,
+    LoggingTensor,
+    MultiPointerTensor,
+    Plan,
+    pointers.PointerTensor,
+    pointers.ObjectWrapper,
+    TrainConfig,
+    VirtualWorker,
+]
 
+EXCEPTION_SIMPLIFIER_AND_DETAILERS = [GetNotPermittedError, ResponseSignatureError]
 
 # COMPRESSION SCHEME INT CODES
 NO_COMPRESSION = 40
 LZ4 = 41
 ZSTD = 42
 
+## SECTION: High Level Simplification Router
+def _force_full_simplify(obj: object) -> object:
+    current_type = type(obj)
+
+    if current_type in forced_full_simplifiers:
+
+        left = forced_full_simplifiers[current_type][0]
+
+        right = forced_full_simplifiers[current_type][1]
+
+        right = right(obj)
+
+        result = (left, right)
+    else:
+        result = _simplify(obj)
+
+    return result
 
-# High Level Public Functions (these are the ones you use)
+
+def _force_full_detail(worker: AbstractWorker, worker_tuple: tuple) -> tuple:
+    worker_id, _objects, auto_add = worker_tuple
+    worker_id = _detail(worker, worker_id)
+
+    result = sy.VirtualWorker(sy.hook, worker_id, auto_add=auto_add)
+    _objects = _detail(worker, _objects)
+    result._objects = _objects
+
+    # make sure they weren't accidentally double registered
+    for _, obj in _objects.items():
+        if obj.id in worker._objects:
+            del worker._objects[obj.id]
+
+    return result
+
+
+## SECTION: dinamically generate simplifiers and detailers
+def _generate_simplifiers_and_detailers():
+    """Generate simplifiers, forced full simplifiers and detailers."""
+    simplifiers = OrderedDict()
+    forced_full_simplifiers = OrderedDict()
+    detailers = []
+
+    def _add_simplifier_and_detailer(curr_type, simplifier, detailer, forced=False):
+        if detailer in detailers:
+            curr_index = detailers.index(detailer)
+        else:
+            curr_index = len(detailers)
+            detailers.append(detailer)
+
+        if forced:
+            forced_full_simplifiers[curr_type] = (curr_index, simplifier)
+        else:
+            simplifiers[curr_type] = (curr_index, simplifier)
+
+    # Register native and torch types
+    for curr_type in MAP_TO_SIMPLIFIERS_AND_DETAILERS:
+        simplifier, detailer = MAP_TO_SIMPLIFIERS_AND_DETAILERS[curr_type]
+        _add_simplifier_and_detailer(curr_type, simplifier, detailer)
+
+    # Register syft objects with custom simplify and detail methods
+    for syft_type in OBJ_SIMPLIFIER_AND_DETAILERS + EXCEPTION_SIMPLIFIER_AND_DETAILERS:
+        simplifier, detailer = syft_type.simplify, syft_type.detail
+        _add_simplifier_and_detailer(syft_type, simplifier, detailer)
+
+    # Add forced full simplifiers
+    for curr_type in MAP_TO_FORCE_FULL_SIMPLIFY:
+        _add_simplifier_and_detailer(
+            curr_type, _force_full_simplify, _force_full_detail, forced=True
+        )
+
+    return simplifiers, forced_full_simplifiers, detailers
+
+
+simplifiers, forced_full_simplifiers, detailers = _generate_simplifiers_and_detailers()
+
+## SECTION:  High Level Public Functions (these are the ones you use)
 def serialize(
     obj: object,
     simplified: bool = False,
@@ -183,7 +250,7 @@ def deserialize(binary: bin, worker: AbstractWorker = None, details=True) -> obj
         worker (AbstractWorker): the worker which is acquiring the message content,
             for example used to specify the owner of a tensor received(not obvious
             for virtual workers)
-        detail (bool): there are some cases where we need to perform the decompression
+        details (bool): there are some cases where we need to perform the decompression
             and deserialization part, but we don't need to detail all the message.
             This is the case for Plan workers for instance
 
@@ -215,7 +282,7 @@ def deserialize(binary: bin, worker: AbstractWorker = None, details=True) -> obj
         return simple_objects
 
 
-# Chosen Compression Algorithm
+## SECTION: chosen Compression Algorithm
 
 
 def _apply_compress_scheme(decompressed_input_bin) -> tuple:
@@ -276,9 +343,7 @@ def _compress(decompressed_input_bin: bin) -> bin:
         bin: a compressed binary
 
     """
-
     compress_stream, compress_scheme = _apply_compress_scheme(decompressed_input_bin)
-
     if len(compress_stream) < len(decompressed_input_bin):
         return compress_scheme.to_bytes(1, byteorder="big") + compress_stream
     else:
@@ -315,9 +380,6 @@ def _decompress(binary: bin) -> bin:
         )
 
 
-# High Level Simplification Router
-
-
 def _simplify(obj: object) -> object:
     """
     This function takes an object as input and returns a simple
@@ -331,29 +393,24 @@ def _simplify(obj: object) -> object:
     being sent.
 
     Args:
-        obj: an object which may need to be simplified
+        obj: an object which may need to be simplified.
 
     Returns:
-        obj: an simple Python object which msgpack can serialize
+        obj: an simple Python object which msgpack can serialize.
 
     Raises:
         ValueError: if `move_this` or `in_front_of_that` are not both single ASCII
         characters.
-
     """
-
     try:
         # check to see if there is a simplifier
         # for this type. If there is, run return
         # the simplified object
         current_type = type(obj)
-
         result = (simplifiers[current_type][0], simplifiers[current_type][1](obj))
-
         return result
 
     except KeyError:
-
         # if there is not a simplifier for this
         # object, then the object is already a
         # simple python object and we can just
@@ -361,105 +418,23 @@ def _simplify(obj: object) -> object:
         return obj
 
 
-def _force_full_simplify(obj: object) -> object:
-    current_type = type(obj)
-
-    if current_type in forced_full_simplifiers:
-
-        left = forced_full_simplifiers[current_type][0]
-
-        right = forced_full_simplifiers[current_type][1]
-
-        right = right(obj)
-
-        result = (left, right)
-    else:
-        result = _simplify(obj)
-
-    return result
-
-
-simplifiers = {
-    torch.Tensor: [0, _simplify_torch_tensor],
-    torch.nn.Parameter: [1, _simplify_torch_parameter],
-    tuple: [2, _simplify_collection],
-    list: [3, _simplify_collection],
-    set: [4, _simplify_collection],
-    dict: [5, _simplify_dictionary],
-    range: [6, _simplify_range],
-    numpy.ndarray: [7, _simplify_ndarray],
-    slice: [8, _simplify_slice],
-    type(Ellipsis): [9, _simplify_ellipsis],
-    torch.device: [10, _simplify_torch_device],
-    pointers.PointerTensor: [11, sy.PointerTensor.simplify],
-    LoggingTensor: [12, sy.LoggingTensor.simplify],
-    AdditiveSharingTensor: [13, sy.AdditiveSharingTensor.simplify],
-    MultiPointerTensor: [14, sy.MultiPointerTensor.simplify],
-    Plan: [15, sy.Plan.simplify],
-    VirtualWorker: [16, sy.VirtualWorker.simplify],
-    str: [18, _simplify_str],
-    pointers.ObjectWrapper: [19, sy.ObjectWrapper.simplify],
-    GetNotPermittedError: [20, sy.exceptions.GetNotPermittedError.simplify],
-    ResponseSignatureError: [20, sy.exceptions.ResponseSignatureError.simplify],
-    torch.jit.ScriptModule: [21, _simplify_script_module],
-    torch.jit.TopLevelTracedModule: [
-        21,
-        _simplify_script_module,
-    ],  # treat as torch.jit.ScriptModule
-    TrainConfig: [22, sy.TrainConfig.simplify],
-}
-
-
-forced_full_simplifiers = {VirtualWorker: [19, _force_full_simplify]}
-
-
 def _detail(worker: AbstractWorker, obj: object) -> object:
-    """
-    This function reverses the functionality of _simplify. Where applicable,
-    it converts simple objects into more complex objects such as converting
-    binary objects into torch tensors. Read _simplify for more information on
-    why _simplify and _detail are needed.
+    """Reverses the functionality of _simplify.
+    Where applicable, it converts simple objects into more complex objects such
+    as converting binary objects into torch tensors. Read _simplify for more
+    information on why _simplify and detail are needed.
 
     Args:
         worker: the worker which is acquiring the message content, for example
         used to specify the owner of a tensor received(not obvious for
-        virtual workers)
-        obj: a simple Python object which msgpack deserialized
+        virtual workers).
+        obj: a simple Python object which msgpack deserialized.
 
     Returns:
         obj: a more complex Python object which msgpack would have had trouble
             deserializing directly.
-
     """
-
     if type(obj) in (list, tuple):
         return detailers[obj[0]](worker, obj[1])
     else:
         return obj
-
-
-detailers = [
-    _detail_torch_tensor,
-    _detail_torch_parameter,
-    _detail_collection_tuple,
-    _detail_collection_list,
-    _detail_collection_set,
-    _detail_dictionary,
-    _detail_range,
-    _detail_ndarray,
-    _detail_slice,
-    _detail_ellipsis,
-    _detail_torch_device,
-    sy.PointerTensor.detail,
-    sy.LoggingTensor.detail,
-    sy.AdditiveSharingTensor.detail,
-    sy.MultiPointerTensor.detail,
-    sy.Plan.detail,
-    sy.VirtualWorker.detail,
-    _force_full_detail,
-    _detail_str,
-    sy.ObjectWrapper.detail,
-    sy.exceptions.GetNotPermittedError.detail,
-    _detail_script_module,
-    sy.TrainConfig.detail,
-]
diff --git a/syft/serde/torch_serde.py b/syft/serde/torch_serde.py
--- a/syft/serde/torch_serde.py
+++ b/syft/serde/torch_serde.py
@@ -1,10 +1,9 @@
 """
 This file exists to provide one common place for all serialisation and simplify_ and _detail
-for all tensors (Torch) and collections.
+for all tensors (Torch and Numpy).
 """
+from collections import OrderedDict
 from tempfile import TemporaryFile
-from typing import Collection
-from typing import Dict
 from typing import Tuple
 import torch
 
@@ -13,7 +12,6 @@
 import warnings
 
 import syft
-import syft as sy
 
 from syft.federated import TrainConfig
 
@@ -32,18 +30,6 @@
 from syft.frameworks.torch import pointers
 
 
-from syft.serde.native_serde import (
-    _simplify_slice,
-    _simplify_ellipsis,
-    _simplify_range,
-    _simplify_str,
-    _detail_slice,
-    _detail_ellipsis,
-    _detail_range,
-    _detail_str,
-)
-
-
 def _serialize_tensor(tensor) -> bin:
     """Serialize the tensor using as default Torch serialization strategy
     This function can be overridden to provide different tensor serialization strategies
@@ -153,7 +139,7 @@ def _simplify_torch_tensor(tensor: torch.Tensor) -> bin:
     # I think the pointer bug is is between here
 
     if hasattr(tensor, "child"):
-        chain = _simplify(tensor.child)
+        chain = syft.serde._simplify(tensor.child)
 
     # and here... leaving a reerence here so i can find it later
     # TODO fix pointer bug
@@ -212,7 +198,7 @@ def _detail_torch_tensor(worker: AbstractWorker, tensor_tuple: tuple) -> torch.T
         tensor.description = description
 
     if chain is not None:
-        chain = detail(worker, chain)
+        chain = syft.serde._detail(worker, chain)
         tensor.child = chain
         tensor.is_wrapper = True
 
@@ -282,193 +268,7 @@ def _detail_torch_parameter(worker: AbstractWorker, param_tuple: tuple) -> torch
     return param
 
 
-# Simplify/Detail Collections (list, set, tuple, etc.)
-
-
-def _simplify_collection(my_collection: Collection) -> Collection:
-    """
-    This function is designed to search a collection for any objects
-    which may need to be simplified (i.e., torch tensors). It iterates
-    through each object in the collection and calls _simplify on it. Finally,
-    it returns the output collection as the same type as the input collection
-    so that the consuming serialization step knows the correct type info. The
-    reverse function to this function is _detail_collection, which undoes
-    the functionality of this function.
-
-    Args:
-        my_collection (Collection): a collection of python objects
-
-    Returns:
-        Collection: a collection of the same type as the input of simplified
-            objects.
-
-    """
-
-    # Step 0: get collection type for later use and itialize empty list
-    my_type = type(my_collection)
-    pieces = list()
-
-    # Step 1: serialize each part of the collection
-    for part in my_collection:
-        pieces.append(_simplify(part))
-
-    # Step 2: convert back to original type and return serialization
-    if my_type == set:
-        return pieces
-    return my_type(pieces)
-
-
-def _detail_collection_list(worker: AbstractWorker, my_collection: Collection) -> Collection:
-    """
-    This function is designed to operate in the opposite direction of
-    _simplify_collection. It takes a collection of simple python objects
-    and iterates through it to determine whether objects in the collection
-    need to be converted into more advanced types. In particular, it
-    converts binary objects into torch Tensors where appropriate.
-
-    Args:
-        worker: the worker doing the deserialization
-        my_collection (Collection): a collection of simple python objects (including binary).
-
-    Returns:
-        Collection: a collection of the same type as the input where the objects
-            in the collection have been detailed.
-    """
-
-    pieces = list()
-
-    # Step 1: deserialize each part of the collection
-    for part in my_collection:
-        try:
-            pieces.append(detail(worker, part).decode("utf-8"))  # transform bytes back to string
-        except AttributeError:
-            pieces.append(detail(worker, part))
-
-    return pieces
-
-
-def _detail_collection_set(worker: AbstractWorker, my_collection: Collection) -> Collection:
-    """
-    This function is designed to operate in the opposite direction of
-    _simplify_collection. It takes a collection of simple python objects
-    and iterates through it to determine whether objects in the collection
-    need to be converted into more advanced types. In particular, it
-    converts binary objects into torch Tensors where appropriate.
-
-    Args:
-        worker: the worker doing the deserialization
-        my_collection (Collection): a collection of simple python objects (including binary).
-
-    Returns:
-        Collection: a collection of the same type as the input where the objects
-            in the collection have been detailed.
-    """
-
-    pieces = list()
-
-    # Step 1: deserialize each part of the collection
-    for part in my_collection:
-        try:
-            pieces.append(detail(worker, part).decode("utf-8"))  # transform bytes back to string
-        except AttributeError:
-            pieces.append(detail(worker, part))
-    return set(pieces)
-
-
-def _detail_collection_tuple(worker: AbstractWorker, my_tuple: Tuple) -> Tuple:
-    """
-    This function is designed to operate in the opposite direction of
-    _simplify_collection. It takes a tuple of simple python objects
-    and iterates through it to determine whether objects in the collection
-    need to be converted into more advanced types. In particular, it
-    converts binary objects into torch Tensors where appropriate.
-    This is only applicable to tuples. They need special handling because
-    `msgpack` is encoding a tuple as a list.
-
-    Args:
-        worker: the worker doing the deserialization
-        my_tuple (Tuple): a collection of simple python objects (including binary).
-
-    Returns:
-        tuple: a collection of the same type as the input where the objects
-            in the collection have been detailed.
-    """
-
-    pieces = list()
-
-    # Step 1: deserialize each part of the collection
-    for part in my_tuple:
-        pieces.append(detail(worker, part))
-
-    return tuple(pieces)
-
-
-# Dictionaries
-
-
-def _simplify_dictionary(my_dict: Dict) -> Dict:
-    """
-    This function is designed to search a dict for any objects
-    which may need to be simplified (i.e., torch tensors). It iterates
-    through each key, value in the dict and calls _simplify on it. Finally,
-    it returns the output dict as the same type as the input dict
-    so that the consuming serialization step knows the correct type info. The
-    reverse function to this function is _detail_dictionary, which undoes
-    the functionality of this function.
-
-    Args:
-        my_dict (Dict): a dictionary of python objects
-
-    Returns:
-        Dict: a dictionary of the same type as the input of simplified
-            objects.
-
-    """
-    pieces = list()
-    # for dictionaries we want to simplify both the key and the value
-    for key, value in my_dict.items():
-        pieces.append((_simplify(key), _simplify(value)))
-
-    return pieces
-
-
-def _detail_dictionary(worker: AbstractWorker, my_dict: Dict) -> Dict:
-    """
-    This function is designed to operate in the opposite direction of
-    _simplify_dictionary. It takes a dictionary of simple python objects
-    and iterates through it to determine whether objects in the collection
-    need to be converted into more advanced types. In particular, it
-    converts binary objects into torch Tensors where appropriate.
-
-    Args:
-        worker: the worker doing the deserialization
-        my_dict (Dict): a dictionary of simple python objects (including binary).
-
-    Returns:
-        tuple: a collection of the same type as the input where the objects
-            in the collection have been detailed.
-    """
-    pieces = {}
-    # for dictionaries we want to detail both the key and the value
-    for key, value in my_dict:
-        detailed_key = detail(worker, key)
-        try:
-            detailed_key = detailed_key.decode("utf-8")
-        except AttributeError:
-            pass
-
-        detailed_value = detail(worker, value)
-        try:
-            detailed_value = detailed_value.decode("utf-8")
-        except AttributeError:
-            pass
-
-        pieces[detailed_key] = detailed_value
-
-    return pieces
-
-
-#   numpy array
+#   Numpy array
 
 
 def _simplify_ndarray(my_array: numpy.ndarray) -> Tuple[bin, Tuple, str]:
@@ -531,30 +331,6 @@ def _detail_torch_device(worker: AbstractWorker, device_type: str) -> torch.devi
     return torch.device(type=device_type)
 
 
-def _force_full_simplify(worker: AbstractWorker) -> tuple:
-    """
-
-    """
-
-    return (_simplify(worker.id), _simplify(worker._objects), worker.auto_add)
-
-
-def _force_full_detail(worker: AbstractWorker, worker_tuple: tuple) -> tuple:
-    worker_id, _objects, auto_add = worker_tuple
-    worker_id = detail(worker, worker_id)
-
-    result = sy.VirtualWorker(sy.hook, worker_id, auto_add=auto_add)
-    _objects = detail(worker, _objects)
-    result._objects = _objects
-
-    # make sure they weren't accidentally double registered
-    for _, obj in _objects.items():
-        if obj.id in worker._objects:
-            del worker._objects[obj.id]
-
-    return result
-
-
 def _simplify_script_module(obj: torch.jit.ScriptModule) -> str:
     """Strategy to serialize a script module using Torch.jit"""
     return obj.save_to_buffer()
@@ -567,148 +343,15 @@ def _detail_script_module(worker: AbstractWorker, script_module_bin: str) -> tor
     return loaded_module
 
 
-# High Level Simplification Router
-
-
-def _simplify(obj: object) -> object:
-    """
-    This function takes an object as input and returns a simple
-    Python object which is supported by the chosen serialization
-    method (such as JSON or msgpack). The reason we have this function
-    is that some objects are either NOT supported by high level (fast)
-    serializers OR the high level serializers don't support the fastest
-    form of serialization. For example, PyTorch tensors have custom pickle
-    functionality thus its better to pre-serialize PyTorch tensors using
-    pickle and then serialize the binary in with the rest of the message
-    being sent.
-
-    Args:
-        obj: an object which may need to be simplified
-
-    Returns:
-        obj: an simple Python object which msgpack can serialize
-
-    Raises:
-        ValueError: if `move_this` or `in_front_of_that` are not both single ASCII
-        characters.
-
-    """
-
-    try:
-        # check to see if there is a simplifier
-        # for this type. If there is, run return
-        # the simplified object
-        current_type = type(obj)
-        result = (simplifiers[current_type][0], simplifiers[current_type][1](obj))
-        return result
-
-    except KeyError:
-
-        # if there is not a simplifier for this
-        # object, then the object is already a
-        # simple python object and we can just
-        # return it
-        return obj
-
-
-def _force_full_simplify(obj: object) -> object:
-    current_type = type(obj)
-
-    if current_type in forced_full_simplifiers:
-
-        left = forced_full_simplifiers[current_type][0]
-
-        right = forced_full_simplifiers[current_type][1]
-
-        right = right(obj)
-
-        result = (left, right)
-    else:
-        result = _simplify(obj)
-
-    return result
-
-
-simplifiers = {
-    torch.Tensor: [0, _simplify_torch_tensor],
-    torch.nn.Parameter: [1, _simplify_torch_parameter],
-    tuple: [2, _simplify_collection],
-    list: [3, _simplify_collection],
-    set: [4, _simplify_collection],
-    dict: [5, _simplify_dictionary],
-    range: [6, _simplify_range],
-    numpy.ndarray: [7, _simplify_ndarray],
-    slice: [8, _simplify_slice],
-    type(Ellipsis): [9, _simplify_ellipsis],
-    torch.device: [10, _simplify_torch_device],
-    pointers.PointerTensor: [11, sy.PointerTensor.simplify],
-    LoggingTensor: [12, sy.LoggingTensor.simplify],
-    AdditiveSharingTensor: [13, sy.AdditiveSharingTensor.simplify],
-    MultiPointerTensor: [14, sy.MultiPointerTensor.simplify],
-    Plan: [15, sy.Plan.simplify],
-    VirtualWorker: [16, sy.VirtualWorker.simplify],
-    str: [18, _simplify_str],
-    pointers.ObjectWrapper: [19, sy.ObjectWrapper.simplify],
-    GetNotPermittedError: [20, sy.exceptions.GetNotPermittedError.simplify],
-    ResponseSignatureError: [20, sy.exceptions.ResponseSignatureError.simplify],
-    torch.jit.ScriptModule: [21, _simplify_script_module],
-    torch.jit.TopLevelTracedModule: [
-        21,
-        _simplify_script_module,
-    ],  # treat as torch.jit.ScriptModule
-    TrainConfig: [22, sy.TrainConfig.simplify],
-}
-
-forced_full_simplifiers = {VirtualWorker: [17, _force_full_simplify]}
-
-
-def detail(worker: AbstractWorker, obj: object) -> object:
-    """
-    This function reverses the functionality of _simplify. Where applicable,
-    it converts simple objects into more complex objects such as converting
-    binary objects into torch tensors. Read _simplify for more information on
-    why _simplify and _detail are needed.
-
-    Args:
-        worker: the worker which is acquiring the message content, for example
-        used to specify the owner of a tensor received(not obvious for
-        virtual workers)
-        obj: a simple Python object which msgpack deserialized
-
-    Returns:
-        obj: a more complex Python object which msgpack would have had trouble
-            deserializing directly.
-
-    """
-
-    if type(obj) in (list, tuple):
-        return detailers[obj[0]](worker, obj[1])
-    else:
-        return obj
-
-
-detailers = [
-    _detail_torch_tensor,
-    _detail_torch_parameter,
-    _detail_collection_tuple,
-    _detail_collection_list,
-    _detail_collection_set,
-    _detail_dictionary,
-    _detail_range,
-    _detail_ndarray,
-    _detail_slice,
-    _detail_ellipsis,
-    _detail_torch_device,
-    sy.PointerTensor.detail,
-    sy.LoggingTensor.detail,
-    sy.AdditiveSharingTensor.detail,
-    sy.MultiPointerTensor.detail,
-    sy.Plan.detail,
-    sy.VirtualWorker.detail,
-    _force_full_detail,
-    _detail_str,
-    sy.ObjectWrapper.detail,
-    sy.exceptions.GetNotPermittedError.detail,
-    _detail_script_module,
-    sy.TrainConfig.detail,
-]
+# Maps a type to a tuple containing its simplifier and detailer function
+# IMPORTANT: keep this structure sorted A-Z (by type name)
+MAP_TORCH_SIMPLIFIERS_AND_DETAILERS = OrderedDict(
+    {
+        numpy.ndarray: (_simplify_ndarray, _detail_ndarray),
+        torch.device: (_simplify_torch_device, _detail_torch_device),
+        torch.jit.ScriptModule: (_simplify_script_module, _detail_script_module),
+        torch.jit.TopLevelTracedModule: (_simplify_script_module, _detail_script_module),
+        torch.nn.Parameter: (_simplify_torch_parameter, _detail_torch_parameter),
+        torch.Tensor: (_simplify_torch_tensor, _detail_torch_tensor),
+    }
+)


Test patch:
diff --git a/test/test_serde.py b/test/test_serde.py
--- a/test/test_serde.py
+++ b/test/test_serde.py
@@ -3,7 +3,9 @@
 simple python types which are serializable by standard serialization tools.
 For more on how/why this works, see serde.py directly.
 """
+from syft.serde import native_serde
 from syft.serde import serde
+from syft.serde import torch_serde
 
 import syft
 from syft.exceptions import CompressionNotFoundException
@@ -24,7 +26,12 @@ def test_tuple_simplify():
     for tuples so that the detailer knows how to interpret it."""
 
     input = ("hello", "world")
-    target = (2, ((18, (b"hello",)), (18, (b"world",))))
+    tuple_detail_index = serde.detailers.index(native_serde._detail_collection_tuple)
+    str_detail_index = serde.detailers.index(native_serde._detail_str)
+    target = (
+        tuple_detail_index,
+        ((str_detail_index, (b"hello",)), (str_detail_index, (b"world",))),
+    )
     assert serde._simplify(input) == target
 
 
@@ -36,7 +43,9 @@ def test_list_simplify():
     for lists so that the detailer knows how to interpret it."""
 
     input = ["hello", "world"]
-    target = (3, [(18, (b"hello",)), (18, (b"world",))])
+    list_detail_index = serde.detailers.index(native_serde._detail_collection_list)
+    str_detail_index = serde.detailers.index(native_serde._detail_str)
+    target = (list_detail_index, [(str_detail_index, (b"hello",)), (str_detail_index, (b"world",))])
     assert serde._simplify(input) == target
 
 
@@ -48,7 +57,9 @@ def test_set_simplify():
     for sets so that the detailer knows how to interpret it."""
 
     input = set(["hello", "world"])
-    target = (4, [(18, (b"hello",)), (18, (b"world",))])
+    set_detail_index = serde.detailers.index(native_serde._detail_collection_set)
+    str_detail_index = serde.detailers.index(native_serde._detail_str)
+    target = (set_detail_index, [(str_detail_index, (b"hello",)), (str_detail_index, (b"world",))])
     assert serde._simplify(input)[0] == target[0]
     assert set(serde._simplify(input)[1]) == set(target[1])
 
@@ -82,7 +93,7 @@ def test_string_simplify():
     themselves, with no tuple/id necessary."""
 
     input = "hello"
-    target = (18, (b"hello",))
+    target = (serde.detailers.index(native_serde._detail_str), (b"hello",))
     assert serde._simplify(input) == target
 
 
@@ -90,11 +101,16 @@ def test_dict_simplify():
     """This tests our ability to simplify dict objects.
 
     This test is pretty simple since dicts just serialize to
-    themselves, with a tuple wrapper with the correct ID (4)
+    themselves, with a tuple wrapper with the correct ID
     for dicts so that the detailer knows how to interpret it."""
 
     input = {"hello": "world"}
-    target = (5, [((18, (b"hello",)), (18, (b"world",)))])
+    detail_dict_index = serde.detailers.index(native_serde._detail_dictionary)
+    detail_str_index = serde.detailers.index(native_serde._detail_str)
+    target = (
+        detail_dict_index,
+        [((detail_str_index, (b"hello",)), (detail_str_index, (b"world",)))],
+    )
     assert serde._simplify(input) == target
 
 
@@ -106,7 +122,7 @@ def test_range_simplify():
     for dicts so that the detailer knows how to interpret it."""
 
     input = range(1, 3, 4)
-    target = (6, (1, 3, 4))
+    target = (serde.detailers.index(native_serde._detail_range), (1, 3, 4))
     assert serde._simplify(input) == target
 
 
@@ -130,7 +146,7 @@ def test_torch_tensor_simplify():
 
     # make sure the object type ID is correct
     # (0 for torch.Tensor)
-    assert output[0] == 0
+    assert serde.detailers[output[0]] == torch_serde._detail_torch_tensor
 
     # make sure inner type is correct
     assert type(output[1]) == tuple
@@ -154,7 +170,7 @@ def test_ndarray_simplify():
     output = serde._simplify(input)
 
     # make sure simplified type ID is correct
-    assert output[0] == 7
+    assert serde.detailers[output[0]] == torch_serde._detail_ndarray
 
     # make sure serialized form is correct
     assert type(output[1][0]) == bytes
@@ -164,9 +180,7 @@ def test_ndarray_simplify():
 
 def test_ellipsis_simplify():
     """Make sure ellipsis simplifies correctly."""
-
-    # the id indicating an ellipsis is here
-    assert serde._simplify(Ellipsis)[0] == 9
+    assert serde.detailers[serde._simplify(Ellipsis)[0]] == native_serde._detail_ellipsis
 
     # the simplified ellipsis (empty object)
     assert serde._simplify(Ellipsis)[1] == b""
@@ -176,8 +190,7 @@ def test_torch_device_simplify():
     """Test the simplification of torch.device"""
     device = torch.device("cpu")
 
-    # the id indicating an torch.device is here
-    assert serde._simplify(device)[0] == 10
+    assert serde.detailers[serde._simplify(device)[0]] == torch_serde._detail_torch_device
 
     # the simplified torch.device
     assert serde._simplify(device)[1] == "cpu"
@@ -311,7 +324,9 @@ def test_compressed_serde(compress_scheme):
     else:
         serde._apply_compress_scheme = serde.apply_no_compression
 
-    arr = numpy.random.random((100, 100))
+    # using numpy.ones because numpy.random.random is not compressed.
+    arr = numpy.ones((100, 100))
+
     arr_serialized = serde.serialize(arr)
 
     arr_serialized_deserialized = serde.deserialize(arr_serialized)
@@ -374,7 +389,6 @@ def test_range_serde(compress):
     _range = range(1, 2, 3)
 
     range_serialized = serde.serialize(_range)
-
     range_serialized_deserialized = serde.deserialize(range_serialized)
 
     assert _range == range_serialized_deserialized
@@ -400,10 +414,16 @@ def test_list(compress):
     assert _list == list_serialized_deserialized
 
     # Test with a complex data structure
-    tensor_one = Tensor(numpy.random.random((100, 100)))
-    tensor_two = Tensor(numpy.random.random((100, 100)))
+    tensor_one = Tensor(numpy.ones((100, 100)))
+    tensor_two = Tensor(numpy.ones((100, 100)) * 2)
     _list = (tensor_one, tensor_two)
+
     list_serialized = serde.serialize(_list)
+    if compress:
+        assert list_serialized[0] == serde.LZ4
+    else:
+        assert list_serialized[0] == serde.NO_COMPRESSION
+
     list_serialized_deserialized = serde.deserialize(list_serialized)
     # `assert list_serialized_deserialized == _list` does not work, therefore it's split
     # into 3 assertions
@@ -422,6 +442,7 @@ def test_set(compress):
     # Test with integers
     _set = set([1, 2])
     set_serialized = serde.serialize(_set)
+
     set_serialized_deserialized = serde.deserialize(set_serialized)
     assert _set == set_serialized_deserialized
 
@@ -432,10 +453,16 @@ def test_set(compress):
     assert _set == set_serialized_deserialized
 
     # Test with a complex data structure
-    tensor_one = Tensor(numpy.random.random((100, 100)))
-    tensor_two = Tensor(numpy.random.random((100, 100)))
+    tensor_one = Tensor(numpy.ones((100, 100)))
+    tensor_two = Tensor(numpy.ones((100, 100)) * 2)
     _set = (tensor_one, tensor_two)
+
     set_serialized = serde.serialize(_set)
+    if compress:
+        assert set_serialized[0] == serde.LZ4
+    else:
+        assert set_serialized[0] == serde.NO_COMPRESSION
+
     set_serialized_deserialized = serde.deserialize(set_serialized)
     # `assert set_serialized_deserialized == _set` does not work, therefore it's split
     # into 3 assertions
@@ -488,20 +515,6 @@ def test_float(compress):
     assert y_serialized_deserialized == y
 
 
-def test_compressed_float():
-    x = 0.5
-    y = 1.5
-
-    x_serialized = serde.serialize(x)
-    x_serialized_deserialized = serde.deserialize(x_serialized)
-
-    y_serialized = serde.serialize(y)
-    y_serialized_deserialized = serde.deserialize(y_serialized)
-
-    assert x_serialized_deserialized == x
-    assert y_serialized_deserialized == y
-
-
 @pytest.mark.parametrize(
     "compress, compress_scheme",
     [
@@ -524,8 +537,11 @@ def test_hooked_tensor(compress, compress_scheme):
     else:
         serde._apply_compress_scheme = serde.apply_no_compression
 
-    t = Tensor(numpy.random.random((100, 100)))
+    t = Tensor(numpy.ones((100, 100)))
     t_serialized = serde.serialize(t)
+    assert (
+        t_serialized[0] == compress_scheme if compress else t_serialized[0] == serde.NO_COMPRESSION
+    )
     t_serialized_deserialized = serde.deserialize(t_serialized)
     assert (t == t_serialized_deserialized).all()
 
@@ -555,12 +571,15 @@ def test_pointer_tensor_detail(id):
 
 
 def test_numpy_tensor_serde():
+    serde._apply_compress_scheme = serde.apply_lz4_compression
+
     serde._serialize_tensor = syft.serde.numpy_tensor_serializer
     serde._deserialize_tensor = syft.serde.numpy_tensor_deserializer
 
-    tensor = torch.tensor(numpy.random.random((10, 10)), requires_grad=False)
+    tensor = torch.tensor(numpy.ones((10, 10)), requires_grad=False)
 
     tensor_serialized = serde.serialize(tensor)
+    assert tensor_serialized[0] != serde.NO_COMPRESSION
     tensor_deserialized = serde.deserialize(tensor_serialized)
 
     # Back to Pytorch serializer