diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinder.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinder.py
index 892bf9a7181e97990e2c0be6ca14f6102db6168f..7e1c386951f3849aef57d6483831f8c0c3f5eff9 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinder.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinder.py
@@ -14,7 +14,7 @@ import numpy as np
 
 from mantid import AnalysisDataService
 from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress)
-from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator, Logger)
+from mantid.kernel import (Direction, StringListValidator, Logger)
 from mantid.simpleapi import CloneWorkspace, GroupWorkspaces
 from sans.algorithm_detail.beamcentrefinder_plotting import can_plot_beamcentrefinder, plot_workspace_quartiles
 from sans.algorithm_detail.crop_helper import get_component_name
@@ -25,7 +25,7 @@ from sans.common.enums import (DetectorType, MaskingQuadrant, FindDirectionEnum)
 from sans.common.file_information import get_instrument_paths_for_sans_file
 from sans.common.general_functions import create_child_algorithm
 from sans.common.xml_parsing import get_named_elements_from_ipf_file
-from sans.state.state_base import create_deserialized_sans_state_from_property_manager
+from sans.state.Serializer import Serializer
 
 
 class SANSBeamCentreFinder(DataProcessorAlgorithm):
@@ -40,8 +40,8 @@ class SANSBeamCentreFinder(DataProcessorAlgorithm):
         # INPUT
         # ----------
         # Workspace which is to be cropped
-        self.declareProperty(PropertyManagerProperty('SANSState'),
-                             doc='A property manager which fulfills the SANSState contract.')
+        self.declareProperty('SANSState', '',
+                             doc='A JSON string which fulfills the SANSState contract.')
 
         self.declareProperty(MatrixWorkspaceProperty("SampleScatterWorkspace", '',
                                                      optional=PropertyMode.Mandatory, direction=Direction.Input),
@@ -112,7 +112,6 @@ class SANSBeamCentreFinder(DataProcessorAlgorithm):
 
     def PyExec(self):
         state = self._get_state()
-        state_serialized = state.property_manager
         self.logger = Logger("CentreFinder")
         self.logger.notice("Starting centre finder routine...")
         progress = self._get_progress()
@@ -174,11 +173,11 @@ class SANSBeamCentreFinder(DataProcessorAlgorithm):
             progress.report("Reducing ... Pos1 " + str(centre1) + " Pos2 " + str(centre2))
             sample_quartiles = self._run_quartile_reduction(sample_scatter, sample_transmission, sample_direct,
                                                             "Sample", sample_scatter_monitor, component,
-                                                            state_serialized, centre1, centre2, r_min, r_max)
+                                                            centre1, centre2, r_min, r_max)
 
             if can_scatter:
                 can_quartiles = self._run_quartile_reduction(can_scatter, can_transmission, can_direct, "Can",
-                                                             can_scatter_monitor, component, state_serialized, centre1,
+                                                             can_scatter_monitor, component, centre1,
                                                              centre2, r_min, r_max)
                 for key in sample_quartiles:
                     sample_quartiles[key] = perform_can_subtraction(sample_quartiles[key], can_quartiles[key], self)
@@ -284,14 +283,17 @@ class SANSBeamCentreFinder(DataProcessorAlgorithm):
         return ''
 
     def _run_quartile_reduction(self, scatter_workspace, transmission_workspace, direct_workspace, data_type,
-                                scatter_monitor_workspace, component, state, centre1, centre2, r_min, r_max):
+                                scatter_monitor_workspace, component, centre1, centre2, r_min, r_max):
+
+        serialized_state = self.getProperty("SANSState").value
+
         algorithm_name = "SANSBeamCentreFinderCore"
         alg_options = {"ScatterWorkspace": scatter_workspace,
                        "ScatterMonitorWorkspace": scatter_monitor_workspace,
                        "TransmissionWorkspace": transmission_workspace,
                        "DirectWorkspace": direct_workspace,
                        "Component": component,
-                       "SANSState": state,
+                       "SANSState": serialized_state,
                        "DataType": data_type,
                        "Centre1": centre1,
                        "Centre2": centre2,
@@ -316,9 +318,9 @@ class SANSBeamCentreFinder(DataProcessorAlgorithm):
         return get_component_name(workspace, component)
 
     def _get_state(self):
-        state_property_manager = self.getProperty("SANSState").value
-        state = create_deserialized_sans_state_from_property_manager(state_property_manager)
-        state.property_manager = state_property_manager
+        state_json = self.getProperty("SANSState").value
+        state = Serializer.from_json(state_json)
+
         return state
 
     def _calculate_residuals(self, quartile1, quartile2):
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinderCore.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinderCore.py
index bf1050837d382b416549377c3d4edcd5b6b5ec2e..b3356723c4d895e96152f23c1c70b8680f946bc3 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinderCore.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinderCore.py
@@ -12,7 +12,7 @@ from __future__ import (absolute_import, division, print_function)
 
 from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress,
                         IEventWorkspace)
-from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator)
+from mantid.kernel import (Direction, StringListValidator)
 from sans.algorithm_detail.CreateSANSAdjustmentWorkspaces import CreateSANSAdjustmentWorkspaces
 from sans.algorithm_detail.convert_to_q import convert_workspace
 from sans.algorithm_detail.crop_helper import get_component_name
@@ -24,7 +24,7 @@ from sans.algorithm_detail.xml_shapes import quadrant_xml
 from sans.common.constants import EMPTY_NAME
 from sans.common.enums import (DetectorType, DataType, MaskingQuadrant)
 from sans.common.general_functions import create_child_algorithm, append_to_sans_file_tag
-from sans.state.state_base import create_deserialized_sans_state_from_property_manager
+from sans.state.Serializer import Serializer
 
 
 class SANSBeamCentreFinderCore(DataProcessorAlgorithm):
@@ -38,8 +38,8 @@ class SANSBeamCentreFinderCore(DataProcessorAlgorithm):
         # ----------
         # INPUT
         # ----------
-        self.declareProperty(PropertyManagerProperty('SANSState'),
-                             doc='A property manager which fulfills the SANSState contract.')
+        self.declareProperty('SANSState', '',
+                             doc='A JSON string which fulfills the SANSState contract.')
 
         # WORKSPACES
         # Scatter Workspaces
@@ -410,9 +410,9 @@ class SANSBeamCentreFinderCore(DataProcessorAlgorithm):
         return errors
 
     def _get_state(self):
-        state_property_manager = self.getProperty("SANSState").value
-        state = create_deserialized_sans_state_from_property_manager(state_property_manager)
-        state.property_manager = state_property_manager
+        state_json = self.getProperty("SANSState").value
+        state = Serializer.from_json(state_json)
+
         return state
 
     def _get_transmission_workspace(self):
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinderMassMethod.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinderMassMethod.py
index d07bdb3797e7e44afe149917849a0e9bf5c5d8da..b04430c6ccb1eff47cf8b82f11725f8b7f37e9e4 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinderMassMethod.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinderMassMethod.py
@@ -12,7 +12,7 @@ from __future__ import (absolute_import, division, print_function)
 
 from mantid.api import (DataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode, Progress,
                         IEventWorkspace)
-from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator)
+from mantid.kernel import (Direction, StringListValidator)
 from sans.algorithm_detail.crop_helper import get_component_name
 from sans.algorithm_detail.mask_sans_workspace import mask_workspace
 from sans.algorithm_detail.move_sans_instrument_component import move_component, MoveTypes
@@ -21,7 +21,7 @@ from sans.algorithm_detail.slice_sans_event import slice_sans_event
 from sans.common.constants import EMPTY_NAME
 from sans.common.enums import (DetectorType)
 from sans.common.general_functions import create_child_algorithm, append_to_sans_file_tag
-from sans.state.state_base import create_deserialized_sans_state_from_property_manager
+from sans.state.Serializer import Serializer
 
 
 class SANSBeamCentreFinderMassMethod(DataProcessorAlgorithm):
@@ -36,8 +36,8 @@ class SANSBeamCentreFinderMassMethod(DataProcessorAlgorithm):
         # INPUT
         # ----------
         # Workspace which is to be cropped
-        self.declareProperty(PropertyManagerProperty('SANSState'),
-                             doc='A property manager which fulfills the SANSState contract.')
+        self.declareProperty('SANSState', '',
+                             doc='A JSON string which fulfills the SANSState contract.')
 
         self.declareProperty(MatrixWorkspaceProperty("SampleScatterWorkspace", '',
                                                      optional=PropertyMode.Mandatory, direction=Direction.Input),
@@ -266,9 +266,9 @@ class SANSBeamCentreFinderMassMethod(DataProcessorAlgorithm):
         return workspace
 
     def _get_state(self):
-        state_property_manager = self.getProperty("SANSState").value
-        state = create_deserialized_sans_state_from_property_manager(state_property_manager)
-        state.property_manager = state_property_manager
+        state_json = self.getProperty("SANSState").value
+        state = Serializer.from_json(state_json)
+
         return state
 
     def _get_monitor_workspace(self):
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateAdjustmentWorkspaces.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateAdjustmentWorkspaces.py
index c9351a18a22367e54891295af5480a271b363278..69d4cbe9af5bd8241713e92ca619e5f8ebb19692 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateAdjustmentWorkspaces.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSCreateAdjustmentWorkspaces.py
@@ -14,13 +14,13 @@ from __future__ import (absolute_import, division, print_function)
 
 from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
                         WorkspaceUnitValidator)
-from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator, CompositeValidator)
+from mantid.kernel import (Direction, StringListValidator, CompositeValidator)
 from sans.algorithm_detail.calculate_sans_transmission import calculate_transmission
 from sans.algorithm_detail.normalize_to_sans_monitor import normalize_to_monitor
 from sans.common.constants import EMPTY_NAME
 from sans.common.enums import (DataType, DetectorType)
 from sans.common.general_functions import create_unmanaged_algorithm
-from sans.state.state_base import create_deserialized_sans_state_from_property_manager
+from sans.state.Serializer import Serializer
 
 
 class SANSCreateAdjustmentWorkspaces(DistributedDataProcessorAlgorithm):
@@ -36,8 +36,8 @@ class SANSCreateAdjustmentWorkspaces(DistributedDataProcessorAlgorithm):
         # INPUT
         # ---------------
         # State
-        self.declareProperty(PropertyManagerProperty('SANSState'),
-                             doc='A property manager which fulfills the SANSState contract.')
+        self.declareProperty('SANSState', '',
+                             doc='A JSON string which fulfills the SANSState contract.')
 
         # Input workspaces
         self.declareProperty(MatrixWorkspaceProperty('TransmissionWorkspace', '',
@@ -103,8 +103,8 @@ class SANSCreateAdjustmentWorkspaces(DistributedDataProcessorAlgorithm):
 
     def PyExec(self):
         # Read the state
-        state_property_manager = self.getProperty("SANSState").value
-        state = create_deserialized_sans_state_from_property_manager(state_property_manager)
+        state_json = self.getProperty("SANSState").value
+        state = Serializer.from_json(state_json)
 
         # --------------------------------------
         # Get the monitor normalization workspace
@@ -127,8 +127,7 @@ class SANSCreateAdjustmentWorkspaces(DistributedDataProcessorAlgorithm):
         # Get the full wavelength and pixel adjustment
         # --------------------------------------------
         wave_length_adjustment_workspace, \
-        pixel_length_adjustment_workspace = self._get_wavelength_and_pixel_adjustment_workspaces(state,
-                                                                                                 monitor_normalization_workspace,
+        pixel_length_adjustment_workspace = self._get_wavelength_and_pixel_adjustment_workspaces(monitor_normalization_workspace,
                                                                                                  # noqa
                                                                                                  calculated_transmission_workspace)  # noqa
 
@@ -142,13 +141,13 @@ class SANSCreateAdjustmentWorkspaces(DistributedDataProcessorAlgorithm):
         self.setProperty("CalculatedTransmissionWorkspace", calculated_transmission_workspace)
         self.setProperty("UnfittedTransmissionWorkspace", unfitted_transmission_workspace)
 
-    def _get_wavelength_and_pixel_adjustment_workspaces(self, state,
+    def _get_wavelength_and_pixel_adjustment_workspaces(self,
                                                         monitor_normalization_workspace,
                                                         calculated_transmission_workspace):
         component = self.getProperty("Component").value
 
         wave_pixel_adjustment_name = "SANSCreateWavelengthAndPixelAdjustment"
-        serialized_state = state.property_manager
+        serialized_state = self.getProperty("SANSState").value
         wave_pixel_adjustment_options = {"SANSState": serialized_state,
                                          "NormalizeToMonitorWorkspace": monitor_normalization_workspace,
                                          "OutputWorkspaceWavelengthAdjustment": EMPTY_NAME,
@@ -222,8 +221,7 @@ class SANSCreateAdjustmentWorkspaces(DistributedDataProcessorAlgorithm):
         # Check that the input can be converted into the right state object
         state_property_manager = self.getProperty("SANSState").value
         try:
-            state = create_deserialized_sans_state_from_property_manager(state_property_manager)
-            state.property_manager = state_property_manager
+            state = Serializer.from_json(state_property_manager)
             state.validate()
         except ValueError as err:
             errors.update({"SANSCreateAdjustmentWorkspaces": str(err)})
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSLoad.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSLoad.py
index bdbe213a5b89bafd55d84c54b4f0b7e8306c7937..252521b89afdd6f8c443b028c4db5dfba63b9c85 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSLoad.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSLoad.py
@@ -11,13 +11,13 @@
 from __future__ import (absolute_import, division, print_function)
 
 from mantid.api import (ParallelDataProcessorAlgorithm, MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
-                        Progress,
-                        WorkspaceProperty)
-from mantid.kernel import (Direction, PropertyManagerProperty, FloatArrayProperty)
+                        Progress, WorkspaceProperty)
+from mantid.kernel import (Direction, FloatArrayProperty)
 from sans.algorithm_detail.load_data import SANSLoadDataFactory
 from sans.algorithm_detail.move_sans_instrument_component import move_component, MoveTypes
 from sans.common.enums import SANSDataType
-from sans.state.state_base import create_deserialized_sans_state_from_property_manager
+
+from sans.state.Serializer import Serializer
 
 
 class SANSLoad(ParallelDataProcessorAlgorithm):
@@ -31,8 +31,8 @@ class SANSLoad(ParallelDataProcessorAlgorithm):
         # ----------
         # INPUT
         # ----------
-        self.declareProperty(PropertyManagerProperty('SANSState'),
-                             doc='A property manager which fulfills the SANSState contract.')
+        self.declareProperty('SANSState', "",
+                             doc='A JSON String which fulfills the SANSState contract.')
 
         self.declareProperty("PublishToCache", True, direction=Direction.Input,
                              doc="Publish the calibration workspace to a cache, in order to avoid reloading "
@@ -117,7 +117,7 @@ class SANSLoad(ParallelDataProcessorAlgorithm):
     def PyExec(self):
         # Read the state
         state_property_manager = self.getProperty("SANSState").value
-        state = create_deserialized_sans_state_from_property_manager(state_property_manager)
+        state = Serializer.from_json(state_property_manager)
 
         # Run the appropriate SANSLoader and get the workspaces and the workspace monitors
         # Note that cache optimization is only applied to the calibration workspace since it is not available as a
@@ -153,13 +153,13 @@ class SANSLoad(ParallelDataProcessorAlgorithm):
     def validateInputs(self):
         errors = dict()
         # Check that the input can be converted into the right state object
-        state_property_manager = self.getProperty("SANSState").value
+        state_json = self.getProperty("SANSState").value
         try:
-            state = create_deserialized_sans_state_from_property_manager(state_property_manager)
-            state.property_manager = state_property_manager
+            state = Serializer.from_json(state_json)
             state.validate()
         except ValueError as err:
             errors.update({"SANSState": str(err)})
+            return errors
 
         # We need to validate that the for each expected output workspace of the SANSState a output workspace name
         # was supplied in the PyInit
@@ -178,7 +178,6 @@ class SANSLoad(ParallelDataProcessorAlgorithm):
         # ------------------------------------
         # Check the optional output workspaces
         # If they are specified in the SANSState, then we require them to be set on the output as well.
-        state = create_deserialized_sans_state_from_property_manager(state_property_manager)
         data_info = state.data
 
         # For sample transmission
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCoreBase.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCoreBase.py
index 8c7f43c7f3eb926238b1ba8d97bf310e6c7fbd5f..c46946979ea8ec694716f0538bd273659fd3d74e 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCoreBase.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCoreBase.py
@@ -11,7 +11,7 @@
 from __future__ import (absolute_import, division, print_function)
 
 from mantid.api import (DistributedDataProcessorAlgorithm, MatrixWorkspaceProperty, PropertyMode, IEventWorkspace)
-from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator)
+from mantid.kernel import (Direction, StringListValidator)
 from sans.algorithm_detail.CreateSANSAdjustmentWorkspaces import CreateSANSAdjustmentWorkspaces
 from sans.algorithm_detail.convert_to_q import convert_workspace
 from sans.algorithm_detail.crop_helper import get_component_name
@@ -22,7 +22,7 @@ from sans.algorithm_detail.slice_sans_event import slice_sans_event
 from sans.common.constants import EMPTY_NAME
 from sans.common.enums import (DetectorType, DataType)
 from sans.common.general_functions import (create_child_algorithm, append_to_sans_file_tag)
-from sans.state.state_base import create_deserialized_sans_state_from_property_manager
+from sans.state.Serializer import Serializer
 
 
 class SANSReductionCoreBase(DistributedDataProcessorAlgorithm):
@@ -30,8 +30,8 @@ class SANSReductionCoreBase(DistributedDataProcessorAlgorithm):
         # ----------
         # INPUT
         # ----------
-        self.declareProperty(PropertyManagerProperty('SANSState'),
-                             doc='A property manager which fulfills the SANSState contract.')
+        self.declareProperty('SANSState', '',
+                             doc='A JSON String which fulfills the SANSState contract.')
 
         # WORKSPACES
         # Scatter Workspaces
@@ -236,9 +236,8 @@ class SANSReductionCoreBase(DistributedDataProcessorAlgorithm):
         return output_workspace, sum_of_counts, sum_of_norms
 
     def _get_state(self):
-        state_property_manager = self.getProperty("SANSState").value
-        state = create_deserialized_sans_state_from_property_manager(state_property_manager)
-        state.property_manager = state_property_manager
+        json_state = self.getProperty("SANSState").value
+        state = Serializer.from_json(json_state)
         return state
 
     def _get_transmission_workspace(self):
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCoreEventSlice.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCoreEventSlice.py
index 77f101549b7fab0d3cf183f697b9c58b206dc925..eea62a214ced21f9d8557a8cba816310e756f836 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCoreEventSlice.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSReductionCoreEventSlice.py
@@ -15,7 +15,7 @@ from SANSReductionCoreBase import SANSReductionCoreBase
 
 from mantid.api import (MatrixWorkspaceProperty, AlgorithmFactory, PropertyMode,
                         Progress)
-from mantid.kernel import (Direction, PropertyManagerProperty, StringListValidator)
+from mantid.kernel import (Direction, StringListValidator)
 from sans.common.enums import (DetectorType, DataType)
 
 
@@ -31,8 +31,8 @@ class SANSReductionCoreEventSlice(SANSReductionCoreBase):
         # ----------
         # INPUT
         # ----------
-        self.declareProperty(PropertyManagerProperty('SANSState'),
-                             doc='A property manager which fulfills the SANSState contract.')
+        self.declareProperty('SANSState', '',
+                             doc='A JSON string which fulfills the SANSState contract.')
 
         # WORKSPACES
         # Scatter Workspaces
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction.py
index f1c94d6c6dc9cae4f463cf4b907920f46ba0acee..a17d8ab41a8f273faafcbc6db43e7544d257a4d6 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction.py
@@ -283,7 +283,7 @@ class SANSSingleReduction(SANSSingleReductionBase):
 
     def set_transmission_workspaces_on_output(self, transmission_bundles, fit_state):
         for transmission_bundle in transmission_bundles:
-            fit_performed = fit_state[transmission_bundle.data_type].fit_type != FitType.NO_FIT
+            fit_performed = fit_state[transmission_bundle.data_type.value].fit_type != FitType.NO_FIT
             calculated_transmission_workspace = transmission_bundle.calculated_transmission_workspace
             unfitted_transmission_workspace = transmission_bundle.unfitted_transmission_workspace
             if transmission_bundle.data_type is DataType.CAN:
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction2.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction2.py
index aa6b8222e74596aaa755259514bee08c555d09ef..0ca3fb774dbe530a0ba6ecefc1c0453c93f78adc 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction2.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReduction2.py
@@ -433,7 +433,7 @@ class SANSSingleReduction(SANSSingleReductionBase):
 
     def set_transmission_workspaces_on_output(self, transmission_bundles, fit_state):
         for transmission_bundle in transmission_bundles:
-            fit_performed = fit_state[transmission_bundle.data_type].fit_type != FitType.NO_FIT
+            fit_performed = fit_state[transmission_bundle.data_type.value].fit_type != FitType.NO_FIT
             calculated_transmission_workspace = transmission_bundle.calculated_transmission_workspace
             unfitted_transmission_workspace = transmission_bundle.unfitted_transmission_workspace
             if transmission_bundle.data_type is DataType.CAN:
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReductionBase.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReductionBase.py
index e2bb4ba5c5a8a7289a8faad319264cc3c1bbd1ad..f5bc11cc8a42e2a9719e10ebdc71c0b24a805f13 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReductionBase.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSSingleReductionBase.py
@@ -14,14 +14,14 @@ from collections import defaultdict
 
 from mantid.api import (DistributedDataProcessorAlgorithm,
                         MatrixWorkspaceProperty, Progress, PropertyMode)
-from mantid.kernel import (Direction, PropertyManagerProperty)
+from mantid.kernel import Direction
 from sans.algorithm_detail.bundles import ReductionSettingBundle
 from sans.algorithm_detail.single_execution import (get_final_output_workspaces,
                                                     get_merge_bundle_for_merge_request)
 from sans.algorithm_detail.strip_end_nans_and_infs import strip_end_nans
 from sans.common.enums import (DataType, ReductionMode)
 from sans.common.general_functions import create_child_algorithm
-from sans.state.state_base import create_deserialized_sans_state_from_property_manager
+from sans.state.Serializer import Serializer
 
 
 class SANSSingleReductionBase(DistributedDataProcessorAlgorithm):
@@ -29,8 +29,8 @@ class SANSSingleReductionBase(DistributedDataProcessorAlgorithm):
         # ----------
         # INPUT
         # ----------
-        self.declareProperty(PropertyManagerProperty('SANSState'),
-                             doc='A property manager which fulfills the SANSState contract.')
+        self.declareProperty('SANSState', '',
+                             doc='A JSON string which fulfills the SANSState contract.')
 
         self.declareProperty("UseOptimizations", True, direction=Direction.Input,
                              doc="When enabled the ADS is being searched for already loaded and reduced workspaces. "
@@ -233,9 +233,9 @@ class SANSSingleReductionBase(DistributedDataProcessorAlgorithm):
         return errors
 
     def _get_state(self):
-        state_property_manager = self.getProperty("SANSState").value
-        state = create_deserialized_sans_state_from_property_manager(state_property_manager)
-        state.property_manager = state_property_manager
+        state_json = self.getProperty("SANSState").value
+        state = Serializer.from_json(state_json)
+
         return state
 
     @staticmethod
diff --git a/Testing/SystemTests/tests/analysis/SANSBeamCentreFinderCoreTest.py b/Testing/SystemTests/tests/analysis/SANSBeamCentreFinderCoreTest.py
index 99dfdb2e7a537cf766ad88bf0bb92a4c37e48196..dac357ff81a1b3dcb4769b342fc901ecbd03c903 100644
--- a/Testing/SystemTests/tests/analysis/SANSBeamCentreFinderCoreTest.py
+++ b/Testing/SystemTests/tests/analysis/SANSBeamCentreFinderCoreTest.py
@@ -13,6 +13,7 @@ import systemtesting
 
 import mantid
 from mantid.api import AlgorithmManager
+from sans.state.Serializer import Serializer
 
 from sans.state.data import get_data_builder
 from sans.common.enums import (DetectorType, DataType, SANSFacility)
@@ -31,7 +32,7 @@ class SANSBeamCentreFinderCoreTest(unittest.TestCase):
         load_alg.setChild(True)
         load_alg.initialize()
 
-        state_dict = state.property_manager
+        state_dict = Serializer.to_json(state)
         load_alg.setProperty("SANSState", state_dict)
         load_alg.setProperty("PublishToCache", False)
         load_alg.setProperty("UseCached", False)
@@ -64,7 +65,7 @@ class SANSBeamCentreFinderCoreTest(unittest.TestCase):
         beam_centre_core_alg.setChild(True)
         beam_centre_core_alg.initialize()
 
-        state_dict = state.property_manager
+        state_dict = Serializer.to_json(state)
         beam_centre_core_alg.setProperty("SANSState", state_dict)
         beam_centre_core_alg.setProperty("ScatterWorkspace", workspace)
         beam_centre_core_alg.setProperty("ScatterMonitorWorkspace", monitor)
diff --git a/Testing/SystemTests/tests/analysis/SANSLoadTest.py b/Testing/SystemTests/tests/analysis/SANSLoadTest.py
index 12ced9eeaeaa1b23417a6bc3cb638627bc2e42ed..4ed7a0ccef866806f47f7ae7338f6276fc603f13 100644
--- a/Testing/SystemTests/tests/analysis/SANSLoadTest.py
+++ b/Testing/SystemTests/tests/analysis/SANSLoadTest.py
@@ -20,6 +20,7 @@ from sans.common.constants import (CALIBRATION_WORKSPACE_TAG, SANS_FILE_TAG)
 # Not clear why the names in the module are not found by Pylint, but it seems to get confused. Hence this check
 # needs to be disabled here.
 # pylint: disable=no-name-in-module
+from sans.state.Serializer import Serializer
 from sans.test_helper.test_director import TestDirector
 from sans.common.enums import SANSFacility
 from sans.state.data import get_data_builder
@@ -200,7 +201,7 @@ class SANSLoadTest(unittest.TestCase):
         load_alg.setRethrows(True)
         load_alg.initialize()
 
-        state_dict = state.property_manager
+        state_dict = Serializer.to_json(state)
         load_alg.setProperty("SANSState", state_dict)
         load_alg.setProperty("PublishToCache", publish_to_cache)
         load_alg.setProperty("UseCached", use_cached)
diff --git a/Testing/SystemTests/tests/analysis/SANSReductionCoreTest.py b/Testing/SystemTests/tests/analysis/SANSReductionCoreTest.py
index e6c8fe65ad38b2468e43875a259f346ea6ffc848..bc10d87cfe452a438115935aa977db7bd4492f78 100644
--- a/Testing/SystemTests/tests/analysis/SANSReductionCoreTest.py
+++ b/Testing/SystemTests/tests/analysis/SANSReductionCoreTest.py
@@ -13,6 +13,7 @@ import systemtesting
 
 import mantid
 from mantid.api import AlgorithmManager
+from sans.state.Serializer import Serializer
 
 from sans.state.data import get_data_builder
 from sans.common.enums import (DetectorType, DataType, SANSFacility)
@@ -31,7 +32,7 @@ class SANSReductionCoreTest(unittest.TestCase):
         load_alg.setChild(True)
         load_alg.initialize()
 
-        state_dict = state.property_manager
+        state_dict = Serializer.to_json(state)
         load_alg.setProperty("SANSState", state_dict)
         load_alg.setProperty("PublishToCache", False)
         load_alg.setProperty("UseCached", False)
@@ -63,7 +64,7 @@ class SANSReductionCoreTest(unittest.TestCase):
         reduction_core_alg.setChild(True)
         reduction_core_alg.initialize()
 
-        state_dict = state.property_manager
+        state_dict = Serializer.to_json(state)
         reduction_core_alg.setProperty("SANSState", state_dict)
         reduction_core_alg.setProperty("ScatterWorkspace", workspace)
         reduction_core_alg.setProperty("ScatterMonitorWorkspace", monitor)
diff --git a/Testing/SystemTests/tests/analysis/SANSSingleReductionTest.py b/Testing/SystemTests/tests/analysis/SANSSingleReductionTest.py
index bac2bbb2603bd4f56aaaab5e27ee6fa44f7900eb..eaf4273e917f2301c7d3352855aa247623c07b91 100644
--- a/Testing/SystemTests/tests/analysis/SANSSingleReductionTest.py
+++ b/Testing/SystemTests/tests/analysis/SANSSingleReductionTest.py
@@ -13,6 +13,7 @@ import unittest
 
 import mantid  # noqa
 from mantid.api import AlgorithmManager
+from sans.state.Serializer import Serializer
 from sans.user_file.state_director import StateDirectorISIS
 from sans.state.data import get_data_builder
 from sans.common.enums import (SANSFacility, ReductionMode, ReductionDimensionality, FitModeForMerge)
@@ -30,7 +31,7 @@ class SingleReductionTest(unittest.TestCase):
         load_alg.setChild(True)
         load_alg.initialize()
 
-        state_dict = state.property_manager
+        state_dict = Serializer.to_json(state)
         load_alg.setProperty("SANSState", state_dict)
         load_alg.setProperty("PublishToCache", False)
         load_alg.setProperty("UseCached", False)
@@ -101,7 +102,7 @@ class SingleReductionTest(unittest.TestCase):
                               output_settings=None, event_slice_optimisation=False, save_can=False, use_optimizations=False):
         single_reduction_name = "SANSSingleReduction"
         ver = 1 if not event_slice_optimisation else 2
-        state_dict = state.property_manager
+        state_dict = Serializer.to_json(state)
 
         single_reduction_options = {"SANSState": state_dict,
                                     "SampleScatterWorkspace": sample_scatter,
diff --git a/dev-docs/source/ISISSANSReductionBackend.rst b/dev-docs/source/ISISSANSReductionBackend.rst
index c8c6ea13ce5a307f9a94daffc3a7d20b56c5a14f..4df10a40583bced8f7269ebe8d4fb4aed1a616d0 100644
--- a/dev-docs/source/ISISSANSReductionBackend.rst
+++ b/dev-docs/source/ISISSANSReductionBackend.rst
@@ -152,37 +152,18 @@ the state construction.
 *state_base.py*
 ^^^^^^^^^^^^^^^
 
-The *state_base.py* module contains the essential ingredients for defining a
-state object. These are the *StateBase* class which allows for serialization
-and a set of *TypedParameter*.
-
-The *StateBase*'s *property_manager* property is responsible for serialization.
-Due to the nature of the *PropertyManagerProperty* of algorithms it serializes
-the state object to a Python dictionary and receives a Mantid *PropertyManager*
-object. This asymmetry is unfortunate, but mirrors the asymmetry of the
-algorithm inputs.
-
-States which want to fulfill the *StateBase* contract must override the
-*validate* method. This method is used to ensure internal consistency
-of the *TypedParameters* on the state. It is important to have comprehensive
-and tight checks here.
-
-The entries on the state objects are all descriptors of type *TypedParameter* which allows
-for type checking, ensuring consistency early on. It is easy to
-build custom types. The current list of types are:
-
-- *StringParameter*
-- *BoolParameter*
-- *FloatParameter*
-- *PositiveFloatParameter*
-- *PositiveIntegerParameter*
-- *DictParameter*
-- *FloatWithNoneParameter*
-- *StringWithNoneParameter*
-- *PositiveFloatWithNoneParameter*
-- *FloatListParameter*
-- *StringListParameter*
-- *PositiveIntegerListParameter*
+The *JsonSerializable* metaclass contains the essential ingredients for
+serializing a state object. Additionally it provides a decorator for any
+Enum types which need to be JSON serializable.
+
+Any classes which use the metaclass must place any attributes they intend
+to be serialized into JSON string in the instance. I.e. class level variables
+are not recommended since they may not end up in the instances internal
+dictionary.
+
+The *Serializer* is responsible for serialization using the JSON library and
+provides static methods to (de)serialize to a string or file.
+
 
 Individual states
 ^^^^^^^^^^^^^^^^^
diff --git a/docs/source/release/v4.3.0/sans.rst b/docs/source/release/v4.3.0/sans.rst
index 683c3414fcbd6b6d681e24ec0df211380d7d02f6..649868e6eafcaa97c5fd9043462bcadaee924d53 100644
--- a/docs/source/release/v4.3.0/sans.rst
+++ b/docs/source/release/v4.3.0/sans.rst
@@ -15,5 +15,8 @@ Improved
 - :ref:`MaskBTP <algm-MaskBTP>` now handles both old and new instrument definitions for BIOSANS and GPSANS
 - Data with invalid proton charge logs will now be fixed before performing
   slicing. A warning is emitted when this happens.
+- ISIS SANS history for top level algorithms now works correctly. A user
+  can copy the history of a workspace to their clipboard or a file and the data
+  will be reproduced on that machine without requiring editing of the script.
 
 :ref:`Release 4.3.0 <v4.3.0>`
diff --git a/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui b/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui
index 716857a7123ccf2c9272f1e5e8e4ef3664b777e9..4d13462bde0bfd113bd88c9686649bc6e9701692 100644
--- a/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui
+++ b/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui
@@ -648,7 +648,7 @@ QGroupBox::title {
           <item>
            <widget class="QTabWidget" name="settings_tab_widget">
             <property name="currentIndex">
-             <number>2</number>
+             <number>0</number>
             </property>
             <widget class="QWidget" name="general_tab">
              <attribute name="title">
@@ -856,7 +856,7 @@ QGroupBox::title {
                           <bool>true</bool>
                          </property>
                          <property name="toolTip">
-                         <string>
+                          <string>
                          &lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;In the case of data which was measured in event-mode, it is possible to perform time-of-flight slices of the data and reduce these separately.&lt;/p&gt;&lt;p&gt;Input can be:&lt;/p&gt;&lt;p&gt;-&lt;span style=&quot; font-style:italic;&quot;&gt; start:step:stop&lt;/span&gt; specifies time slices from the &lt;span style=&quot; font-style:italic;&quot;&gt;start&lt;/span&gt; value to the &lt;span style=&quot; font-style:italic;&quot;&gt;stop &lt;/span&gt;value in steps of &lt;span style=&quot; font-style:italic;&quot;&gt;step&lt;/span&gt;&lt;/p&gt;&lt;p&gt;- &lt;span style=&quot; font-style:italic;&quot;&gt;start-stop &lt;/span&gt;which specifies a time slice from the &lt;span style=&quot; font-style:italic;&quot;&gt;start&lt;/span&gt; value to the &lt;span style=&quot; font-style:italic;&quot;&gt;stop&lt;/span&gt; value&lt;/p&gt;&lt;p&gt;- &lt;span style=&quot; font-style:italic;&quot;&gt;&amp;gt;start&lt;/span&gt; specifies a slice from the &lt;span style=&quot; font-style:italic;&quot;&gt;start &lt;/span&gt;value to the end of the data set&lt;/p&gt;&lt;p&gt;- &lt;span style=&quot; font-style:italic;&quot;&gt;&amp;lt;stop&lt;/span&gt; specifes a slice from the start of the data set to the &lt;span style=&quot; font-style:italic;&quot;&gt;stop &lt;/span&gt;value&lt;/p&gt;&lt;p&gt;In addition it is possible to concatenate these specifications using comma-separation. An example is:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;5-10,12:2:16,20-30&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;
                          </string>
                          </property>
diff --git a/scripts/Interface/ui/sans_isis/settings_diagnostic_tab.py b/scripts/Interface/ui/sans_isis/settings_diagnostic_tab.py
index 0a88c566e8c6ba9126618c054c0d63b0d357b771..b2cf5cca0b01a39fc1b42994b5ec6245432f9f7b 100644
--- a/scripts/Interface/ui/sans_isis/settings_diagnostic_tab.py
+++ b/scripts/Interface/ui/sans_isis/settings_diagnostic_tab.py
@@ -12,7 +12,6 @@ and helps the developer to identify issues.
 """
 
 from __future__ import (absolute_import, division, print_function)
-
 from abc import ABCMeta, abstractmethod
 import os
 from qtpy import QtWidgets
@@ -23,7 +22,6 @@ from mantidqt.utils.qt import load_ui
 from mantid import UsageService
 from mantid.kernel import FeatureType
 from sans.gui_logic.gui_common import (GENERIC_SETTINGS, JSON_SUFFIX, load_file)
-from sans.state.state_base import ENUM_TYPE_TAG
 
 if PY3:
     unicode = str
@@ -150,19 +148,9 @@ class SettingsDiagnosticTab(QtWidgets.QWidget, Ui_SettingsDiagnosticTab):
                 item.addChild(child)
         else:
             child = QtWidgets.QTreeWidgetItem()
-            value = self.clean_class_type(value)
             child.setText(1, unicode(value))
             item.addChild(child)
 
-    def clean_class_type(self, value):
-        # TODO the UI should not be doing logic like this
-        if isinstance(value, str) and ENUM_TYPE_TAG in value:
-            # Only the last element is of interest
-            split_values = value.split("#")
-            return split_values[-1]
-        else:
-            return value
-
     def set_row(self, index):
         found_index = self.select_row_combo_box.findText(str(index))
         if found_index and found_index != -1:
diff --git a/scripts/SANS/sans/algorithm_detail/batch_execution.py b/scripts/SANS/sans/algorithm_detail/batch_execution.py
index 12fd96e3dfc1cb9b7897ae9bafc41b4033fb2376..a7f3932de0bff005450855bb9981e3f540c06b65 100644
--- a/scripts/SANS/sans/algorithm_detail/batch_execution.py
+++ b/scripts/SANS/sans/algorithm_detail/batch_execution.py
@@ -22,6 +22,7 @@ from sans.common.constants import (TRANS_SUFFIX, SANS_SUFFIX, ALL_PERIODS,
                                    CAN_AND_SAMPLE_WORKSPACE)
 from sans.common.file_information import (get_extension_for_file_type, SANSFileInformationFactory)
 from sans.gui_logic.plotting import get_plotting_module
+from sans.state.Serializer import Serializer
 from sans.state.data import StateData
 
 
@@ -443,7 +444,7 @@ def provide_loaded_data(state, use_optimizations, workspace_to_name, workspace_t
     :return: a list fo workspaces and a list of monitor workspaces
     """
     # Load the data
-    state_serialized = state.property_manager
+    state_serialized = Serializer.to_json(state)
     load_name = "SANSLoad"
     load_options = {"SANSState": state_serialized,
                     "PublishToCache": use_optimizations,
@@ -847,7 +848,7 @@ def set_properties_for_reduction_algorithm(reduction_alg, reduction_package, wor
     # Go through the elements of the reduction package and set them on the reduction algorithm
     # Set the SANSState
     state = reduction_package.state
-    state_dict = state.property_manager
+    state_dict = Serializer.to_json(state)
     reduction_alg.setProperty("SANSState", state_dict)
 
     # Set the input workspaces
diff --git a/scripts/SANS/sans/algorithm_detail/calculate_sans_transmission.py b/scripts/SANS/sans/algorithm_detail/calculate_sans_transmission.py
index b602f55919957c070c157e3032749325f0c1ba60..c4d8b79f23c5036069f05c769d724c71ac45165a 100644
--- a/scripts/SANS/sans/algorithm_detail/calculate_sans_transmission.py
+++ b/scripts/SANS/sans/algorithm_detail/calculate_sans_transmission.py
@@ -114,7 +114,7 @@ def _perform_fit(transmission_workspace, direct_workspace,
         raise RuntimeError("No transmission monitor has been provided.")
 
     # Get the fit setting for the correct data type, ie either for the Sample of the Can
-    fit_type = calculate_transmission_state.fit[data_type].fit_type
+    fit_type = calculate_transmission_state.fit[data_type.value].fit_type
     if fit_type is FitType.LOGARITHMIC:
         fit_string = "Log"
     elif fit_type is FitType.POLYNOMIAL:
@@ -124,7 +124,7 @@ def _perform_fit(transmission_workspace, direct_workspace,
 
     trans_options.update({"FitMethod": fit_string})
     if fit_type is FitType.POLYNOMIAL:
-        polynomial_order = calculate_transmission_state.fit[data_type].polynomial_order
+        polynomial_order = calculate_transmission_state.fit[data_type.value].polynomial_order
         trans_options.update({"PolynomialOrder": polynomial_order})
 
     trans_alg = create_unmanaged_algorithm(trans_name, **trans_options)
@@ -256,7 +256,7 @@ def _get_corrected_wavelength_workspace(workspace, detector_ids, calculate_trans
         wavelength_low = calculate_transmission_state.wavelength_full_range_low
         wavelength_high = calculate_transmission_state.wavelength_full_range_high
     else:
-        fit_state = calculate_transmission_state.fit[data_type]
+        fit_state = calculate_transmission_state.fit[data_type.value]
         wavelength_low = fit_state.wavelength_low if fit_state.wavelength_low \
             else calculate_transmission_state.wavelength_low[0]
         wavelength_high = fit_state.wavelength_high if fit_state.wavelength_high \
diff --git a/scripts/SANS/sans/algorithm_detail/centre_finder_new.py b/scripts/SANS/sans/algorithm_detail/centre_finder_new.py
index 0dc9d962beb0ffba6bf32aa8649428335418fc01..e4e1bc19daba04baa6c40ccd002c6c14022952f3 100644
--- a/scripts/SANS/sans/algorithm_detail/centre_finder_new.py
+++ b/scripts/SANS/sans/algorithm_detail/centre_finder_new.py
@@ -15,6 +15,9 @@ from mantid.simpleapi import CreateEmptyTableWorkspace
 # ----------------------------------------------------------------------------------------------------------------------
 # Functions for the execution of a single batch iteration
 # ----------------------------------------------------------------------------------------------------------------------
+from sans.state.Serializer import Serializer
+
+
 def centre_finder_new(state, r_min = 0.06, r_max = 0.26, iterations = 10, position_1_start = 0.0, position_2_start = 0.0
                       , tolerance = 0.0001251, find_direction = FindDirectionEnum.ALL, verbose=False, component=DetectorType.LAB):
     """
@@ -153,8 +156,8 @@ def set_properties_for_beam_centre_algorithm(beam_centre_alg, reduction_package,
     # Go through the elements of the reduction package and set them on the beam centre algorithm
     # Set the SANSState
     state = reduction_package.state
-    state_dict = state.property_manager
-    beam_centre_alg.setProperty("SANSState", state_dict)
+    state_json = Serializer.to_json(state)
+    beam_centre_alg.setProperty("SANSState", state_json)
 
     # Set the input workspaces
     workspaces = reduction_package.workspaces
diff --git a/scripts/SANS/sans/algorithm_detail/single_execution.py b/scripts/SANS/sans/algorithm_detail/single_execution.py
index 6b389b67b75e5fc055db85f18da3511a29b34e8f..3e48361b465cb5b85b91e6d9ff9cc83de3c42d38 100644
--- a/scripts/SANS/sans/algorithm_detail/single_execution.py
+++ b/scripts/SANS/sans/algorithm_detail/single_execution.py
@@ -18,6 +18,7 @@ from sans.common.enums import (DetectorType, ReductionMode, OutputParts, Transmi
 from sans.common.general_functions import (create_child_algorithm, get_reduced_can_workspace_from_ads,
                                            get_transmission_workspaces_from_ads,
                                            write_hash_into_reduced_can_workspace)
+from sans.state.Serializer import Serializer
 
 
 def run_initial_event_slice_reduction(reduction_alg, reduction_setting_bundle):
@@ -32,7 +33,7 @@ def run_initial_event_slice_reduction(reduction_alg, reduction_setting_bundle):
     # Get component to reduce
     component = get_component_to_reduce(reduction_setting_bundle)
     # Set the properties on the reduction algorithms
-    serialized_state = reduction_setting_bundle.state.property_manager
+    serialized_state = Serializer.to_json(reduction_setting_bundle.state)
     reduction_alg.setProperty("SANSState", serialized_state)
     reduction_alg.setProperty("Component", component)
     reduction_alg.setProperty("ScatterWorkspace", reduction_setting_bundle.scatter_workspace)
@@ -74,7 +75,7 @@ def run_core_event_slice_reduction(reduction_alg, reduction_setting_bundle):
     # Get component to reduce
     component = get_component_to_reduce(reduction_setting_bundle)
     # Set the properties on the reduction algorithms
-    serialized_state = reduction_setting_bundle.state.property_manager
+    serialized_state = Serializer.to_json(reduction_setting_bundle.state)
     reduction_alg.setProperty("SANSState", serialized_state)
     reduction_alg.setProperty("Component", component)
     reduction_alg.setProperty("ScatterWorkspace", reduction_setting_bundle.scatter_workspace)
@@ -133,7 +134,7 @@ def run_core_reduction(reduction_alg, reduction_setting_bundle):
     # Get component to reduce
     component = get_component_to_reduce(reduction_setting_bundle)
     # Set the properties on the reduction algorithms
-    serialized_state = reduction_setting_bundle.state.property_manager
+    serialized_state = Serializer.to_json(reduction_setting_bundle.state)
     reduction_alg.setProperty("SANSState", serialized_state)
     reduction_alg.setProperty("Component", component)
     reduction_alg.setProperty("ScatterWorkspace", reduction_setting_bundle.scatter_workspace)
diff --git a/scripts/SANS/sans/common/enums.py b/scripts/SANS/sans/common/enums.py
index 7e690c32e482487415617e715c5749e89d253c0c..0e7a5bde4f7b9ea4de5953ce71e45154c8b118a9 100644
--- a/scripts/SANS/sans/common/enums.py
+++ b/scripts/SANS/sans/common/enums.py
@@ -8,8 +8,10 @@
 
 from __future__ import (absolute_import, division, print_function)
 from mantid.py3compat import Enum
+from sans.state.JsonSerializable import json_serializable
 
 
+@json_serializable
 class SANSInstrument(Enum):
     NO_INSTRUMENT = "No Instrument"
 
@@ -19,6 +21,7 @@ class SANSInstrument(Enum):
     ZOOM = "ZOOM"
 
 
+@json_serializable
 class SANSFacility(Enum):
     NO_FACILITY = "No Facility"
     ISIS = "ISIS"
@@ -38,12 +41,14 @@ class SANSDataType(Enum):
     SAMPLE_TRANSMISSION = "Sample Transmission"
 
 
+@json_serializable
 class CanonicalCoordinates(Enum):
     X = "X"
     Y = "Y"
     Z = "Z"
 
 
+@json_serializable
 class ReductionMode(Enum):
     NOT_SET = "Not Set"
     ALL = "All"
@@ -52,6 +57,7 @@ class ReductionMode(Enum):
     LAB = "LAB"
 
 
+@json_serializable
 class ReductionDimensionality(Enum):
     ONE_DIM = "OneDim"
     TWO_DIM = "TwoDim"
@@ -83,6 +89,7 @@ class OutputParts(Enum):
     NORM = "Norm"
 
 
+@json_serializable
 class FitModeForMerge(Enum):
     """
     Defines which fit operation to use during the merge of two reductions.
@@ -109,6 +116,7 @@ class TransmissionType(Enum):
     UNFITTED = "Unfitted"
 
 
+@json_serializable
 class RangeStepType(Enum):
     """
     Defines the step type of a range
@@ -120,11 +128,13 @@ class RangeStepType(Enum):
     RANGE_LOG = "RangeLog"
 
 
+@json_serializable
 class RebinType(Enum):
     INTERPOLATING_REBIN = "InterpolatingRebin"
     REBIN = "Rebin"
 
 
+@json_serializable
 class SaveType(Enum):
     CAN_SAS = "CanSAS"
     CSV = "CSV"
@@ -135,6 +145,7 @@ class SaveType(Enum):
     RKH = "RKH"
 
 
+@json_serializable
 class FitType(Enum):
     """
     Defines possible fit types for the transmission calculation
@@ -145,6 +156,7 @@ class FitType(Enum):
     NO_FIT = "NotFit"
 
 
+@json_serializable
 class SampleShape(Enum):
     """
     Defines the sample shape types
diff --git a/scripts/SANS/sans/common/file_information.py b/scripts/SANS/sans/common/file_information.py
index 3a2bf92c1b89b564cda35387112acde509083bc7..6ca8eafe54add6467845137393dbcf7f3b270d3a 100644
--- a/scripts/SANS/sans/common/file_information.py
+++ b/scripts/SANS/sans/common/file_information.py
@@ -108,9 +108,7 @@ def find_sans_file(file_name):
             # TODO: If we only provide a run number for example 98843 for LOQ measurments, but have LARMOR specified as the
             #       Mantid instrument, then the FileFinder will search itself to death. This is a general Mantid issue.
             #       One way to handle this graceful would be a timeout option.
-            file_name_as_bytes = str.encode(file_name)
-            assert (type(file_name_as_bytes) == bytes)
-            runs = FileFinder.findRuns(file_name_as_bytes)
+            runs = FileFinder.findRuns(file_name)
             if runs:
                 full_path = runs[0]
     except RuntimeError:
diff --git a/scripts/SANS/sans/common/general_functions.py b/scripts/SANS/sans/common/general_functions.py
index d79cf3335aadc083abb21b0300851a9fd36fe26f..e18f1687cf3b32e437581f5fe3c3151fc93c818c 100644
--- a/scripts/SANS/sans/common/general_functions.py
+++ b/scripts/SANS/sans/common/general_functions.py
@@ -26,6 +26,8 @@ from sans.common.enums import (DetectorType, RangeStepType, ReductionDimensional
 # -------------------------------------------
 # Constants
 # -------------------------------------------
+from sans.state.Serializer import Serializer
+
 ALTERNATIVE_SANS2D_NAME = "SAN"
 
 
@@ -768,7 +770,7 @@ def get_transmission_output_name(state, data_type=DataType.SAMPLE, multi_reducti
     short_run_number_as_string = str(short_run_number)
 
     calculated_transmission_state = state.adjustment.calculate_transmission
-    fit = calculated_transmission_state.fit[DataType.SAMPLE]
+    fit = calculated_transmission_state.fit[DataType.SAMPLE.value]
     wavelength_range_string = "_" + str(fit.wavelength_low) + "_" + str(fit.wavelength_high)
 
     trans_suffix = "_trans_Sample" if data_type == DataType.SAMPLE else "_trans_Can"
@@ -932,7 +934,7 @@ def get_state_hash_for_can_reduction(state, reduction_mode, partial_type=None):
         return state_to_hash
 
     new_state = remove_sample_related_information(state)
-    new_state_serialized = new_state.property_manager
+    new_state_serialized = Serializer.to_json(new_state)
     new_state_serialized = json.dumps(new_state_serialized, sort_keys=True, indent=4)
 
     # Add a tag for the reduction mode
diff --git a/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py b/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py
index d189039f697c311cd03593931a59c23bfc90e0a2..69f1620ed6d08319adfc1f88e01d5e94abc8f05e 100644
--- a/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py
+++ b/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py
@@ -15,6 +15,7 @@ from mantid.kernel import Logger
 from mantid.api import (AnalysisDataService)
 from sans.algorithm_detail.mask_sans_workspace import mask_workspace
 from sans.algorithm_detail.move_sans_instrument_component import move_component, MoveTypes
+from sans.state.Serializer import Serializer
 from ui.sans_isis.masking_table import MaskingTable
 from sans.common.enums import DetectorType
 from sans.common.constants import EMPTY_NAME
@@ -43,7 +44,7 @@ def load_workspace(state, workspace_name):
     prepare_to_load_scatter_sample_only(state)
     handle_multi_period_data(state)
 
-    serialized_state = state.property_manager
+    serialized_state = Serializer.to_json(state)
 
     workspace = perform_load(serialized_state)
     perform_move(state, workspace)
diff --git a/scripts/SANS/sans/gui_logic/presenter/property_manager_service.py b/scripts/SANS/sans/gui_logic/presenter/property_manager_service.py
deleted file mode 100644
index 17e1033c11c7248be37f7743646f5cb36a7c6bb8..0000000000000000000000000000000000000000
--- a/scripts/SANS/sans/gui_logic/presenter/property_manager_service.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# Mantid Repository : https://github.com/mantidproject/mantid
-#
-# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-#     NScD Oak Ridge National Laboratory, European Spallation Source
-#     & Institut Laue - Langevin
-# SPDX - License - Identifier: GPL - 3.0 +
-""" The property manager service.
-
-The property manager service serializes a SANS state object into a PropertyManager object and places it on the
- PropertyManagerDataService. It is also used to retrieve the state from this service.
-"""
-
-from __future__ import (absolute_import, division, print_function)
-
-from mantid.kernel import (PropertyManagerDataService)
-
-from sans.state.state_base import create_deserialized_sans_state_from_property_manager
-
-
-class PropertyManagerService(object):
-    sans_property_manager_prefix = "SANS_PROPERTY_MANAGER_THIS_NEEDS_TO_BE_UNIQUE_"
-
-    def add_states_to_pmds(self, states):
-        # 1. Remove all property managers which belong to the sans property manager type
-        self.remove_sans_property_managers()
-
-        # 2. Add all property managers
-        self._add_property_managers_to_pmds(states)
-
-    def get_single_state_from_pmds(self, index_to_retrieve):
-        # 1. Find all sans state names
-        sans_property_managers = {}
-        for name in PropertyManagerDataService.getObjectNames():
-            if name.startswith(self.sans_property_manager_prefix):
-                property_manager = PropertyManagerDataService.retrieve(name)
-                index = self._get_index_from_name(name)
-                sans_property_managers.update({index: property_manager})
-
-        # 2. Convert property managers to states
-        if index_to_retrieve not in list(sans_property_managers.keys()):
-            return []
-        sans_property_manager = sans_property_managers[index_to_retrieve]
-        states_map = self._convert_property_manager_to_state({index_to_retrieve: sans_property_manager})
-
-        # 3. Create a sequence container
-        return self._get_states_list(states_map)
-
-    def get_states_from_pmds(self):
-        # 1. Find all sans state names
-        sans_property_managers = {}
-        for name in PropertyManagerDataService.getObjectNames():
-            if name.startswith(self.sans_property_manager_prefix):
-                property_manager = PropertyManagerDataService.retrieve(name)
-                index = self._get_index_from_name(name)
-                sans_property_managers.update({index: property_manager})
-
-        # 2. Convert property managers to states
-        states_map = self._convert_property_manager_to_state(sans_property_managers)
-
-        # 3. Create a sequence container
-        return self._get_states_list(states_map)
-
-    def remove_sans_property_managers(self):
-        property_manager_names_to_delete = []
-        for name in PropertyManagerDataService.getObjectNames():
-            if name.startswith(self.sans_property_manager_prefix):
-                property_manager_names_to_delete.append(name)
-
-        for element in property_manager_names_to_delete:
-            PropertyManagerDataService.remove(element)
-
-    def _add_property_managers_to_pmds(self, states):
-        for index, state in states.items():
-            name = self.sans_property_manager_prefix + str(index)
-            PropertyManagerDataService.addOrReplace(name, state.property_manager)
-
-    def _get_index_from_name(self, name):
-        return int(name.replace(self.sans_property_manager_prefix, ""))
-
-    @staticmethod
-    def _convert_property_manager_to_state(property_managers):
-        states = {}
-        for key, property_manager in property_managers.items():
-            state = create_deserialized_sans_state_from_property_manager(property_manager)
-            states.update({key: state})
-        return states
-
-    @staticmethod
-    def _get_states_list(states_map):
-        states = []
-        indices = list(states_map.keys())
-        indices.sort()
-        for index in indices:
-            states.append(states_map[index])
-        return states
diff --git a/scripts/SANS/sans/gui_logic/presenter/settings_diagnostic_presenter.py b/scripts/SANS/sans/gui_logic/presenter/settings_diagnostic_presenter.py
index c9e108245152aba09f16ca25b6e6f401a553d65e..f1b55bf2bdacdaafe32af1680b89f3de6a660605 100644
--- a/scripts/SANS/sans/gui_logic/presenter/settings_diagnostic_presenter.py
+++ b/scripts/SANS/sans/gui_logic/presenter/settings_diagnostic_presenter.py
@@ -12,6 +12,7 @@ import os
 import json
 
 from mantid.kernel import Logger
+from sans.state.Serializer import Serializer
 from ui.sans_isis.settings_diagnostic_tab import SettingsDiagnosticTab
 from sans.gui_logic.gui_common import JSON_SUFFIX
 
@@ -101,9 +102,13 @@ class SettingsDiagnosticPresenter(object):
 
     def display_state_diagnostic_tree(self, state):
         # Convert to dict before passing the state to the view
-        if state is not None:
-            state = state.property_manager
-        self._view.set_tree(state)
+        dict_vals = None
+
+        if state:
+            state = Serializer.to_json(state)
+            dict_vals = json.loads(state)  # We intentionally do not use serializer to get a dict type back
+
+        self._view.set_tree(dict_vals)
 
     def on_save_state(self):
         # Get the save location
@@ -120,9 +125,7 @@ class SettingsDiagnosticPresenter(object):
 
         row_index = self._view.get_current_row()
         state = self.get_state(row_index)
-        serialized_state = state.property_manager
-        with open(full_file_path, 'w') as f:
-            json.dump(serialized_state, f, sort_keys=True, indent=4)
+        Serializer.save_file(state, full_file_path)
         self.gui_logger.information("The state for row {} has been saved to: {} ".format(row_index, full_file_path))
 
         # Update the file name in the UI
diff --git a/scripts/SANS/sans/gui_logic/sans_data_processor_gui_algorithm.py b/scripts/SANS/sans/gui_logic/sans_data_processor_gui_algorithm.py
deleted file mode 100644
index ca2bd2ccfb63aa2e9456c7b3713d59e54774b260..0000000000000000000000000000000000000000
--- a/scripts/SANS/sans/gui_logic/sans_data_processor_gui_algorithm.py
+++ /dev/null
@@ -1,391 +0,0 @@
-# Mantid Repository : https://github.com/mantidproject/mantid
-#
-# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-#     NScD Oak Ridge National Laboratory, European Spallation Source
-#     & Institut Laue - Langevin
-# SPDX - License - Identifier: GPL - 3.0 +
-from mantid.kernel import (Direction, Property)
-from mantid.api import (DataProcessorAlgorithm, AlgorithmFactory, MatrixWorkspaceProperty, PropertyMode)
-from sans.common.enums import (SANSFacility, OutputMode)
-from collections import namedtuple
-from sans.gui_logic.presenter.property_manager_service import PropertyManagerService
-from sans.sans_batch import SANSBatchReduction
-
-# ----------------------------------------------------------------------------------------------------------------------
-# Globals
-# ----------------------------------------------------------------------------------------------------------------------
-SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME = '__sans_dummy_gui_workspace'
-SANS_DUMMY_OUTPUT_ALGORITHM_PROPERTY_NAME = '__sans_dummy_gui_workspace'
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-# Set up the white list and black list properties of the data algorithm
-# ----------------------------------------------------------------------------------------------------------------------
-algorithm_list_entry = namedtuple('algorithm_list_entry', 'column_name, algorithm_property, description, '
-                                                          'show_value, default, prefix, property_type')
-
-
-def create_option_column_properties():
-    """
-    Adds a new property which is meant for the Options column.
-
-    This column should correspond to features in our settings section. We need to parse the entries before the
-    runs are processed in order to account for the settings in the Options column in the state creation.
-
-    Important note: If you add it here then you have to add it to the parsing logic, else nothing will happen with it.
-                    The important bit to edit is in gui_state_director. There the set properties are parsed.
-    """
-    props = [algorithm_list_entry(column_name="",
-                                  algorithm_property="WavelengthMin",
-                                  description='The min value of the wavelength when converting from TOF.',
-                                  show_value=True,
-                                  default='',
-                                  prefix='',
-                                  property_type=float),
-             algorithm_list_entry(column_name="",
-                                  algorithm_property="WavelengthMax",
-                                  description='The max value of the wavelength when converting from TOF.',
-                                  show_value=True,
-                                  default='',
-                                  prefix='',
-                                  property_type=float),
-             algorithm_list_entry(column_name="",
-                                  algorithm_property="EventSlices",
-                                  description='The event slices to reduce. The format is the same as for the event slices'
-                                              ' box in settings, however if a comma separated list is given '
-                                              'it must be enclosed in quotes',
-                                  show_value=True,
-                                  default='',
-                                  prefix='',
-                                  property_type=str)
-             ]
-    return props
-
-
-def create_properties(show_periods=True):
-    if show_periods:
-        properties = [algorithm_list_entry(column_name="SampleScatter",
-                                           algorithm_property="SampleScatter",
-                                           description='The run number of the scatter sample',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="ssp",
-                                           algorithm_property="SampleScatterPeriod",
-                                           description='The sample scatter period',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="SampleTrans",
-                                           algorithm_property="SampleTransmission",
-                                           description='The run number of the transmission sample',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="stp",
-                                           algorithm_property="SampleTransmissionPeriod",
-                                           description='The sample transmission period',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="SampleDirect",
-                                           algorithm_property="SampleDirect",
-                                           description='The run number of the direct sample',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="sdp",
-                                           algorithm_property="SampleDirectPeriod",
-                                           description='The sample direct period',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="CanScatter",
-                                           algorithm_property="CanScatter",
-                                           description='The run number of the scatter can',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="csp",
-                                           algorithm_property="CanScatterPeriod",
-                                           description='The can scatter period',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="CanTrans",
-                                           algorithm_property="CanTransmission",
-                                           description='The run number of the transmission can',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="ctp",
-                                           algorithm_property="CanTransmissionPeriod",
-                                           description='The can transmission period',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="CanDirect",
-                                           algorithm_property="CanDirect",
-                                           description='The run number of the direct can',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="cdp",
-                                           algorithm_property="CanDirectPeriod",
-                                           description='The can direct period',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="UseOptimizations",
-                                           description='If optimizations should be used.',
-                                           show_value=False,
-                                           default=False,
-                                           prefix='',
-                                           property_type=bool),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="PlotResults",
-                                           description='If results should be plotted.',
-                                           show_value=False,
-                                           default=False,
-                                           prefix='',
-                                           property_type=bool),
-                      algorithm_list_entry(column_name="OutputName",
-                                           algorithm_property="OutputName",
-                                           description='An optional custom output workspace name.',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="User File",
-                                           algorithm_property="UserFile",
-                                           description=('The user file to use, this will override GUI changes for this row.'
-                                                        ' If left unspecified default will be used'),
-                                           show_value=False,
-                                           default="",
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="Sample Thickness",
-                                           algorithm_property="SampleThickness",
-                                           description=('The sample thickness from the user file'),
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="RowIndex",
-                                           description='The row index (which is automatically populated by the GUI)',
-                                           show_value=False,
-                                           default=Property.EMPTY_INT,
-                                           prefix='',
-                                           property_type=int),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="OutputMode",
-                                           description='The output mode.',
-                                           show_value=False,
-                                           default=OutputMode.PUBLISH_TO_ADS.value,
-                                           prefix='',
-                                           property_type=bool),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="OutputGraph",
-                                           description='The name of the graph to output to.',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str)
-                      ]
-    else:
-        properties = [algorithm_list_entry(column_name="SampleScatter",
-                                           algorithm_property="SampleScatter",
-                                           description='The run number of the scatter sample',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="SampleTrans",
-                                           algorithm_property="SampleTransmission",
-                                           description='The run number of the transmission sample',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="SampleDirect",
-                                           algorithm_property="SampleDirect",
-                                           description='The run number of the direct sample',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="CanScatter",
-                                           algorithm_property="CanScatter",
-                                           description='The run number of the scatter can',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="CanTrans",
-                                           algorithm_property="CanTransmission",
-                                           description='The run number of the transmission can',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="CanDirect",
-                                           algorithm_property="CanDirect",
-                                           description='The run number of the direct can',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="UseOptimizations",
-                                           description='If optimizations should be used.',
-                                           show_value=False,
-                                           default=False,
-                                           prefix='',
-                                           property_type=bool),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="PlotResults",
-                                           description='If results should be plotted.',
-                                           show_value=False,
-                                           default=False,
-                                           prefix='',
-                                           property_type=bool),
-                      algorithm_list_entry(column_name="OutputName",
-                                           algorithm_property="OutputName",
-                                           description='An optional custom output workspace name.',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="User File",
-                                           algorithm_property="UserFile",
-                                           description=('The user file to use, this will override GUI changes for this row.'
-                                                        ' If left unspecified default will be used'),
-                                           show_value=False,
-                                           default="",
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="Sample Thickness",
-                                           algorithm_property="SampleThickness",
-                                           description=('The sample thickness from the user file'),
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="RowIndex",
-                                           description='The row index (which is automatically populated by the GUI)',
-                                           show_value=False,
-                                           default=Property.EMPTY_INT,
-                                           prefix='',
-                                           property_type=int),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="OutputMode",
-                                           description='The output mode.',
-                                           show_value=False,
-                                           default=OutputMode.PUBLISH_TO_ADS.value,
-                                           prefix='',
-                                           property_type=bool),
-                      algorithm_list_entry(column_name="",
-                                           algorithm_property="OutputGraph",
-                                           description='The name of the graph to output to.',
-                                           show_value=False,
-                                           default='',
-                                           prefix='',
-                                           property_type=str)
-                      ]
-    return properties
-
-
-def get_white_list(show_periods=True):
-    return create_properties(show_periods=show_periods)
-
-
-def get_black_list(show_periods=True):
-    black_list = "InputWorkspace,OutputWorkspace,"
-    properties = create_properties(show_periods=show_periods)
-    for prop in properties:
-        if not prop.show_value:
-            black_list += prop.algorithm_property
-            black_list += ","
-    return black_list
-
-
-def get_gui_algorithm_name(facility):
-    if facility is SANSFacility.ISIS:
-        algorithm_name = "SANSGuiDataProcessorAlgorithm"
-        AlgorithmFactory.subscribe(SANSGuiDataProcessorAlgorithm)
-    else:
-        raise RuntimeError("The facility is currently not supported")
-    return algorithm_name
-
-
-class SANSGuiDataProcessorAlgorithm(DataProcessorAlgorithm):
-    def category(self):
-        return 'SANS\\Gui'
-
-    def summary(self):
-        return 'Dynamic SANS Gui algorithm.'
-
-    def PyInit(self):
-        # ------------------------------------------------------------
-        # Dummy workspace properties.
-        # ------------------------------------------------------------
-        self.declareProperty(MatrixWorkspaceProperty("InputWorkspace", SANS_DUMMY_INPUT_ALGORITHM_PROPERTY_NAME,
-                                                     optional=PropertyMode.Optional, direction=Direction.Input),
-                             doc='The input workspace (which is not used)')
-
-        self.declareProperty(MatrixWorkspaceProperty("OutputWorkspace", SANS_DUMMY_OUTPUT_ALGORITHM_PROPERTY_NAME,
-                                                     optional=PropertyMode.Optional, direction=Direction.Output),
-                             doc='The output workspace (which is not used)')
-
-        # ------------------------------------------------------------
-        # Create the properties
-        # ------------------------------------------------------------
-        properties = create_properties()
-        for prop in properties:
-            self.declareProperty(prop.algorithm_property, defaultValue=prop.default,
-                                 direction=Direction.Input, doc=prop.description)
-
-        # ------------------------------------------------------------
-        # Add properties which will show up in the options column
-        # ------------------------------------------------------------
-        properties = create_option_column_properties()
-        for prop in properties:
-            self.declareProperty(prop.algorithm_property, defaultValue=prop.default,
-                                 direction=Direction.Input, doc=prop.description)
-
-    def PyExec(self):
-        # 1. Get the index of the batch reduction
-        index = self.getProperty("RowIndex").value
-
-        if index == Property.EMPTY_INT:
-            return
-
-        # 2. Get the state for the index from the PropertyManagerDataService
-        property_manager_service = PropertyManagerService()
-        state = property_manager_service.get_single_state_from_pmds(index_to_retrieve=index)
-        # 3. Get some global settings
-        use_optimizations = self.getProperty("UseOptimizations").value
-        output_mode_as_string = self.getProperty("OutputMode").value
-        output_mode = OutputMode(output_mode_as_string)
-        plot_results = self.getProperty('PlotResults').value
-        output_graph = self.getProperty('OutputGraph').value
-
-        # 3. Run the sans_batch script
-        sans_batch = SANSBatchReduction()
-        sans_batch(states=state, use_optimizations=use_optimizations, output_mode=output_mode, plot_results=plot_results
-                   , output_graph=output_graph)
diff --git a/scripts/SANS/sans/state/JsonSerializable.py b/scripts/SANS/sans/state/JsonSerializable.py
new file mode 100644
index 0000000000000000000000000000000000000000..453ae9c2dddee6e0f8b1e50c34204bf4712b9466
--- /dev/null
+++ b/scripts/SANS/sans/state/JsonSerializable.py
@@ -0,0 +1,75 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+from enum import Enum
+
+
+def json_serializable(cls):  # Decorator for enums
+    assert issubclass(cls, Enum)
+    JsonSerializable._register_enum_type(cls)
+    return cls
+
+
+class JsonSerializable(type):
+    """ The fundamental base of the SANS State"""
+    _derived_types = {}
+
+    __ENUM_TAG = "E#"
+    __TYPE_TAG = "T#"
+
+    def __init__(cls, name, bases, dct):
+        cls._derived_types[cls._tag_type(cls)] = cls
+        super(JsonSerializable, cls).__init__(name, bases, dct)
+
+    @staticmethod
+    def tag_type(incoming_type):
+        def check_in_dict(tag):
+            if tag not in JsonSerializable._derived_types:
+                raise RuntimeError("Trying to serialize enum {0} which is not registered with JsonSerializer"
+                                   "\nUse the add_json_support decorator on the enum".format(tag))
+            return tag
+
+        metaclass = type(incoming_type)
+        if issubclass(incoming_type, Enum):
+            return check_in_dict(JsonSerializable._tag_enum(incoming_type))
+
+        if issubclass(metaclass, JsonSerializable):
+            return check_in_dict(JsonSerializable._tag_type(incoming_type))
+
+        raise RuntimeError("Unknown type {0} passed to tag_type".format(incoming_type))
+
+    @staticmethod
+    def class_type_from_tag(tag):
+        return JsonSerializable._find_type(tag, JsonSerializable.__TYPE_TAG)
+
+    @staticmethod
+    def _enum_type_from_tag(tag):
+        return JsonSerializable._find_type(tag, JsonSerializable.__ENUM_TAG)
+
+    @staticmethod
+    def _find_type(tag, type_to_search_for):
+        if not tag.startswith(type_to_search_for):
+            return
+
+        try:
+            return JsonSerializable._derived_types[tag]
+        except KeyError:
+            if tag.startswith(type_to_search_for):
+                raise RuntimeError("Trying to deserialize {0} which is not registered with JsonSerializer".format(tag))
+
+    @staticmethod
+    def _register_enum_type(e_type):
+        JsonSerializable._derived_types[JsonSerializable._tag_enum(e_type)] = e_type
+
+    @staticmethod
+    def _tag_enum(t):
+        return JsonSerializable.__ENUM_TAG + t.__name__
+
+    @staticmethod
+    def _tag_type(t):
+        return JsonSerializable.__TYPE_TAG + t.__name__
diff --git a/scripts/SANS/sans/state/Serializer.py b/scripts/SANS/sans/state/Serializer.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa45bafa9d100a82a525849a9fcfedeb16f2bb1c
--- /dev/null
+++ b/scripts/SANS/sans/state/Serializer.py
@@ -0,0 +1,78 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+import json
+from enum import Enum
+
+import six
+
+from sans.state.JsonSerializable import JsonSerializable
+
+
+class Serializer(object):
+    @staticmethod
+    def to_json(obj):
+        return json.dumps(obj, cls=SerializerImpl)
+
+    @staticmethod
+    def from_json(json_str):
+        assert isinstance(json_str, str)
+        return json.loads(json_str, object_hook=SerializerImpl.obj_hook)
+
+    @staticmethod
+    def load_file(file_path):
+        with open(file_path, 'r') as f:
+            return json.load(f, object_hook=SerializerImpl.obj_hook)
+
+    @staticmethod
+    def save_file(obj, file_path):
+        with open(file_path, 'w') as f:
+            json.dump(obj, f, cls=SerializerImpl, sort_keys=True, indent=4)
+
+
+class SerializerImpl(json.JSONEncoder):
+    def default(self, o):
+        metaclass = type(type(o))  # Get class of o, then get metaclass of the class type
+        if issubclass(metaclass, JsonSerializable):
+            tag = JsonSerializable.tag_type(type(o))
+            return {tag: o.__dict__}
+
+        if isinstance(o, Enum):
+            tag = JsonSerializable.tag_type(type(o))
+            return {tag: o.value}
+
+        if isinstance(o, tuple) and hasattr(o, "_fields"):
+            raise ValueError("A NamedTuple was passed to the JSON encoder, this is not supported as"
+                             " it will be deserialized to a list")
+
+        return json.JSONEncoder.default(self, o)
+
+    @staticmethod
+    def obj_hook(o):
+        for type_tag, internal_dict in o.items():
+            assert isinstance(type_tag, six.string_types)
+
+            cls_type = JsonSerializable.class_type_from_tag(type_tag)
+            if cls_type:
+                return SerializerImpl._reconstruct_class(cls_type, internal_dict)
+
+            enum_type = JsonSerializable._enum_type_from_tag(type_tag)
+            if enum_type:
+                return SerializerImpl._reconstruct_enum(enum_type, internal_dict)
+
+        return o
+
+    @staticmethod
+    def _reconstruct_class(found_type, ordered_dict):
+        assert isinstance(found_type, type)
+        assert isinstance(ordered_dict, dict)
+        obj = found_type()
+        obj.__dict__ = ordered_dict
+        return obj
+
+    @staticmethod
+    def _reconstruct_enum(found_type, val):
+        return found_type(val)
diff --git a/scripts/SANS/sans/state/adjustment.py b/scripts/SANS/sans/state/adjustment.py
index 6cece2c654a3f649696e4c09db26464d1ea9ad65..1d02c7a351909e82090e875b1bedbd901b07029c 100644
--- a/scripts/SANS/sans/state/adjustment.py
+++ b/scripts/SANS/sans/state/adjustment.py
@@ -9,30 +9,25 @@
 """State describing the adjustment workspace creation of the SANS reduction."""
 
 from __future__ import (absolute_import, division, print_function)
-import json
+
 import copy
-from sans.state.state_base import (StateBase, TypedParameter, rename_descriptor_names, BoolParameter,
-                                   validator_sub_state)
-from sans.state.calculate_transmission import StateCalculateTransmission
-from sans.state.normalize_to_monitor import StateNormalizeToMonitor
-from sans.state.wavelength_and_pixel_adjustment import StateWavelengthAndPixelAdjustment
-from sans.state.automatic_setters import (automatic_setters)
+import json
+
+from six import with_metaclass
+
 from sans.common.enums import SANSFacility
+from sans.state.JsonSerializable import JsonSerializable
+from sans.state.automatic_setters import automatic_setters
 
 
-# ----------------------------------------------------------------------------------------------------------------------
-# State
-# ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateAdjustment(StateBase):
-    calculate_transmission = TypedParameter(StateCalculateTransmission, validator_sub_state)
-    normalize_to_monitor = TypedParameter(StateNormalizeToMonitor, validator_sub_state)
-    wavelength_and_pixel_adjustment = TypedParameter(StateWavelengthAndPixelAdjustment, validator_sub_state)
-    wide_angle_correction = BoolParameter()
+class StateAdjustment(with_metaclass(JsonSerializable)):
 
     def __init__(self):
         super(StateAdjustment, self).__init__()
-        self.wide_angle_correction = False
+        self.calculate_transmission = None  # : StateCalculateTransmission
+        self.normalize_to_monitor = None  # : StateNormalizeToMonitor
+        self.wavelength_and_pixel_adjustment = None  # : StateWavelengthAndPixelAdjustment
+        self.wide_angle_correction = False  # : Bool
 
     def validate(self):
         is_invalid = {}
diff --git a/scripts/SANS/sans/state/automatic_setters.py b/scripts/SANS/sans/state/automatic_setters.py
index 78e6dccdbf1018e24f0d650636b3e3abf848b2bd..9f6557ca7eb43b3ceca854567c58882d099506c7 100644
--- a/scripts/SANS/sans/state/automatic_setters.py
+++ b/scripts/SANS/sans/state/automatic_setters.py
@@ -8,7 +8,6 @@ from __future__ import (absolute_import, division, print_function)
 from functools import (partial, wraps)
 import inspect
 
-from sans.state.state_base import (TypedParameter, DictParameter)
 # -------------------------------------------------------------------------------------------------------------
 # Automatic Setter functionality
 # This creates setters on a builder/director instance for parameters of a state object.
@@ -43,7 +42,11 @@ def forwarding_setter(value, builder_instance, attribute_name_list):
 def update_the_method(builder_instance,  new_methods, setter_name, attribute_name, attribute_name_list):
     setter_name_copy = list(setter_name)
     setter_name_copy.append(attribute_name)
-    method_name = "_".join(setter_name_copy)
+    try:
+        method_name = "_".join(setter_name_copy)
+    except TypeError as e:
+        # An enum is being used for a key - the dev needs to switch to a value rather than the enum type
+        raise TypeError("You are likely trying to use an enum as a dict key which is not supported.\n {0}".format(e))
 
     attribute_name_list_copy = list(attribute_name_list)
     attribute_name_list_copy.append(attribute_name)
@@ -55,8 +58,8 @@ def update_the_method(builder_instance,  new_methods, setter_name, attribute_nam
 
 def get_all_typed_parameter_descriptors(instance):
     descriptor_types = {}
-    for descriptor_name, descriptor_object in inspect.getmembers(type(instance)):
-        if inspect.isdatadescriptor(descriptor_object) and isinstance(descriptor_object, TypedParameter):
+    for descriptor_name, descriptor_object in inspect.getmembers(instance):
+        if not descriptor_name.startswith('__'):
             descriptor_types.update({descriptor_name: descriptor_object})
     return descriptor_types
 
@@ -76,7 +79,7 @@ def create_automatic_setters_for_state(attribute_value, builder_instance, attrib
         # 1. A dictionary which is empty or None-> install a setter
         # 2. A dictionary containing elements -> for each element apply a recursion
         # 3. A regular attribute -> install the setter
-        if isinstance(value, DictParameter):
+        if isinstance(value, dict):
             dict_parameter_value = getattr(attribute_value, name)
             if dict_parameter_value is None or len(dict_parameter_value) == 0:
                 update_the_method(builder_instance, new_methods, setter_name, name, attribute_name_list)
diff --git a/scripts/SANS/sans/state/calculate_transmission.py b/scripts/SANS/sans/state/calculate_transmission.py
index eb7911f07aeca46263d93ee05317085ea45ce87e..a3723ef43141a6575ce0966b01e6e29e583f1259 100644
--- a/scripts/SANS/sans/state/calculate_transmission.py
+++ b/scripts/SANS/sans/state/calculate_transmission.py
@@ -12,33 +12,25 @@ from __future__ import (absolute_import, division, print_function)
 import json
 import copy
 import abc
-import six
+from six import with_metaclass, itervalues, add_metaclass
 
-from sans.state.state_base import (StateBase, rename_descriptor_names, PositiveIntegerParameter, BoolParameter,
-                                   PositiveFloatParameter, FloatParameter, DictParameter,
-                                   StringListParameter, PositiveFloatWithNoneParameter, PositiveFloatListParameter)
+from sans.state.JsonSerializable import JsonSerializable
 from sans.common.enums import (RebinType, RangeStepType, FitType, DataType, SANSInstrument)
 from sans.common.configurations import Configurations
+from sans.state.automatic_setters import automatic_setters
 from sans.state.state_functions import (is_pure_none_or_not_none, validation_message,
                                         is_not_none_and_first_larger_than_second, one_is_none)
-from sans.state.automatic_setters import (automatic_setters)
 from sans.common.xml_parsing import get_named_elements_from_ipf_file
 
 
-# ----------------------------------------------------------------------------------------------------------------------
-# State
-# ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateTransmissionFit(StateBase):
-    fit_type = FitType.LOGARITHMIC
-    polynomial_order = PositiveIntegerParameter()
-    wavelength_low = PositiveFloatWithNoneParameter()
-    wavelength_high = PositiveFloatWithNoneParameter()
+class StateTransmissionFit(with_metaclass(JsonSerializable)):
 
     def __init__(self):
         super(StateTransmissionFit, self).__init__()
         self.fit_type = FitType.LOGARITHMIC
-        self.polynomial_order = 0
+        self.polynomial_order = 0  # : Int (Positive)
+        self.wavelength_low = None  # : Float (Optional)
+        self.wavelength_high = None  # : Float (Optional)
 
     def validate(self):
         is_invalid = {}
@@ -67,65 +59,54 @@ class StateTransmissionFit(StateBase):
                              "Please see: {0}".format(json.dumps(is_invalid)))
 
 
-@rename_descriptor_names
-class StateCalculateTransmission(StateBase):
-    # -----------------------
-    # Transmission
-    # -----------------------
-    transmission_radius_on_detector = PositiveFloatParameter()
-    transmission_roi_files = StringListParameter()
-    transmission_mask_files = StringListParameter()
-
-    default_transmission_monitor = PositiveIntegerParameter()
-    transmission_monitor = PositiveIntegerParameter()
-
-    default_incident_monitor = PositiveIntegerParameter()
-    incident_monitor = PositiveIntegerParameter()
-
-    # ----------------------
-    # Prompt peak correction
-    # ----------------------
-    prompt_peak_correction_min = PositiveFloatParameter()
-    prompt_peak_correction_max = PositiveFloatParameter()
-    prompt_peak_correction_enabled = BoolParameter()
-
-    # ----------------
-    # Wavelength rebin
-    # ----------------
-    wavelength_low = PositiveFloatListParameter()
-    wavelength_high = PositiveFloatListParameter()
-    wavelength_step = PositiveFloatParameter()
-    rebin_type = RebinType.REBIN
-    wavelength_step_type = RangeStepType.NOT_SET
-
-    use_full_wavelength_range = BoolParameter()
-    wavelength_full_range_low = PositiveFloatParameter()
-    wavelength_full_range_high = PositiveFloatParameter()
-
-    # -----------------------
-    # Background correction
-    # ----------------------
-    background_TOF_general_start = FloatParameter()
-    background_TOF_general_stop = FloatParameter()
-    background_TOF_monitor_start = DictParameter()
-    background_TOF_monitor_stop = DictParameter()
-    background_TOF_roi_start = FloatParameter()
-    background_TOF_roi_stop = FloatParameter()
-
-    fit = {DataType.CAN : StateTransmissionFit(),
-           DataType.SAMPLE : StateTransmissionFit()}
-
+class StateCalculateTransmission(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateCalculateTransmission, self).__init__()
-        # The keys of this dictionaries are the spectrum number of the monitors (as a string)
-        self.background_TOF_monitor_start = {}
-        self.background_TOF_monitor_stop = {}
-        self.use_full_wavelength_range = False
+        # -----------------------
+        # Transmission
+        # -----------------------
+        self.transmission_radius_on_detector = None  # : Float (Positive)
+        self.transmission_roi_files = None  # : List[Str]
+        self.transmission_mask_files = None  # : List[Str]
+
+        self.default_transmission_monitor = None  # : Int (Positive)
+        self.transmission_monitor = None  # : Int (Positive)
 
-        # Default rebin type is a standard Rebin
+        self.default_incident_monitor = None  # : Int (Positive)
+        self.incident_monitor = None  # : Int (Positive)
+
+        # ----------------------
+        # Prompt peak correction
+        # ----------------------
+        self.prompt_peak_correction_min = None  # : Float (Positive)
+        self.prompt_peak_correction_max = None  # : Float (Positive)
+        self.prompt_peak_correction_enabled = False  # : Bool
+
+        # ----------------
+        # Wavelength rebin
+        # ----------------
+        self.wavelength_low = None  # : List[Float] (Positive)
+        self.wavelength_high = None  # : List[Float] (Positive)
+        self.wavelength_step = None  # : Float (Positive)
         self.rebin_type = RebinType.REBIN
+        self.wavelength_step_type = RangeStepType.NOT_SET
+
+        self.use_full_wavelength_range = False  # : Bool
+        self.wavelength_full_range_low = None  # : Float (Positive)
+        self.wavelength_full_range_high = None  # : Float (Positive)
+
+        # -----------------------
+        # Background correction
+        # ----------------------
+        self.background_TOF_general_start = None  # : Float
+        self.background_TOF_general_stop = None  # : Float
+        self.background_TOF_monitor_start = {}  # : Dict
+        self.background_TOF_monitor_stop = {}  # : Dict
+        self.background_TOF_roi_start = None  # : Float
+        self.background_TOF_roi_stop = None  # : Float
 
-        self.prompt_peak_correction_enabled = False
+        self.fit = {DataType.CAN.value: StateTransmissionFit(),
+                    DataType.SAMPLE.value: StateTransmissionFit()}
 
     def validate(self):  # noqa
         is_invalid = {}
@@ -142,9 +123,9 @@ class StateCalculateTransmission(StateBase):
         # --------------
         # Transmission, either we need some ROI (ie radius, roi files /mask files) or a transmission monitor
         # --------------
-        has_no_transmission_monitor_setting = self.transmission_monitor is None and\
+        has_no_transmission_monitor_setting = self.transmission_monitor is None and \
                                               self.default_transmission_monitor is None  # noqa
-        has_no_transmission_roi_setting = self.transmission_radius_on_detector is None and\
+        has_no_transmission_roi_setting = self.transmission_radius_on_detector is None and \
                                           self.transmission_roi_files is None  # noqa
         if has_no_transmission_monitor_setting and has_no_transmission_roi_setting:
             entry = validation_message("No transmission settings were specified.",
@@ -188,7 +169,7 @@ class StateCalculateTransmission(StateBase):
         if self.wavelength_step_type is RangeStepType.NOT_SET:
             entry = validation_message("A wavelength entry has not been set.",
                                        "Make sure that all entries are set.",
-                                       {"wavelength_step_type" : self.wavelength_step_type})
+                                       {"wavelength_step_type": self.wavelength_step_type})
             is_invalid.update(entry)
 
         if is_not_none_and_first_larger_than_second([self.wavelength_low, self.wavelength_high]):
@@ -276,7 +257,7 @@ class StateCalculateTransmission(StateBase):
                                                     "background_TOF_monitor_stop": self.background_TOF_monitor_stop})
                         is_invalid.update(entry)
 
-        for fit_type in six.itervalues(self.fit):
+        for fit_type in itervalues(self.fit):
             fit_type.validate()
 
         if is_invalid:
@@ -359,7 +340,7 @@ def set_default_monitors(calculate_transmission_info, data_info):
 # ---------------------------------------
 # State builders
 # ---------------------------------------
-@six.add_metaclass(abc.ABCMeta)
+@add_metaclass(abc.ABCMeta)
 class StateCalculateTransmissionBuilderCommon(object):
     def __init__(self, state):
         self.state = state
@@ -371,28 +352,28 @@ class StateCalculateTransmissionBuilderCommon(object):
         self.state.rebin_type = val
 
     def set_can_fit_type(self, val):
-        self.state.fit[DataType.CAN].fit_type = val
+        self.state.fit[DataType.CAN.value].fit_type = val
 
     def set_can_polynomial_order(self, val):
-        self.state.fit[DataType.CAN].polynomial_order = val
+        self.state.fit[DataType.CAN.value].polynomial_order = val
 
     def set_can_wavelength_low(self, val):
-        self.state.fit[DataType.CAN].wavelength_low = val
+        self.state.fit[DataType.CAN.value].wavelength_low = val
 
     def set_can_wavelength_high(self, val):
-        self.state.fit[DataType.CAN].wavelength_high = val
+        self.state.fit[DataType.CAN.value].wavelength_high = val
 
     def set_sample_fit_type(self, val):
-        self.state.fit[DataType.SAMPLE].fit_type = val
+        self.state.fit[DataType.SAMPLE.value].fit_type = val
 
     def set_sample_polynomial_order(self, val):
-        self.state.fit[DataType.SAMPLE].polynomial_order = val
+        self.state.fit[DataType.SAMPLE.value].polynomial_order = val
 
     def set_sample_wavelength_low(self, val):
-        self.state.fit[DataType.SAMPLE].wavelength_low = val
+        self.state.fit[DataType.SAMPLE.value].wavelength_low = val
 
     def set_sample_wavelength_high(self, val):
-        self.state.fit[DataType.SAMPLE].wavelength_high = val
+        self.state.fit[DataType.SAMPLE.value].wavelength_high = val
 
 
 class StateCalculateTransmissionBuilderLOQ(StateCalculateTransmissionBuilderCommon):
diff --git a/scripts/SANS/sans/state/compatibility.py b/scripts/SANS/sans/state/compatibility.py
index 6316b81bb76c7bda2d45248c7bd41c085c1b1f7c..846279dd5d3530f20b0975a8bce1c95ea8e92b42 100644
--- a/scripts/SANS/sans/state/compatibility.py
+++ b/scripts/SANS/sans/state/compatibility.py
@@ -14,25 +14,26 @@
 
 from __future__ import (absolute_import, division, print_function)
 import copy
-from sans.state.state_base import (StateBase, rename_descriptor_names, BoolParameter, StringParameter)
-from sans.state.automatic_setters import (automatic_setters)
+
+from six import with_metaclass
+
+from sans.state.JsonSerializable import JsonSerializable
+
 from sans.common.enums import SANSFacility
 
 
 # ----------------------------------------------------------------------------------------------------------------------
 # State
 # ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateCompatibility(StateBase):
-    use_compatibility_mode = BoolParameter()
-    time_rebin_string = StringParameter()
-    use_event_slice_optimisation = BoolParameter()
+from sans.state.automatic_setters import automatic_setters
+
 
+class StateCompatibility(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateCompatibility, self).__init__()
-        self.use_compatibility_mode = False
-        self.use_event_slice_optimisation = False
-        self.time_rebin_string = ""
+        self.use_compatibility_mode = False  # : Bool
+        self.use_event_slice_optimisation = False  # : Bool
+        self.time_rebin_string = ""  # Str
 
     def validate(self):
         pass
diff --git a/scripts/SANS/sans/state/convert_to_q.py b/scripts/SANS/sans/state/convert_to_q.py
index 75cdf108f85db6074cd62882a1836c55837d2726..8b61b484188efc3252a9fc29feae6d4e5f3d8ad6 100644
--- a/scripts/SANS/sans/state/convert_to_q.py
+++ b/scripts/SANS/sans/state/convert_to_q.py
@@ -11,61 +11,57 @@
 from __future__ import (absolute_import, division, print_function)
 import json
 import copy
-from sans.state.state_base import (StateBase, rename_descriptor_names, BoolParameter, PositiveFloatParameter,
-                                   StringParameter)
+
+from six import with_metaclass
+
+from sans.state.JsonSerializable import JsonSerializable
 from sans.common.enums import (ReductionDimensionality, RangeStepType, SANSFacility)
+from sans.state.automatic_setters import automatic_setters
 from sans.state.state_functions import (is_pure_none_or_not_none, is_not_none_and_first_larger_than_second,
                                         validation_message)
-from sans.state.automatic_setters import (automatic_setters)
 
 
 # ----------------------------------------------------------------------------------------------------------------------
 # State
 # ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateConvertToQ(StateBase):
-    reduction_dimensionality = ReductionDimensionality.ONE_DIM
-    use_gravity = BoolParameter()
-    gravity_extra_length = PositiveFloatParameter()
-    radius_cutoff = PositiveFloatParameter()
-    wavelength_cutoff = PositiveFloatParameter()
-
-    # 1D settings
-    q_min = PositiveFloatParameter()
-    q_max = PositiveFloatParameter()
-    q_1d_rebin_string = StringParameter()
-
-    # 2D settings
-    q_xy_max = PositiveFloatParameter()
-    q_xy_step = PositiveFloatParameter()
-    q_xy_step_type = RangeStepType.LIN
-
-    # -----------------------
-    # Q Resolution specific
-    # ---------------------
-    use_q_resolution = BoolParameter()
-    q_resolution_collimation_length = PositiveFloatParameter()
-    q_resolution_delta_r = PositiveFloatParameter()
-    moderator_file = StringParameter()
-
-    # Circular aperture settings
-    q_resolution_a1 = PositiveFloatParameter()
-    q_resolution_a2 = PositiveFloatParameter()
-
-    # Rectangular aperture settings
-    q_resolution_h1 = PositiveFloatParameter()
-    q_resolution_h2 = PositiveFloatParameter()
-    q_resolution_w1 = PositiveFloatParameter()
-    q_resolution_w2 = PositiveFloatParameter()
+
+class StateConvertToQ(with_metaclass(JsonSerializable)):
 
     def __init__(self):
         super(StateConvertToQ, self).__init__()
         self.reduction_dimensionality = ReductionDimensionality.ONE_DIM
-        self.use_gravity = False
-        self.gravity_extra_length = 0.0
-        self.use_q_resolution = False
-        self.radius_cutoff = 0.0
-        self.wavelength_cutoff = 0.0
+        self.use_gravity = False  # : Bool
+        self.gravity_extra_length = 0.0  # : Float (Positive)
+        self.radius_cutoff = 0.0  # : Float (Positive)
+        self.wavelength_cutoff = 0.0  # : Float (Positive)
+
+        # 1D settings
+        self.q_min = None  # : Float (Positive)
+        self.q_max = None  # : Float (Positive)
+        self.q_1d_rebin_string = None  # : Str()
+
+        # 2D settings
+        self.q_xy_max = None  # : Float (Positive)
+        self.q_xy_step = None  # : Float (Positive)
+        self.q_xy_step_type = RangeStepType.LIN
+
+        # -----------------------
+        # Q Resolution specific
+        # ---------------------
+        self.use_q_resolution = False  # : Bool
+        self.q_resolution_collimation_length = None  # : Float (Positive)
+        self.q_resolution_delta_r = None  # : Float (Positive)
+        self.moderator_file = None  # : Str()
+
+        # Circular aperture settings
+        self.q_resolution_a1 = None  # : Float (Positive)
+        self.q_resolution_a2 = None  # : Float (Positive)
+
+        # Rectangular aperture settings
+        self.q_resolution_h1 = None  # : Float (Positive)
+        self.q_resolution_h2 = None  # : Float (Positive)
+        self.q_resolution_w1 = None  # : Float (Positive)
+        self.q_resolution_w2 = None  # : Float (Positive)
 
     def validate(self):
         is_invalid = {}
diff --git a/scripts/SANS/sans/state/data.py b/scripts/SANS/sans/state/data.py
index 4a62bab729d14a6a77e95c1f2c54c148aebab029..03204871fd8ebdeb3a9c84eec3fbf6100e6f3138 100644
--- a/scripts/SANS/sans/state/data.py
+++ b/scripts/SANS/sans/state/data.py
@@ -11,62 +11,51 @@ from __future__ import (absolute_import, division, print_function)
 import json
 import copy
 
-from sans.state.state_base import (StateBase, StringParameter, PositiveIntegerParameter, BoolParameter,
-                                   rename_descriptor_names)
+from six import with_metaclass
+
+from sans.state.JsonSerializable import JsonSerializable
 from sans.common.enums import SANSFacility, SANSInstrument
 import sans.common.constants
-from sans.state.state_functions import (is_pure_none_or_not_none, validation_message)
 from sans.state.automatic_setters import automatic_setters
+from sans.state.state_functions import (is_pure_none_or_not_none, validation_message)
 
 
 # ----------------------------------------------------------------------------------------------------------------------
 # State
 # ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateData(StateBase):
+
+class StateData(with_metaclass(JsonSerializable)):
     ALL_PERIODS = sans.common.constants.ALL_PERIODS
-    sample_scatter = StringParameter()
-    sample_scatter_period = PositiveIntegerParameter()
-    sample_transmission = StringParameter()
-    sample_transmission_period = PositiveIntegerParameter()
-    sample_direct = StringParameter()
-    sample_direct_period = PositiveIntegerParameter()
-
-    can_scatter = StringParameter()
-    can_scatter_period = PositiveIntegerParameter()
-    can_transmission = StringParameter()
-    can_transmission_period = PositiveIntegerParameter()
-    can_direct = StringParameter()
-    can_direct_period = PositiveIntegerParameter()
-
-    calibration = StringParameter()
-
-    sample_scatter_run_number = PositiveIntegerParameter()
-    sample_scatter_is_multi_period = BoolParameter()
-    idf_file_path = StringParameter()
-    ipf_file_path = StringParameter()
-    user_file = StringParameter()
-
-    instrument = SANSInstrument.NO_INSTRUMENT
-    facility = SANSFacility.NO_FACILITY
 
     def __init__(self):
         super(StateData, self).__init__()
 
-        # Setup default values for periods
-        self.sample_scatter_period = StateData.ALL_PERIODS
-        self.sample_transmission_period = StateData.ALL_PERIODS
-        self.sample_direct_period = StateData.ALL_PERIODS
+        self.sample_scatter = None  # : Str()
+        self.sample_scatter_period =  StateData.ALL_PERIODS  # : Int (Positive)
+        self.sample_transmission = None  # : Str()
+        self.sample_transmission_period = StateData.ALL_PERIODS  # : Int (Positive)
+        self.sample_direct = None  # : Str()
+        self.sample_direct_period = StateData.ALL_PERIODS  # : Int (Positive)
+
+        self.can_scatter = None  # : Str()
+        self.can_scatter_period = StateData.ALL_PERIODS  # : Int (Positive)
+        self.can_transmission = None  # : Str()
+        self.can_transmission_period = StateData.ALL_PERIODS  # : Int (Positive)
+        self.can_direct = None  # : Str()
+        self.can_direct_period = StateData.ALL_PERIODS  # : Int (Positive)
+
+        self.calibration = None  # : Str()
 
-        self.can_scatter_period = StateData.ALL_PERIODS
-        self.can_transmission_period = StateData.ALL_PERIODS
-        self.can_direct_period = StateData.ALL_PERIODS
+        self.sample_scatter_run_number = None  # : Int (Positive)
+        self.sample_scatter_is_multi_period = None  # : Bool
+        self.idf_file_path = None  # : Str()
+        self.ipf_file_path = None  # : Str()
+        self.user_file = ""  # : Str()
 
         # This should be reset by the builder. Setting this to NoInstrument ensure that we will trip early on,
         # in case this is not set, for example by not using the builders.
         self.instrument = SANSInstrument.NO_INSTRUMENT
         self.facility = SANSFacility.NO_FACILITY
-        self.user_file = ""
 
     def validate(self):
         is_invalid = dict()
diff --git a/scripts/SANS/sans/state/mask.py b/scripts/SANS/sans/state/mask.py
index 3711f52926f11f5ae43e84ed4ecb1e91f4d94c63..c1bfc8453f87e067f88803dcf7a26b5791465175 100644
--- a/scripts/SANS/sans/state/mask.py
+++ b/scripts/SANS/sans/state/mask.py
@@ -11,11 +11,13 @@
 from __future__ import (absolute_import, division, print_function)
 import json
 import copy
-from sans.state.state_base import (StateBase, BoolParameter, StringListParameter, StringParameter,
-                                   PositiveFloatParameter, FloatParameter, FloatListParameter, FloatWithNoneParameter,
-                                   DictParameter, PositiveIntegerListParameter, rename_descriptor_names)
+
+from six import with_metaclass
+
+from sans.state.JsonSerializable import JsonSerializable
+from sans.state.automatic_setters import automatic_setters
 from sans.state.state_functions import (is_pure_none_or_not_none, validation_message, set_detector_names)
-from sans.state.automatic_setters import (automatic_setters)
+
 from sans.common.file_information import find_full_file_path
 from sans.common.enums import (DetectorType, SANSInstrument)
 from sans.common.general_functions import get_bank_for_spectrum_number
@@ -80,45 +82,44 @@ def is_spectrum_range_all_on_one_detector(start, stop, invalid_dict, start_name,
 # ------------------------------------------------
 # StateData
 # ------------------------------------------------
-@rename_descriptor_names
-class StateMaskDetector(StateBase):
-    # Vertical strip masks
-    single_vertical_strip_mask = PositiveIntegerListParameter()
-    range_vertical_strip_start = PositiveIntegerListParameter()
-    range_vertical_strip_stop = PositiveIntegerListParameter()
-
-    # Horizontal strip masks
-    single_horizontal_strip_mask = PositiveIntegerListParameter()
-    range_horizontal_strip_start = PositiveIntegerListParameter()
-    range_horizontal_strip_stop = PositiveIntegerListParameter()
-
-    # Spectrum Block
-    block_horizontal_start = PositiveIntegerListParameter()
-    block_horizontal_stop = PositiveIntegerListParameter()
-    block_vertical_start = PositiveIntegerListParameter()
-    block_vertical_stop = PositiveIntegerListParameter()
-
-    # Spectrum block cross
-    block_cross_horizontal = PositiveIntegerListParameter()
-    block_cross_vertical = PositiveIntegerListParameter()
-
-    # Time/Bin mask
-    bin_mask_start = FloatListParameter()
-    bin_mask_stop = FloatListParameter()
-
-    # Name of the detector
-    detector_name = StringParameter()
-    detector_name_short = StringParameter()
-
-    # Single Spectra
-    single_spectra = PositiveIntegerListParameter()
-
-    # Spectrum Range
-    spectrum_range_start = PositiveIntegerListParameter()
-    spectrum_range_stop = PositiveIntegerListParameter()
 
+class StateMaskDetector(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateMaskDetector, self).__init__()
+        # Vertical strip masks
+        self.single_vertical_strip_mask = None  # : List[Int] (Positive)
+        self.range_vertical_strip_start = None  # : List[Int] (Positive)
+        self.range_vertical_strip_stop = None  # : List[Int] (Positive)
+
+        # Horizontal strip masks
+        self.single_horizontal_strip_mask = None  # : List[Int] (Positive)
+        self.range_horizontal_strip_start = None  # : List[Int] (Positive)
+        self.range_horizontal_strip_stop = None  # : List[Int] (Positive)
+
+        # Spectrum Block
+        self.block_horizontal_start = None  # : List[Int] (Positive)
+        self.block_horizontal_stop = None  # : List[Int] (Positive)
+        self.block_vertical_start = None  # : List[Int] (Positive)
+        self.block_vertical_stop = None  # : List[Int] (Positive)
+
+        # Spectrum block cross
+        self.block_cross_horizontal = None  # : List[Int] (Positive)
+        self.block_cross_vertical = None  # : List[Int] (Positive)
+
+        # Time/Bin mask
+        self.bin_mask_start = None  # : List[Float]
+        self.bin_mask_stop = None  # : List[Float]
+
+        # Name of the detector
+        self.detector_name = None  # : Str()
+        self.detector_name_short = None  # : Str()
+
+        # Single Spectra
+        self.single_spectra = None  # : List[Int] (Positive)
+
+        # Spectrum Range
+        self.spectrum_range_start = None  # : List[Int] (Positive)
+        self.spectrum_range_stop = None  # : List[Int] (Positive)
 
     def validate(self):
         is_invalid = {}
@@ -170,47 +171,40 @@ class StateMaskDetector(StateBase):
                              "Please see: {0}".format(json.dumps(is_invalid)))
 
 
-@rename_descriptor_names
-class StateMask(StateBase):
-    # Radius Mask
-    radius_min = FloatParameter()
-    radius_max = FloatParameter()
-
-    # Bin mask
-    bin_mask_general_start = FloatListParameter()
-    bin_mask_general_stop = FloatListParameter()
+class StateMask(with_metaclass(JsonSerializable)):
+    def __init__(self):
+        super(StateMask, self).__init__()
+        # Radius Mask
+        self.radius_min = None  # : Float
+        self.radius_max = None  # : Float
 
-    # Mask files
-    mask_files = StringListParameter()
+        # Bin mask
+        self.bin_mask_general_start = None  # : List[Float]
+        self.bin_mask_general_stop = None  # : List[Float]
 
-    # Angle masking
-    phi_min = FloatWithNoneParameter()
-    phi_max = FloatWithNoneParameter()
-    use_mask_phi_mirror = BoolParameter()
+        # Mask files
+        self.mask_files = None  # : List[Str]
 
-    # Beam stop
-    beam_stop_arm_width = PositiveFloatParameter()
-    beam_stop_arm_angle = FloatParameter()
-    beam_stop_arm_pos1 = FloatParameter()
-    beam_stop_arm_pos2 = FloatParameter()
+        # Angle masking
+        self.phi_min = -90.0
+        self.phi_max = 90.0
+        self.use_mask_phi_mirror = True
 
-    # Clear commands
-    clear = BoolParameter()
-    clear_time = BoolParameter()
+        # Beam stop
+        self.beam_stop_arm_width = None  # : Float (Positive)
+        self.beam_stop_arm_angle = None  # : Float
+        self.beam_stop_arm_pos1 = None  # : Float
+        self.beam_stop_arm_pos2 = None  # : Float
 
-    # The detector dependent masks
-    detectors = DictParameter()
+        # Clear commands
+        self.clear = None  # : Bool
+        self.clear_time = None  # : Bool
 
-    # The idf path of the instrument
-    idf_path = StringParameter()
+        # The detector dependent masks
+        self.detectors = None  # : Dict
 
-    def __init__(self):
-        super(StateMask, self).__init__()
-        # IDF Path
+        # The idf path of the instrument
         self.idf_path = ""
-        self.phi_min = -90.0
-        self.phi_max = 90.0
-        self.use_mask_phi_mirror = True
 
     def validate(self):
         is_invalid = dict()
@@ -257,7 +251,6 @@ class StateMask(StateBase):
                              "Please see: {0}".format(json.dumps(is_invalid)))
 
 
-@rename_descriptor_names
 class StateMaskSANS2D(StateMask):
     def __init__(self):
         super(StateMaskSANS2D, self).__init__()
@@ -269,7 +262,6 @@ class StateMaskSANS2D(StateMask):
         super(StateMaskSANS2D, self).validate()
 
 
-@rename_descriptor_names
 class StateMaskLOQ(StateMask):
     def __init__(self):
         super(StateMaskLOQ, self).__init__()
@@ -281,7 +273,6 @@ class StateMaskLOQ(StateMask):
         super(StateMaskLOQ, self).validate()
 
 
-@rename_descriptor_names
 class StateMaskLARMOR(StateMask):
     def __init__(self):
         super(StateMaskLARMOR, self).__init__()
@@ -292,7 +283,6 @@ class StateMaskLARMOR(StateMask):
         super(StateMaskLARMOR, self).validate()
 
 
-@rename_descriptor_names
 class StateMaskZOOM(StateMask):
     def __init__(self):
         super(StateMaskZOOM, self).__init__()
diff --git a/scripts/SANS/sans/state/move.py b/scripts/SANS/sans/state/move.py
index 322517940278aca711bfc65a142e42c00bf7fe5d..0836f5ed6c598af830a599500b1b54f927b02754 100644
--- a/scripts/SANS/sans/state/move.py
+++ b/scripts/SANS/sans/state/move.py
@@ -13,55 +13,41 @@ from __future__ import (absolute_import, division, print_function)
 import copy
 import json
 
+from six import with_metaclass
+
 from sans.common.enums import (CanonicalCoordinates, SANSInstrument, DetectorType)
+from sans.state.JsonSerializable import JsonSerializable
 from sans.state.automatic_setters import automatic_setters
-from sans.state.state_base import (StateBase, FloatParameter, DictParameter,
-                                   StringWithNoneParameter, rename_descriptor_names)
 from sans.state.state_functions import (validation_message, set_detector_names, set_monitor_names)
 
 
 # ----------------------------------------------------------------------------------------------------------------------
 # State
 # ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateMoveDetector(StateBase):
-    x_translation_correction = FloatParameter()
-    y_translation_correction = FloatParameter()
-    z_translation_correction = FloatParameter()
-
-    rotation_correction = FloatParameter()
-    side_correction = FloatParameter()
-    radius_correction = FloatParameter()
-
-    x_tilt_correction = FloatParameter()
-    y_tilt_correction = FloatParameter()
-    z_tilt_correction = FloatParameter()
-
-    sample_centre_pos1 = FloatParameter()
-    sample_centre_pos2 = FloatParameter()
-
-    # Name of the detector
-    detector_name = StringWithNoneParameter()
-    detector_name_short = StringWithNoneParameter()
 
+class StateMoveDetector(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateMoveDetector, self).__init__()
         # Translation correction
-        self.x_translation_correction = 0.0
-        self.y_translation_correction = 0.0
-        self.z_translation_correction = 0.0
+        self.x_translation_correction = 0.0  # : Float
+        self.y_translation_correction = 0.0  # : Float
+        self.z_translation_correction = 0.0  # : Float
 
-        self.rotation_correction = 0.0
-        self.side_correction = 0.0
-        self.radius_correction = 0.0
+        self.rotation_correction = 0.0  # : Float
+        self.side_correction = 0.0  # : Float
+        self.radius_correction = 0.0  # : Float
 
-        self.x_tilt_correction = 0.0
-        self.y_tilt_correction = 0.0
-        self.z_tilt_correction = 0.0
+        self.x_tilt_correction = 0.0  # : Float
+        self.y_tilt_correction = 0.0  # : Float
+        self.z_tilt_correction = 0.0  # : Float
 
         # Sample centre Pos 1 + Pos 2
-        self.sample_centre_pos1 = 0.0
-        self.sample_centre_pos2 = 0.0
+        self.sample_centre_pos1 = 0.0  # : Float
+        self.sample_centre_pos2 = 0.0  # : Float
+
+        # Name of the detector
+        self.detector_name = None  # : Str
+        self.detector_name_short = None  # : Str
 
     def validate(self):
         is_invalid = {}
@@ -80,19 +66,13 @@ class StateMoveDetector(StateBase):
                              "Please see: {0}".format(json.dumps(is_invalid)))
 
 
-@rename_descriptor_names
-class StateMove(StateBase):
-    sample_offset = FloatParameter()
-    detectors = DictParameter()
-    monitor_names = DictParameter()
-
-    sample_offset_direction = CanonicalCoordinates.Z
-
+class StateMove(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateMove, self).__init__()
 
-        # Setup the sample offset
-        self.sample_offset = 0.0
+        self.sample_offset = 0.0  # : Float
+        self.detectors = None  # : Dict
+        self.monitor_names = None  # : Dict
 
         # The sample offset direction is Z for the ISIS instruments
         self.sample_offset_direction = CanonicalCoordinates.Z
@@ -107,14 +87,11 @@ class StateMove(StateBase):
             self.detectors[key].validate()
 
 
-@rename_descriptor_names
 class StateMoveLOQ(StateMove):
-    center_position = FloatParameter()
-
     def __init__(self):
         super(StateMoveLOQ, self).__init__()
         # Set the center_position in meter
-        self.center_position = 317.5 / 1000.
+        self.center_position = 317.5 / 1000.  # : Float
 
         # Set the monitor names
         self.monitor_names = {}
@@ -128,42 +105,25 @@ class StateMoveLOQ(StateMove):
         super(StateMoveLOQ, self).validate()
 
 
-@rename_descriptor_names
 class StateMoveSANS2D(StateMove):
-    hab_detector_radius = FloatParameter()
-    hab_detector_default_sd_m = FloatParameter()
-    hab_detector_default_x_m = FloatParameter()
-
-    lab_detector_default_sd_m = FloatParameter()
-
-    hab_detector_x = FloatParameter()
-    hab_detector_z = FloatParameter()
-
-    hab_detector_rotation = FloatParameter()
-
-    lab_detector_x = FloatParameter()
-    lab_detector_z = FloatParameter()
-
-    monitor_4_offset = FloatParameter()
-
     def __init__(self):
         super(StateMoveSANS2D, self).__init__()
         # Set the descriptors which corresponds to information which we gain through the IPF
-        self.hab_detector_radius = 306.0 / 1000.
-        self.hab_detector_default_sd_m = 4.0
-        self.hab_detector_default_x_m = 1.1
-        self.lab_detector_default_sd_m = 4.0
+        self.hab_detector_radius = 306.0 / 1000.  # : Float
+        self.hab_detector_default_sd_m = 4.0  # : Float
+        self.hab_detector_default_x_m = 1.1  # : Float
+        self.lab_detector_default_sd_m = 4.0  # : Float
 
         # The actual values are found on the workspace and should be used from there. This is only a fall back.
-        self.hab_detector_x = 0.0
-        self.hab_detector_z = 0.0
-        self.hab_detector_rotation = 0.0
-        self.lab_detector_x = 0.0
-        self.lab_detector_z = 0.0
+        self.hab_detector_x = 0.0  # : Float
+        self.hab_detector_z = 0.0  # : Float
+        self.hab_detector_rotation = 0.0  # : Float
+        self.lab_detector_x = 0.0  # : Float
+        self.lab_detector_z = 0.0  # : Float
 
         # Set the monitor names
         self.monitor_names = {}
-        self.monitor_4_offset = 0.0
+        self.monitor_4_offset = 0.0  # : Float
 
         # Setup the detectors
         self.detectors = {DetectorType.LAB.value: StateMoveDetector(),
@@ -173,15 +133,12 @@ class StateMoveSANS2D(StateMove):
         super(StateMoveSANS2D, self).validate()
 
 
-@rename_descriptor_names
 class StateMoveLARMOR(StateMove):
-    bench_rotation = FloatParameter()
-
     def __init__(self):
         super(StateMoveLARMOR, self).__init__()
 
         # Set a default for the bench rotation
-        self.bench_rotation = 0.0
+        self.bench_rotation = 0.0  # : Float
 
         # Set the monitor names
         self.monitor_names = {}
@@ -193,22 +150,16 @@ class StateMoveLARMOR(StateMove):
         super(StateMoveLARMOR, self).validate()
 
 
-@rename_descriptor_names
 class StateMoveZOOM(StateMove):
-
-    lab_detector_default_sd_m = FloatParameter()
-    monitor_4_offset = FloatParameter()
-    monitor_5_offset = FloatParameter()
-
     def __init__(self):
         super(StateMoveZOOM, self).__init__()
-        self.lab_detector_default_sd_m = 0.0
+        self.lab_detector_default_sd_m = 0.0  # : Float
 
         # Set the monitor names
         self.monitor_names = {}
 
-        self.monitor_4_offset = 0.0
-        self.monitor_5_offset = 0.0
+        self.monitor_4_offset = 0.0  # : Float
+        self.monitor_5_offset = 0.0  # : Float
 
         # Setup the detectors
         self.detectors = {DetectorType.LAB.value: StateMoveDetector()}
diff --git a/scripts/SANS/sans/state/normalize_to_monitor.py b/scripts/SANS/sans/state/normalize_to_monitor.py
index 9e0a68c1dac5b026b166654a6b07dab778b351fd..69f19f39bea4cfe33b4e0dbdf16df8077f72df8b 100644
--- a/scripts/SANS/sans/state/normalize_to_monitor.py
+++ b/scripts/SANS/sans/state/normalize_to_monitor.py
@@ -11,46 +11,36 @@
 from __future__ import (absolute_import, division, print_function)
 import json
 import copy
-from sans.state.state_base import (StateBase, rename_descriptor_names, PositiveIntegerParameter,
-                                   PositiveFloatParameter, FloatParameter, DictParameter,
-                                   PositiveFloatWithNoneParameter, BoolParameter, PositiveFloatListParameter)
-from sans.state.automatic_setters import (automatic_setters)
+
+from six import with_metaclass
+
+from sans.state.JsonSerializable import JsonSerializable
 from sans.common.enums import (RebinType, RangeStepType, SANSInstrument)
+from sans.state.automatic_setters import automatic_setters
 from sans.state.state_functions import (is_pure_none_or_not_none, is_not_none_and_first_larger_than_second,
                                         one_is_none, validation_message)
 from sans.common.xml_parsing import get_named_elements_from_ipf_file
 
 
-# ----------------------------------------------------------------------------------------------------------------------
-# State
-# ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateNormalizeToMonitor(StateBase):
-    prompt_peak_correction_min = PositiveFloatWithNoneParameter()
-    prompt_peak_correction_max = PositiveFloatWithNoneParameter()
-    prompt_peak_correction_enabled = BoolParameter()
-
-    rebin_type = RebinType.REBIN
-    wavelength_low = PositiveFloatListParameter()
-    wavelength_high = PositiveFloatListParameter()
-    wavelength_step = PositiveFloatParameter()
-    wavelength_step_type = RangeStepType.NOT_SET
-
-    background_TOF_general_start = FloatParameter()
-    background_TOF_general_stop = FloatParameter()
-    background_TOF_monitor_start = DictParameter()
-    background_TOF_monitor_stop = DictParameter()
-
-    incident_monitor = PositiveIntegerParameter()
-
+class StateNormalizeToMonitor(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateNormalizeToMonitor, self).__init__()
-        self.background_TOF_monitor_start = {}
-        self.background_TOF_monitor_stop = {}
-        self.prompt_peak_correction_enabled = False
+        self.prompt_peak_correction_min = None  # : Float (Optional)
+        self.prompt_peak_correction_max = None  # : Float (Optional)
+        self.prompt_peak_correction_enabled = False  # : Bool
 
-        # Default rebin type is a standard Rebin
         self.rebin_type = RebinType.REBIN
+        self.wavelength_low = None  # : List[Float] (Positive)
+        self.wavelength_high = None  # : List[Float] (Positive)
+        self.wavelength_step = None  # : Float (Positive)
+        self.wavelength_step_type = RangeStepType.NOT_SET
+
+        self.background_TOF_general_start = None  # : Float
+        self.background_TOF_general_stop = None  # : Float
+        self.background_TOF_monitor_start = {}  # : Dict
+        self.background_TOF_monitor_stop = {}  # : Dict
+
+        self.incident_monitor = None  # : Int (Positive)
 
     def validate(self):
         is_invalid = {}
@@ -150,7 +140,6 @@ class StateNormalizeToMonitor(StateBase):
                              "Please see: {0}".format(json.dumps(is_invalid)))
 
 
-@rename_descriptor_names
 class StateNormalizeToMonitorLOQ(StateNormalizeToMonitor):
     def __init__(self):
         super(StateNormalizeToMonitorLOQ, self).__init__()
diff --git a/scripts/SANS/sans/state/reduction_mode.py b/scripts/SANS/sans/state/reduction_mode.py
index 6a98e407841cde44f688a95e7cbcb81ff4c9c2c2..eff1637ae0c3e1f8b9d573a7f6cecf0d20f4d1dc 100644
--- a/scripts/SANS/sans/state/reduction_mode.py
+++ b/scripts/SANS/sans/state/reduction_mode.py
@@ -11,68 +11,31 @@ from __future__ import (absolute_import, division, print_function)
 
 import copy
 import json
-from abc import (ABCMeta, abstractmethod)
 
 from six import (with_metaclass)
 
 from sans.common.enums import (ReductionMode, ReductionDimensionality, FitModeForMerge,
                                SANSFacility, DetectorType)
 from sans.common.xml_parsing import get_named_elements_from_ipf_file
-from sans.state.automatic_setters import (automatic_setters)
-from sans.state.state_base import (StateBase, FloatParameter, DictParameter,
-                                   FloatWithNoneParameter, rename_descriptor_names, BoolParameter)
+from sans.state.JsonSerializable import JsonSerializable
+from sans.state.automatic_setters import automatic_setters
 
 
-# ----------------------------------------------------------------------------------------------------------------------
-# State
-# ----------------------------------------------------------------------------------------------------------------------
-class StateReductionBase(with_metaclass(ABCMeta, object)):
-    @abstractmethod
-    def get_merge_strategy(self):
-        pass
-
-    @abstractmethod
-    def get_detector_name_for_reduction_mode(self, reduction_mode):
-        pass
-
-    @abstractmethod
-    def get_all_reduction_modes(self):
-        pass
-
-
-@rename_descriptor_names
-class StateReductionMode(StateReductionBase, StateBase):
-    reduction_mode = ReductionMode.NOT_SET
-
-    reduction_dimensionality = ReductionDimensionality.ONE_DIM
-    merge_max = FloatWithNoneParameter()
-    merge_min = FloatWithNoneParameter()
-    merge_mask = BoolParameter()
-
-    # Fitting
-    merge_fit_mode = FitModeForMerge.NO_FIT
-    merge_shift = FloatParameter()
-    merge_scale = FloatParameter()
-    merge_range_min = FloatWithNoneParameter()
-    merge_range_max = FloatWithNoneParameter()
-
-    # Map from detector type to detector name
-    detector_names = DictParameter()
-
+class StateReductionMode(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateReductionMode, self).__init__()
         self.reduction_mode = ReductionMode.LAB
         self.reduction_dimensionality = ReductionDimensionality.ONE_DIM
 
         # Set the shifts to defaults which essentially don't do anything.
-        self.merge_shift = 0.0
-        self.merge_scale = 1.0
+        self.merge_shift = 0.0  # : Float
+        self.merge_scale = 1.0  # : Float
         self.merge_fit_mode = FitModeForMerge.NO_FIT
-        self.merge_range_min = None
-        self.merge_range_max = None
-        self.merge_max = None
-        self.merge_min = None
-        self.merge_mask = False
+        self.merge_range_min = None  # : Float
+        self.merge_range_max = None  # : Float
+        self.merge_max = None  # : Float
+        self.merge_min = None  # : Float
+        self.merge_mask = False  # : Bool
 
         # Set the detector names to empty strings
         self.detector_names = {DetectorType.LAB.value: "",
diff --git a/scripts/SANS/sans/state/save.py b/scripts/SANS/sans/state/save.py
index c97334e799a9c892b446817571fdc1ba1a9764ef..12e880fde19def88fa62250a4cf43ed633b27cec 100644
--- a/scripts/SANS/sans/state/save.py
+++ b/scripts/SANS/sans/state/save.py
@@ -10,28 +10,24 @@
 
 from __future__ import (absolute_import, division, print_function)
 import copy
-from sans.state.state_base import (StateBase, BoolParameter, StringParameter, StringWithNoneParameter,
-                                   rename_descriptor_names)
-from sans.common.enums import (SaveType, SANSFacility)
-from sans.state.automatic_setters import (automatic_setters)
 
+from six import with_metaclass
 
-# ----------------------------------------------------------------------------------------------------------------------
-# State
-# ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateSave(StateBase):
-    zero_free_correction = BoolParameter()
-    file_format = SaveType.NO_TYPE
+from sans.state.JsonSerializable import JsonSerializable
+from sans.common.enums import (SaveType, SANSFacility)
+from sans.state.automatic_setters import automatic_setters
 
-    # Settings for the output name
-    user_specified_output_name = StringWithNoneParameter()
-    user_specified_output_name_suffix = StringParameter()
-    use_reduction_mode_as_suffix = BoolParameter()
 
+class StateSave(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateSave, self).__init__()
-        self.zero_free_correction = True
+        self.zero_free_correction = True  # : Bool
+        self.file_format = SaveType.NO_TYPE
+
+        # Settings for the output name
+        self.user_specified_output_name = None  # : Str
+        self.user_specified_output_name_suffix = None  # : Str()
+        self.use_reduction_mode_as_suffix = None  # : Bool
 
     def validate(self):
         pass
diff --git a/scripts/SANS/sans/state/scale.py b/scripts/SANS/sans/state/scale.py
index fb191a4449da2eb7f29814e9826ec9330e0635d8..c0bf8ad877683ca7ab5d82a132fcbb03416c2537 100644
--- a/scripts/SANS/sans/state/scale.py
+++ b/scripts/SANS/sans/state/scale.py
@@ -8,35 +8,37 @@
 
 from __future__ import (absolute_import, division, print_function)
 import copy
-from sans.state.state_base import (StateBase, rename_descriptor_names, PositiveFloatParameter)
-from sans.common.enums import (SampleShape, SANSFacility)
-from sans.state.automatic_setters import (automatic_setters)
+
+from six import with_metaclass
+
+from sans.state.JsonSerializable import JsonSerializable
+from sans.common.enums import SampleShape, SANSFacility
 
 
 # ----------------------------------------------------------------------------------------------------------------------
 #  State
 # ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateScale(StateBase):
-    shape = None
+from sans.state.automatic_setters import automatic_setters
 
-    thickness = PositiveFloatParameter()
-    width = PositiveFloatParameter()
-    height = PositiveFloatParameter()
-    scale = PositiveFloatParameter()
 
-    # Geometry from the file
-    shape_from_file = SampleShape.DISC
-    thickness_from_file = PositiveFloatParameter()
-    width_from_file = PositiveFloatParameter()
-    height_from_file = PositiveFloatParameter()
+class StateScale(with_metaclass(JsonSerializable)):
 
     def __init__(self):
         super(StateScale, self).__init__()
+        self.shape = None
+
+        self.thickness = None  # : Float (Positive)
+        self.width = None  # : Float (Positive)
+        self.height = None  # : Float (Positive)
+        self.scale = None  # : Float (Positive)
+
+        # Geometry from the file
+        self.shape_from_file = SampleShape.DISC
+
         # The default values are 1mm
-        self.thickness_from_file = 1.
-        self.width_from_file = 1.
-        self.height_from_file = 1.
+        self.thickness_from_file = 1.  # : Float (Positive)
+        self.width_from_file = 1.  # : Float (Positive)
+        self.height_from_file = 1.  # : Float (Positive)
 
     def validate(self):
         pass
@@ -54,7 +56,7 @@ def set_geometry_from_file(state, file_information):
 
 
 class StateScaleBuilder(object):
-    @automatic_setters(StateScale, exclusions=[])
+    @automatic_setters(StateScale)
     def __init__(self, file_information):
         super(StateScaleBuilder, self).__init__()
         self.state = StateScale()
diff --git a/scripts/SANS/sans/state/slice_event.py b/scripts/SANS/sans/state/slice_event.py
index 91f3c059f515c5bbc67c734533a206e6b8d62ba2..b740b8b66d7d01e51480052ed2944a16d7e153ef 100644
--- a/scripts/SANS/sans/state/slice_event.py
+++ b/scripts/SANS/sans/state/slice_event.py
@@ -9,23 +9,26 @@
 from __future__ import (absolute_import, division, print_function)
 import json
 import copy
-from sans.state.state_base import (StateBase, rename_descriptor_names, FloatListParameter)
+
+from six import with_metaclass
+
+from sans.state.JsonSerializable import JsonSerializable
+from sans.state.automatic_setters import automatic_setters
 from sans.state.state_functions import (is_pure_none_or_not_none, validation_message)
 from sans.common.enums import SANSFacility
-from sans.state.automatic_setters import (automatic_setters)
 
 
 # ----------------------------------------------------------------------------------------------------------------------
 # State
 # ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateSliceEvent(StateBase):
-    start_time = FloatListParameter()
-    end_time = FloatListParameter()
 
+class StateSliceEvent(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateSliceEvent, self).__init__()
 
+        self.start_time = None  # : List[Float]
+        self.end_time = None  # : List[Float]
+
     def validate(self):
         is_invalid = dict()
 
diff --git a/scripts/SANS/sans/state/state.py b/scripts/SANS/sans/state/state.py
index 132f6c8a8ad34c6ed359af51bff7d699541bc030..68448e4b5f6286c83cbdb6186a5c4c4e94381855 100644
--- a/scripts/SANS/sans/state/state.py
+++ b/scripts/SANS/sans/state/state.py
@@ -5,53 +5,41 @@
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
 """ Defines the main State object."""
-
-# pylint: disable=too-few-public-methods
-
 from __future__ import (absolute_import, division, print_function)
-import json
-import pickle
-import inspect
+
 import copy
-from sans.common.enums import SANSFacility
-from sans.state.state_base import (StateBase, TypedParameter,
-                                   rename_descriptor_names, validator_sub_state)
-from sans.state.data import StateData
-from sans.state.move import StateMove
-from sans.state.reduction_mode import StateReductionMode
-from sans.state.slice_event import StateSliceEvent
-from sans.state.mask import StateMask
-from sans.state.wavelength import StateWavelength
-from sans.state.save import StateSave
-from sans.state.adjustment import StateAdjustment
-from sans.state.scale import StateScale
-from sans.state.convert_to_q import StateConvertToQ
-from sans.state.automatic_setters import (automatic_setters)
+import json
+
+from six import with_metaclass
 
+from sans.common.enums import SANSFacility
+from sans.state.JsonSerializable import JsonSerializable
 # Note that the compatibility state is not part of the new reduction chain, but allows us to accurately compare
 # results obtained via the old and new reduction chain
-from sans.state.compatibility import (StateCompatibility, get_compatibility_builder)
+from sans.state.automatic_setters import automatic_setters
+from sans.state.compatibility import get_compatibility_builder
 
 
 # ----------------------------------------------------------------------------------------------------------------------
 # State
 # ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class State(StateBase):
-    data = TypedParameter(StateData, validator_sub_state)
-    move = TypedParameter(StateMove, validator_sub_state)
-    reduction = TypedParameter(StateReductionMode, validator_sub_state)
-    slice = TypedParameter(StateSliceEvent, validator_sub_state)
-    mask = TypedParameter(StateMask, validator_sub_state)
-    wavelength = TypedParameter(StateWavelength, validator_sub_state)
-    save = TypedParameter(StateSave, validator_sub_state)
-    scale = TypedParameter(StateScale, validator_sub_state)
-    adjustment = TypedParameter(StateAdjustment, validator_sub_state)
-    convert_to_q = TypedParameter(StateConvertToQ, validator_sub_state)
-    compatibility = TypedParameter(StateCompatibility, validator_sub_state)
+
+class State(with_metaclass(JsonSerializable)):
 
     def __init__(self):
+
         super(State, self).__init__()
+        self.data = None  # : StateData
+        self.move = None  # : StateMove
+        self.reduction = None  # : StateReductionMode
+        self.slice = None  # : StateSliceEvent
+        self.mask = None  # : StateMask
+        self.wavelength = None  # : StateWavelength
+        self.save = None  # : StateSave
+        self.scale = None  # : StateScale
+        self.adjustment = None  # : StateAdjustment
+        self.convert_to_q = None  # : StateConvertToQ
+        self.compatibility = None  # : StateCompatibility
 
     def validate(self):
         is_invalid = dict()
@@ -84,25 +72,9 @@ class State(StateBase):
                 self.compatibility = get_compatibility_builder(self.data).build()
 
         if is_invalid:
-            raise ValueError("State: There is an issue with your in put. See: {0}".format(json.dumps(is_invalid)))
-
-        # Check the attributes themselves
-        is_invalid = {}
-        for descriptor_name, descriptor_object in inspect.getmembers(type(self)):
-            if inspect.isdatadescriptor(descriptor_object) and isinstance(descriptor_object, TypedParameter):
-                try:
-                    attr = getattr(self, descriptor_name)
-                    attr.validate()
-                except ValueError as err:
-                    is_invalid.update({descriptor_name: pickle.dumps(str(err))})
+            raise ValueError("State: There is an issue with your input. See: {0}".format(json.dumps(is_invalid)))
 
-        if is_invalid:
-            raise ValueError("State: There is an issue with your in put. See: {0}".format(json.dumps(is_invalid)))
 
-
-# ----------------------------------------------------------------------------------------------------------------------
-# Builder
-# ----------------------------------------------------------------------------------------------------------------------
 class StateBuilder(object):
     @automatic_setters(State)
     def __init__(self):
diff --git a/scripts/SANS/sans/state/state_base.py b/scripts/SANS/sans/state/state_base.py
deleted file mode 100644
index d4d01039b740727ac6c3f13e47e5dcd017149ff0..0000000000000000000000000000000000000000
--- a/scripts/SANS/sans/state/state_base.py
+++ /dev/null
@@ -1,601 +0,0 @@
-# Mantid Repository : https://github.com/mantidproject/mantid
-#
-# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-#     NScD Oak Ridge National Laboratory, European Spallation Source
-#     & Institut Laue - Langevin
-# SPDX - License - Identifier: GPL - 3.0 +
-# pylint: disable=too-few-public-methods, invalid-name
-
-""" Fundamental classes and Descriptors for the State mechanism."""
-from __future__ import (absolute_import, division, print_function)
-
-import copy
-import inspect
-from abc import (ABCMeta, abstractmethod)
-from functools import (partial)
-from importlib import import_module
-
-from six import string_types, with_metaclass
-
-from mantid.kernel import (PropertyManager, std_vector_dbl, std_vector_str, std_vector_int, std_vector_long)
-from mantid.py3compat import Enum
-
-
-# ---------------------------------------------------------------
-# Validator functions
-# ---------------------------------------------------------------
-
-
-def is_not_none(value):
-    return value is not None
-
-
-def is_positive(value):
-    return value >= 0
-
-
-def is_positive_or_none(value):
-    return value is None or value >= 0
-
-
-def all_list_elements_are_of_specific_type_and_not_empty(value, comparison_type,
-                                                         additional_comparison=lambda x: True, type_check=isinstance):
-    """
-    Ensures that all elements of a list are of a specific type and that the list is not empty
-
-    :param value: the list to check
-    :param comparison_type: the expected type of the elements of the list.
-    :param additional_comparison: additional comparison lambda.
-    :param type_check: the method which performs type checking.
-    :return: True if the list is not empty and all types are as expected, else False.
-    """
-    is_of_type = True
-    for element in value:
-        # Perform type check
-        if not type_check(element, comparison_type):
-            is_of_type = False
-        # Perform additional check
-        if not additional_comparison(element):
-            is_of_type = False
-
-    if not value:
-        is_of_type = False
-    return is_of_type
-
-
-def all_list_elements_are_of_instance_type_and_not_empty(value, comparison_type, additional_comparison=lambda x: True):
-    """
-    Ensures that all elements of a list are of a certain INSTANCE type and that the list is not empty.
-    """
-    return all_list_elements_are_of_specific_type_and_not_empty(value=value, comparison_type=comparison_type,
-                                                                additional_comparison=additional_comparison,
-                                                                type_check=isinstance)
-
-
-def all_list_elements_are_of_class_type_and_not_empty(value, comparison_type, additional_comparison=lambda x: True):
-    """
-    Ensures that all elements of a list are of a certain INSTANCE type and that the list is not empty.
-    """
-    return all_list_elements_are_of_specific_type_and_not_empty(value=value, comparison_type=comparison_type,
-                                                                additional_comparison=additional_comparison,
-                                                                type_check=issubclass)
-
-
-def all_list_elements_are_float_and_not_empty(value):
-    typed_comparison = partial(all_list_elements_are_of_instance_type_and_not_empty, comparison_type=float)
-    return typed_comparison(value)
-
-
-def all_list_elements_are_float_and_positive_and_not_empty(value):
-    typed_comparison = partial(all_list_elements_are_of_instance_type_and_not_empty, comparison_type=float,
-                               additional_comparison=lambda x: x >= 0)
-    return typed_comparison(value)
-
-
-def all_list_elements_are_string_and_not_empty(value):
-    typed_comparison = partial(all_list_elements_are_of_instance_type_and_not_empty, comparison_type=str)
-    return typed_comparison(value)
-
-
-def all_list_elements_are_int_and_not_empty(value):
-    typed_comparison = partial(all_list_elements_are_of_instance_type_and_not_empty, comparison_type=int)
-    return typed_comparison(value)
-
-
-def all_list_elements_are_int_and_positive_and_not_empty(value):
-    typed_comparison = partial(all_list_elements_are_of_instance_type_and_not_empty, comparison_type=int,
-                               additional_comparison=lambda x: x >= 0)
-    return typed_comparison(value)
-
-
-def validator_sub_state(sub_state):
-    is_valid = True
-    try:
-        sub_state.validate()
-    except ValueError:
-        is_valid = False
-    return is_valid
-
-
-# -------------------------------------------------------
-# Parameters
-# -------------------------------------------------------
-class TypedParameter(object):
-    """
-    The TypedParameter descriptor allows the user to store/handle a type-checked value with an additional
-    validator option, e.g. one can restrict the held parameter to be only a positive value.
-    """
-    __counter = 0
-
-    def __init__(self, parameter_type, validator=lambda x: True):
-        cls = self.__class__
-        prefix = cls.__name__
-        # pylint: disable=protected-access
-        index = cls.__counter
-        cls.__counter += 1
-        # Name which is used to store value in the instance. This will be unique and not accessible via the standard
-        # attribute access, since the developer/user cannot apply the hash symbol in their code (it is valid though
-        # when writing into the __dict__). Note that the name which we generate here will be altered (via a
-        # class decorator) in the classes which actually use the TypedParameter descriptor, to make it more readable.
-        self.name = '_{0}#{1}'.format(prefix, index)
-        self.parameter_type = parameter_type
-        self.value = None
-        self.validator = validator
-
-    def __get__(self, instance, owner):
-        if instance is None:
-            return self
-        else:
-            if hasattr(instance, self.name):
-                return getattr(instance, self.name)
-            else:
-                return None
-
-    def __set__(self, instance, value):
-        # Perform a type check
-        self._type_check(value)
-        if self.validator(value):
-            # The descriptor should be holding onto its own data and return a deepcopy of the data.
-            copied_value = copy.deepcopy(value)
-            setattr(instance, self.name, copied_value)
-        else:
-            raise ValueError("Trying to set {0} with an invalid value of {1}".format(self.name, str(value)))
-
-    def __delete__(self):
-        raise AttributeError("Cannot delete the attribute {0}".format(self.name))
-
-    def _type_check(self, value):
-        if not isinstance(value, self.parameter_type):
-            raise TypeError("Trying to set {0} which expects a value of type {1}."
-                            " Got a value of {2} which is of type: {3}".format(self.name, str(self.parameter_type),
-                                                                               str(value), str(type(value))))
-
-
-# ---------------------------------------------------
-# Various standard cases of the TypedParameter
-# ---------------------------------------------------
-class StringParameter(TypedParameter):
-    def __init__(self):
-        super(StringParameter, self).__init__(str, is_not_none)
-
-
-class BoolParameter(TypedParameter):
-    def __init__(self):
-        super(BoolParameter, self).__init__(bool, is_not_none)
-
-
-class FloatParameter(TypedParameter):
-    def __init__(self):
-        super(FloatParameter, self).__init__(float, is_not_none)
-
-
-class PositiveFloatParameter(TypedParameter):
-    def __init__(self):
-        super(PositiveFloatParameter, self).__init__(float, is_positive)
-
-
-class PositiveIntegerParameter(TypedParameter):
-    def __init__(self):
-        super(PositiveIntegerParameter, self).__init__(int, is_positive)
-
-
-class DictParameter(TypedParameter):
-    def __init__(self):
-        super(DictParameter, self).__init__(dict, is_not_none)
-
-
-class DictFloatsParameter(TypedParameter):
-    def __init__(self):
-        super(DictFloatsParameter, self).__init__(dict, is_not_none)
-
-    def _type_check(self, value):
-        if not all(isinstance(val, float) for val in value.values()):
-            raise TypeError("Trying to set {0} which expects a value of type {1}."
-                            " Got a value of {2} which is of type: {3}".format(self.name, self.parameter_type,
-                                                                               value, type(value)))
-
-
-class FloatWithNoneParameter(TypedParameter):
-    def __init__(self):
-        super(FloatWithNoneParameter, self).__init__(float)
-
-    def _type_check(self, value):
-        if not isinstance(value, self.parameter_type) and value is not None:
-            raise TypeError("Trying to set {0} which expects a value of type {1}."
-                            " Got a value of {2} which is of type: {3}".format(self.name, self.parameter_type,
-                                                                               value, type(value)))
-
-
-class StringWithNoneParameter(TypedParameter):
-    def __init__(self):
-        super(StringWithNoneParameter, self).__init__(str)
-
-    def _type_check(self, value):
-        if not isinstance(value, self.parameter_type) and value is not None:
-            raise TypeError("Trying to set {0} which expects a value of type {1}."
-                            " Got a value of {2} which is of type: {3}".format(self.name, self.parameter_type,
-                                                                               value, type(value)))
-
-
-class PositiveFloatWithNoneParameter(TypedParameter):
-    def __init__(self):
-        super(PositiveFloatWithNoneParameter, self).__init__(float, is_positive_or_none)
-
-    def _type_check(self, value):
-        if not isinstance(value, self.parameter_type) and value is not None:
-            raise TypeError("Trying to set {0} which expects a value of type {1}."
-                            " Got a value of {2} which is of type: {3}".format(self.name, self.parameter_type,
-                                                                               value, type(value)))
-
-
-class FloatListParameter(TypedParameter):
-    def __init__(self):
-        super(FloatListParameter, self).__init__(list)
-
-    def _type_check(self, value):
-        if not isinstance(value, self.parameter_type) or not all_list_elements_are_float_and_not_empty(value):
-            raise TypeError("Trying to set {0} which expects a value of type {1}."
-                            " Got a value of {2} which is of type: {3}".format(self.name, self.parameter_type,
-                                                                               value, type(value)))
-
-
-class PositiveFloatListParameter(TypedParameter):
-    def __init__(self):
-        super(PositiveFloatListParameter, self).__init__(list, all_list_elements_are_float_and_positive_and_not_empty)
-
-    def _type_check(self, value):
-        if not isinstance(value, self.parameter_type) or not all_list_elements_are_float_and_not_empty(value):
-            raise TypeError("Trying to set {0} which expects a value of type {1}."
-                            " Got a value of {2} which is of type: {3}".format(self.name, self.parameter_type,
-                                                                               value, type(value)))
-
-
-class StringListParameter(TypedParameter):
-    def __init__(self):
-        super(StringListParameter, self).__init__(list, all_list_elements_are_string_and_not_empty)
-
-    def _type_check(self, value):
-        if not isinstance(value, self.parameter_type) or not all_list_elements_are_string_and_not_empty(value):
-            raise TypeError("Trying to set {0} which expects a value of type {1}."
-                            " Got a value of {2} which is of type: {3}".format(self.name, self.parameter_type,
-                                                                               value, type(value)))
-
-
-class PositiveIntegerListParameter(TypedParameter):
-    def __init__(self):
-        super(PositiveIntegerListParameter, self).__init__(list,
-                                                           all_list_elements_are_int_and_positive_and_not_empty)
-
-    def _type_check(self, value):
-        if not isinstance(value, self.parameter_type) or not all_list_elements_are_int_and_not_empty(value):
-            raise TypeError("Trying to set {0} which expects a value of type {1}."
-                            " Got a value of {2} which is of type: {3}".format(self.name, self.parameter_type,
-                                                                               value, type(value)))
-
-
-# ------------------------------------------------
-# StateBase
-# ------------------------------------------------
-class StateBase(with_metaclass(ABCMeta, object)):
-    """ The fundamental base of the SANS State"""
-
-    @property
-    def property_manager(self):
-        return convert_state_to_dict(self)
-
-    @property_manager.setter
-    def property_manager(self, value):
-        set_state_from_property_manager(self, value)
-
-    @abstractmethod
-    def validate(self):
-        pass
-
-    def convert_to_dict(self):
-        return convert_state_to_dict(self)
-
-
-def rename_descriptor_names(cls):
-    """
-    Class decorator which changes the names of TypedParameters in a class instance in order to make it more readable.
-
-    This is especially helpful for debugging. And also in order to find attributes in the dictionaries.
-    :param cls: The class with the TypedParameters
-    :return: The class with the TypedParameters
-    """
-    for attribute_name, attribute_value in list(cls.__dict__.items()):
-        if isinstance(attribute_value, TypedParameter):
-            attribute_value.name = '_{0}#{1}'.format(type(attribute_value).__name__, attribute_name)
-    return cls
-
-
-# ------------------------------------------------
-# Serialization of the State
-# ------------------------------------------------
-# Serialization of the state object is currently done via generating a dict object. Reversely, we can generate a
-# State object from a property manager object, not a dict object. This quirk results from the way Mantid
-# treats property manager inputs and outputs (it reads in dicts and converts them to property manager objects).
-# We might have to live with that for now.
-#
-# During serialization we place identifier tags into the serialized object, e.g. we add a specifier if the item
-# is a State type at all and if so which state it is.
-ENUM_TYPE_TAG = "EnumTag#"
-
-INT_TAG = "int"
-
-STATE_NAME = "state_name"
-STATE_MODULE = "state_module"
-SEPARATOR_SERIAL = "#"
-MODULE = "__module__"
-
-
-def is_state(property_manager):
-    return property_manager.existsProperty(STATE_NAME) and property_manager.existsProperty(STATE_MODULE)
-
-
-def is_float_vector(value):
-    return isinstance(value, std_vector_dbl)
-
-
-def is_string_vector(value):
-    return isinstance(value, std_vector_str)
-
-
-def is_int_vector(value):
-    return isinstance(value, std_vector_int) or isinstance(value, std_vector_long)
-
-
-def get_module_and_class_name(instance):
-    if inspect.isclass(instance):
-        module_name, class_name = str(instance.__dict__[MODULE]), str(instance.__name__)
-    else:
-        module_name, class_name = str(type(instance).__dict__[MODULE]), str(type(instance).__name__)
-    return module_name, class_name
-
-
-def provide_class_from_module_and_class_name(module_name, class_name):
-    module = import_module(module_name)
-    return getattr(module, class_name)
-
-
-def provide_class(instance):
-    module_name = instance.getProperty(STATE_MODULE).value
-    class_name = instance.getProperty(STATE_NAME).value
-    return provide_class_from_module_and_class_name(module_name, class_name)
-
-
-def is_enum_type_parameter(value):
-    return isinstance(value, string_types) and ENUM_TYPE_TAG in value
-
-
-def is_enum_list_parameter(value):
-    if isinstance(value, string_types):
-        return False
-
-    try:
-        return all(ENUM_TYPE_TAG in s for s in value)
-    except TypeError:
-        return False
-
-
-def get_module_and_class_name_from_encoded_string(encoder, value):
-    without_encoder = value.replace(encoder, "")
-    return without_encoder.split(SEPARATOR_SERIAL)
-
-
-def create_sub_state(value):
-    # We are dealing with a sub state. We first have to create it and then populate it
-    sub_state_class = provide_class(value)
-    # Create the sub state, populate it and set it on the super state
-    sub_state = sub_state_class()
-    sub_state.property_manager = value
-    return sub_state
-
-
-def get_descriptor_values(instance):
-    # Get all user defined attributes
-    member_variables = inspect.getmembers(type(instance), lambda x: not(inspect.isroutine(x)))
-    # Remove anything starting with a '_' or '__' - i.e. private attributes such as weak ref
-    member_variables = [item for item in member_variables if not item[0].startswith('_')]
-
-    # Property manager is a fake property that wraps the serializing method (i.e. this)
-    # so trying to pack it causes inf recursion
-    member_variables = [item for item in member_variables if "property_manager" not in item[0]]
-
-    # We only need names
-    member_variables = [item[0] for item in member_variables]
-
-    # Get the descriptor values from the instance
-    descriptor_values = {}
-    for key in member_variables:
-        if hasattr(instance, key):
-            value = getattr(instance, key)
-            if value is not None:
-                descriptor_values.update({key: value})
-
-    return descriptor_values
-
-
-def get_class_descriptor_types(instance):
-    # Get all descriptor names which are TypedParameter of instance's type
-    descriptors = {}
-    for descriptor_name, descriptor_object in inspect.getmembers(type(instance)):
-        if inspect.isdatadescriptor(descriptor_object) and isinstance(descriptor_object, TypedParameter):
-            descriptors.update({descriptor_name: type(descriptor_object)})
-    return descriptors
-
-
-def convert_state_to_dict(instance):
-    """
-    Converts the state object to a dictionary.
-
-    :param instance: the instance which is to be converted
-    :return: a serialized state object in the form of a dict
-    """
-    attribute = get_descriptor_values(instance)
-    # Add the descriptors to a dict
-    state_dict = dict()
-
-    # Don't do anything if primitive type that Mantid can serialize
-    primative_types = (int, str, bool, float, TypedParameter)
-
-    for attr_name, attr_val in attribute.items():
-        if isinstance(attr_val, StateBase):
-            # If the value is a SANSBaseState then create a dict from it
-            attr_val = attr_val.property_manager
-        elif isinstance(attr_val, dict):
-            attr_val = serialize_dict(attr_val)
-        elif isinstance(attr_val, Enum) or isinstance(attr_val, list) and all(isinstance(x, Enum) for x in attr_val):
-            attr_val = serialize_enum(attr_val)
-        elif isinstance(attr_val, primative_types) \
-                or isinstance(attr_val, list) and all(isinstance(x, primative_types) for x in attr_val):
-            pass  # A primative type or list of primitives don't need anything special
-        else:
-            raise ValueError("Cannot serialize {0}".format(attr_val))
-
-        state_dict.update({attr_name: attr_val})
-    # Add information about the current state object, such as in which module it lives and what its name is
-    module_name, class_name = get_module_and_class_name(instance)
-    state_dict.update({STATE_MODULE: module_name})
-    state_dict.update({STATE_NAME: class_name})
-    return state_dict
-
-
-def serialize_dict(value):
-    # If we have a dict, then we need to watch out since a value in the dict might be a State
-    sub_dictionary = {}
-    for key_sub, val_sub in list(value.items()):
-        # We have to handle the key being an enum too
-        if isinstance(key_sub, Enum):
-            key_sub = serialize_enum(key_sub)
-
-        if isinstance(val_sub, StateBase):
-            val_sub = val_sub.property_manager
-        elif isinstance(val_sub, Enum):
-            val_sub = serialize_enum(val_sub)
-
-        sub_dictionary.update({key_sub: val_sub})
-
-    return sub_dictionary
-
-
-def set_state_from_property_manager(instance, property_manager):
-    """
-    Set the State object from the information stored on a property manager object. This is the deserialization step.
-
-    :param instance: the instance which is to be set with a values of the property manager
-    :param property_manager: the property manager with the stored setting
-    """
-
-    def _set_element(inst, k_element, v_element):
-        if k_element != STATE_NAME and k_element != STATE_MODULE:
-            setattr(inst, k_element, v_element)
-
-    keys = list(property_manager.keys())
-    for key in keys:
-        value = property_manager.getProperty(key).value
-        # There are some special scenarios that need to be considered
-        # 1. ParameterManager 1: This indicates (most often) that we are dealing with a new state -> create it and
-        #                      apply recursion
-        # 2. ParameterManager 2: In some cases the ParameterManager object is actually a map rather than a state ->
-        #                         populate the state
-
-        if type(value) is PropertyManager and is_state(value):
-            sub_state = create_sub_state(value)
-            setattr(instance, key, sub_state)
-        elif type(value) is PropertyManager:
-            deserialize_dict(instance, key, value)
-        elif is_enum_type_parameter(value) or is_enum_list_parameter(value):
-            enum_type_parameter = deserialize_enum(value)
-            _set_element(instance, key, enum_type_parameter)
-        elif is_float_vector(value):
-            float_list_value = list(value)
-            _set_element(instance, key, float_list_value)
-        elif is_string_vector(value):
-            string_list_value = list(value)
-            _set_element(instance, key, string_list_value)
-        elif is_int_vector(value):
-            int_list_value = list(value)
-            _set_element(instance, key, int_list_value)
-        else:
-            _set_element(instance, key, value)
-
-
-def deserialize_dict(instance, key, value):
-    # We must be dealing with an actual dict descriptor
-    sub_dict_keys = list(value.keys())
-    dict_element = {}
-    # We need to watch out if a value of the dictionary is a sub state
-    for sub_dict_key in sub_dict_keys:
-        sub_dict_value = value.getProperty(sub_dict_key).value
-        if type(sub_dict_value) == PropertyManager and is_state(sub_dict_value):
-            sub_state = create_sub_state(sub_dict_value)
-            sub_dict_value_to_insert = sub_state
-        elif is_enum_type_parameter(sub_dict_value):
-            sub_dict_value_to_insert = deserialize_enum(sub_dict_value)
-        else:
-            sub_dict_value_to_insert = sub_dict_value
-
-        if is_enum_type_parameter(sub_dict_key):
-            sub_dict_key = deserialize_enum(sub_dict_key)
-
-        dict_element.update({sub_dict_key: sub_dict_value_to_insert})
-    setattr(instance, key, dict_element)
-
-
-def serialize_enum(value):
-    to_parse = value if isinstance(value, list) else [value]
-    serialized = []
-    for val in to_parse:
-        assert (isinstance(val, Enum))
-        module_name, class_name = get_module_and_class_name(val)
-        selected_val = val.value
-
-        # Some devs use int for enums too so handle that
-        if isinstance(selected_val, int):
-            selected_val = INT_TAG + str(selected_val)
-
-        serialized.append(ENUM_TYPE_TAG + module_name + SEPARATOR_SERIAL + class_name + SEPARATOR_SERIAL + selected_val)
-
-    return serialized[0] if len(serialized) == 1 else serialized
-
-
-def deserialize_enum(value):
-    # Mantid returns a std::vec type which needs to decay to a list
-    to_parse = [value] if isinstance(value, string_types) else [i for i in value]
-    parsed = []
-
-    for serialized_str in to_parse:
-        module_name, class_name, selection = get_module_and_class_name_from_encoded_string(ENUM_TYPE_TAG,
-                                                                                           serialized_str)
-        enum_class = provide_class_from_module_and_class_name(module_name, class_name)
-
-        selection = int(selection.replace(INT_TAG, '')) if INT_TAG in selection else selection
-        parsed_val = enum_class(selection)
-        parsed.append(parsed_val)
-
-    return parsed[0] if len(parsed) == 1 else parsed
-
-
-def create_deserialized_sans_state_from_property_manager(property_manager):
-    return create_sub_state(property_manager)
diff --git a/scripts/SANS/sans/state/wavelength.py b/scripts/SANS/sans/state/wavelength.py
index 1cbda44331e73b7c0cf599960f164ff552d1e94d..f10df0f0592197bdb6fe6b91797491772ccc74b8 100644
--- a/scripts/SANS/sans/state/wavelength.py
+++ b/scripts/SANS/sans/state/wavelength.py
@@ -10,27 +10,22 @@ from __future__ import (absolute_import, division, print_function)
 import json
 import copy
 
-from sans.state.state_base import (StateBase, PositiveFloatParameter, rename_descriptor_names,
-                                   PositiveFloatListParameter)
+from six import with_metaclass
+
+from sans.state.JsonSerializable import JsonSerializable
 from sans.common.enums import (RebinType, RangeStepType, SANSFacility)
+from sans.state.automatic_setters import automatic_setters
 from sans.state.state_functions import (is_not_none_and_first_larger_than_second, one_is_none, validation_message)
-from sans.state.automatic_setters import (automatic_setters)
-
 
-# ----------------------------------------------------------------------------------------------------------------------
-# State
-# ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateWavelength(StateBase):
-    rebin_type = RebinType.REBIN
-    wavelength_low = PositiveFloatListParameter()
-    wavelength_high = PositiveFloatListParameter()
-    wavelength_step = PositiveFloatParameter()
-    wavelength_step_type = RangeStepType.NOT_SET
 
+class StateWavelength(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateWavelength, self).__init__()
         self.rebin_type = RebinType.REBIN
+        self.wavelength_low = None  # : List[Float] (Positive)
+        self.wavelength_high = None  # : List[Float] (Positive)
+        self.wavelength_step = None  # : Float (Positive)
+        self.wavelength_step_type = RangeStepType.NOT_SET
 
     def validate(self):
         is_invalid = dict()
diff --git a/scripts/SANS/sans/state/wavelength_and_pixel_adjustment.py b/scripts/SANS/sans/state/wavelength_and_pixel_adjustment.py
index 75eb298ce4d473c91994b68e0c5b7f2eb6774805..375e228660b1a5cc9db4547a93708a633f0630b0 100644
--- a/scripts/SANS/sans/state/wavelength_and_pixel_adjustment.py
+++ b/scripts/SANS/sans/state/wavelength_and_pixel_adjustment.py
@@ -11,23 +11,20 @@
 from __future__ import (absolute_import, division, print_function)
 import json
 import copy
-from sans.state.state_base import (StateBase, rename_descriptor_names, StringParameter,
-                                   PositiveFloatParameter, DictParameter, PositiveFloatListParameter)
+
+from six import with_metaclass
+
+from sans.state.JsonSerializable import JsonSerializable
+from sans.state.automatic_setters import automatic_setters
 from sans.state.state_functions import (is_not_none_and_first_larger_than_second, one_is_none, validation_message)
 from sans.common.enums import (RangeStepType, DetectorType, SANSFacility)
-from sans.state.automatic_setters import (automatic_setters)
-
 
-# ----------------------------------------------------------------------------------------------------------------------
-# State
-# ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateAdjustmentFiles(StateBase):
-    pixel_adjustment_file = StringParameter()
-    wavelength_adjustment_file = StringParameter()
 
+class StateAdjustmentFiles(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateAdjustmentFiles, self).__init__()
+        self.pixel_adjustment_file = None  # : Str()
+        self.wavelength_adjustment_file = None  # : Str()
 
     def validate(self):
         is_invalid = {}
@@ -39,19 +36,16 @@ class StateAdjustmentFiles(StateBase):
                              "Please see: {0}".format(json.dumps(is_invalid)))
 
 
-@rename_descriptor_names
-class StateWavelengthAndPixelAdjustment(StateBase):
-    wavelength_low = PositiveFloatListParameter()
-    wavelength_high = PositiveFloatListParameter()
-    wavelength_step = PositiveFloatParameter()
-    wavelength_step_type = RangeStepType.NOT_SET
-
-    adjustment_files = DictParameter()
-
-    idf_path = StringParameter()
-
+class StateWavelengthAndPixelAdjustment(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(StateWavelengthAndPixelAdjustment, self).__init__()
+        self.wavelength_low = None  # : List[Float] (Positive)
+        self.wavelength_high = None  # : List[Float] (Positive)
+        self.wavelength_step = None  # : Float (Positive)
+        self.wavelength_step_type = RangeStepType.NOT_SET
+
+        self.idf_path = None  # : Str()
+
         self.adjustment_files = {DetectorType.LAB.value: StateAdjustmentFiles(),
                                  DetectorType.HAB.value: StateAdjustmentFiles()}
 
diff --git a/scripts/SANS/sans/test_helper/mock_objects.py b/scripts/SANS/sans/test_helper/mock_objects.py
index f23babbb31d406e2107854a2e065cfd7bfefa6c5..c5871b261f806c017f38a7fe4856e66fc8b81e4e 100644
--- a/scripts/SANS/sans/test_helper/mock_objects.py
+++ b/scripts/SANS/sans/test_helper/mock_objects.py
@@ -8,9 +8,12 @@ from __future__ import (absolute_import)
 
 from functools import (partial)
 
+from six import with_metaclass
+
 from mantid.py3compat import mock
 from sans.gui_logic.presenter.run_tab_presenter import RunTabPresenter
 from sans.common.enums import (RangeStepType, OutputMode, SANSFacility, SANSInstrument)
+from sans.state.JsonSerializable import JsonSerializable
 from sans.test_helper.test_director import TestDirector
 from ui.sans_isis.sans_data_processor_gui import SANSDataProcessorGui
 from ui.sans_isis.settings_diagnostic_tab import SettingsDiagnosticTab
@@ -237,11 +240,10 @@ def create_mock_view2(user_file_path, batch_file_path=None):
     return view
 
 
-class FakeState(object):
-    dummy_state = "dummy_state"
-
+class FakeState(with_metaclass(JsonSerializable)):
     def __init__(self):
         super(FakeState, self).__init__()
+        self.dummy_state = "dummy_state"
 
     @property
     def property_manager(self):
diff --git a/scripts/SANS/sans/test_helper/test_director.py b/scripts/SANS/sans/test_helper/test_director.py
index 44266e0661f34bb9e83af83dc70999859161f31f..9f58258b073219b857009d183f3c8898986c6beb 100644
--- a/scripts/SANS/sans/test_helper/test_director.py
+++ b/scripts/SANS/sans/test_helper/test_director.py
@@ -6,12 +6,12 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 """ A Test director """
 from __future__ import (absolute_import, division, print_function)
-from sans.state.state import get_state_builder
 from sans.state.data import get_data_builder
 from sans.state.move import get_move_builder
 from sans.state.reduction_mode import get_reduction_mode_builder
 from sans.state.slice_event import get_slice_event_builder
 from sans.state.mask import get_mask_builder
+from sans.state.state import get_state_builder
 from sans.state.wavelength import get_wavelength_builder
 from sans.state.save import get_save_builder
 from sans.state.normalize_to_monitor import get_normalize_to_monitor_builder
@@ -22,7 +22,8 @@ from sans.state.adjustment import get_adjustment_builder
 from sans.state.convert_to_q import get_convert_to_q_builder
 
 from sans.common.enums import (SANSFacility, ReductionMode, ReductionDimensionality,
-                               FitModeForMerge, RebinType, RangeStepType, SaveType, FitType, SampleShape, SANSInstrument)
+                               FitModeForMerge, RebinType, RangeStepType, SaveType, FitType, SampleShape,
+                               SANSInstrument)
 from sans.test_helper.file_information_mock import SANSFileInformationMock
 
 
diff --git a/scripts/SANS/sans/user_file/state_director.py b/scripts/SANS/sans/user_file/state_director.py
index 635189b63010618f620dfa859fec02d8b53eb573..caafa10403d49ec62b786be42a234273c00db590 100644
--- a/scripts/SANS/sans/user_file/state_director.py
+++ b/scripts/SANS/sans/user_file/state_director.py
@@ -11,11 +11,12 @@ from sans.common.enums import (DetectorType, FitModeForMerge, RebinType, DataTyp
 from sans.common.file_information import find_full_file_path
 from sans.common.general_functions import (get_ranges_for_rebin_setting, get_ranges_for_rebin_array,
                                            get_ranges_from_event_slice_setting)
+from sans.state.automatic_setters import set_up_setter_forwarding_from_director_to_builder
 from sans.user_file.user_file_reader import UserFileReader
 from sans.user_file.settings_tags import (DetectorId, BackId, LimitsId, simple_range, complex_range, MaskId,
                                           rebin_string_values, SampleId, SetId, TransId, TubeCalibrationFileId,
                                           QResolutionId, FitId, MonId, GravityId, OtherId)
-from sans.state.automatic_setters import set_up_setter_forwarding_from_director_to_builder
+
 from sans.state.state import get_state_builder
 from sans.state.mask import get_mask_builder
 from sans.state.move import get_move_builder
diff --git a/scripts/test/SANS/algorithm_detail/centre_finder_new_test.py b/scripts/test/SANS/algorithm_detail/centre_finder_new_test.py
index 796a9db5d1c48b6e2d996c0905a4c5f76c7b2a62..2de8f332037506a9aa0465c79b2d278cc7aa16f2 100644
--- a/scripts/test/SANS/algorithm_detail/centre_finder_new_test.py
+++ b/scripts/test/SANS/algorithm_detail/centre_finder_new_test.py
@@ -11,11 +11,13 @@ import unittest
 from mantid.py3compat import mock
 from sans.algorithm_detail.centre_finder_new import centre_finder_new, centre_finder_mass
 from sans.common.enums import (SANSDataType, FindDirectionEnum, DetectorType)
+from sans.test_helper.test_director import TestDirector
 
 
 class CentreFinderNewTest(unittest.TestCase):
     def setUp(self):
-        self.state = mock.MagicMock()
+        state_builder = TestDirector()
+        self.state = state_builder.construct()
 
     @mock.patch('sans.algorithm_detail.centre_finder_new.provide_loaded_data')
     @mock.patch('sans.algorithm_detail.centre_finder_new.create_managed_non_child_algorithm')
diff --git a/scripts/test/SANS/algorithm_detail/mask_sans_workspace_test.py b/scripts/test/SANS/algorithm_detail/mask_sans_workspace_test.py
index bf926e44a8d5851a461364ea1ee4d3d8d0521550..bf4bda666cb635f095236a2efb8c3239d4f841d3 100644
--- a/scripts/test/SANS/algorithm_detail/mask_sans_workspace_test.py
+++ b/scripts/test/SANS/algorithm_detail/mask_sans_workspace_test.py
@@ -13,6 +13,7 @@ from mantid.simpleapi import CloneWorkspace, DeleteWorkspace, Load, LoadEmptyIns
 from sans.algorithm_detail.mask_sans_workspace import mask_workspace
 from sans.common.enums import SANSFacility
 from sans.common.file_information import SANSFileInformationFactory
+from sans.state.Serializer import Serializer
 from sans.state.data import get_data_builder
 from sans.state.mask import get_mask_builder
 from sans.state.move import get_move_builder
@@ -294,9 +295,6 @@ class MaskSansWorkspaceTest(unittest.TestCase):
         bin_mask_general_start = [30000., 67000.]
         bin_mask_general_stop = [35000., 75000.]
 
-        # bin_mask_start = [14000]
-        # bin_mask_stop = FloatListParameter()
-
         mask_builder.set_bin_mask_general_start(bin_mask_general_start)
         mask_builder.set_bin_mask_general_stop(bin_mask_general_stop)
 
@@ -409,8 +407,9 @@ class MaskSansWorkspaceTest(unittest.TestCase):
         test_director.set_states(data_state=data_info, mask_state=mask_info)
         state = test_director.construct()
 
-        returned_data = SANSLoad(SANSState=state.property_manager, SampleScatterWorkspace="mask_sans_ws",
+        returned_data = SANSLoad(SANSState=Serializer.to_json(state), SampleScatterWorkspace="mask_sans_ws",
                                  SampleScatterMonitorWorkspace="dummy")
+
         workspace = returned_data[0]
         DeleteWorkspace(returned_data[1])
 
diff --git a/scripts/test/SANS/command_interface/command_interface_state_director_test.py b/scripts/test/SANS/command_interface/command_interface_state_director_test.py
index 7704e3b2419d501c0d4e8cdc50a5041129e8a3f3..00ef1c5ec64086d2afafadd2a7fb783a8b088187 100644
--- a/scripts/test/SANS/command_interface/command_interface_state_director_test.py
+++ b/scripts/test/SANS/command_interface/command_interface_state_director_test.py
@@ -141,14 +141,14 @@ class CommandInterfaceStateDirectorTest(unittest.TestCase):
         self.assertEqual(state.move.detectors[DetectorType.HAB.value].sample_centre_pos1,  12.4/1000.)
         self.assertTrue(state.move.detectors[DetectorType.HAB.value].sample_centre_pos2
                         == 23.54/1000.)
-        self.assertTrue(state.adjustment.calculate_transmission.fit[DataType.CAN].fit_type
+        self.assertTrue(state.adjustment.calculate_transmission.fit[DataType.CAN.value].fit_type
                         is FitType.LOGARITHMIC)
-        self.assertTrue(state.adjustment.calculate_transmission.fit[DataType.CAN].polynomial_order
+        self.assertTrue(state.adjustment.calculate_transmission.fit[DataType.CAN.value].polynomial_order
                         == 0)
 
-        self.assertTrue(state.adjustment.calculate_transmission.fit[DataType.CAN].wavelength_low
+        self.assertTrue(state.adjustment.calculate_transmission.fit[DataType.CAN.value].wavelength_low
                         == 10.4)
-        self.assertTrue(state.adjustment.calculate_transmission.fit[DataType.CAN].wavelength_high
+        self.assertTrue(state.adjustment.calculate_transmission.fit[DataType.CAN.value].wavelength_high
                         == 12.54)
 
         self.assertEqual(state.reduction.merge_scale,  1.2)
diff --git a/scripts/test/SANS/gui_logic/CMakeLists.txt b/scripts/test/SANS/gui_logic/CMakeLists.txt
index 62b534b2ccaaaa7e8850222e705ac7c6f7e16296..9262d9d9590c5c0ee9a74cd2a6af2c7420845a43 100644
--- a/scripts/test/SANS/gui_logic/CMakeLists.txt
+++ b/scripts/test/SANS/gui_logic/CMakeLists.txt
@@ -7,9 +7,7 @@ set(TEST_PY_FILES
     gui_state_director_test.py
     gui_common_test.py
     masking_table_presenter_test.py
-    property_manager_service_test.py
     run_tab_presenter_test.py
-    sans_data_processor_gui_algorithm_test.py
     state_gui_model_test.py
     settings_diagnostic_presenter_test.py
     table_model_test.py
diff --git a/scripts/test/SANS/gui_logic/property_manager_service_test.py b/scripts/test/SANS/gui_logic/property_manager_service_test.py
deleted file mode 100644
index e1e0e612b0bd1e0541556a00abd7f4a92c839723..0000000000000000000000000000000000000000
--- a/scripts/test/SANS/gui_logic/property_manager_service_test.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Mantid Repository : https://github.com/mantidproject/mantid
-#
-# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-#     NScD Oak Ridge National Laboratory, European Spallation Source
-#     & Institut Laue - Langevin
-# SPDX - License - Identifier: GPL - 3.0 +
-from __future__ import (absolute_import, division, print_function)
-
-import unittest
-
-from mantid.api import Algorithm
-from mantid.kernel import PropertyManagerDataService, PropertyManagerProperty
-from sans.common.enums import SANSFacility, SANSInstrument
-from sans.gui_logic.presenter.property_manager_service import PropertyManagerService
-from sans.state.data import get_data_builder
-from sans.test_helper.file_information_mock import SANSFileInformationMock
-from sans.test_helper.test_director import TestDirector
-
-
-class FakeAlgorithm(Algorithm):
-    def PyInit(self):
-        self.declareProperty(PropertyManagerProperty("Args"))
-
-    def PyExec(self):
-        pass
-
-
-def get_example_state():
-    ws_name_sample = "SANS2D00022024"
-    file_information = SANSFileInformationMock(instrument=SANSInstrument.SANS2D, run_number=22024)
-    data_builder = get_data_builder(SANSFacility.ISIS, file_information)
-    data_builder.set_sample_scatter(ws_name_sample)
-    data = data_builder.build()
-
-    # Get the sample state
-    test_director = TestDirector()
-    test_director.set_states(data_state=data)
-    return test_director.construct()
-
-
-class PropertyManagerServiceTest(unittest.TestCase):
-    def test_that_add_states_to_pmds(self):
-        self.assertEqual(len(PropertyManagerDataService.getObjectNames()),  0)
-        states = {0: get_example_state(), 1: get_example_state()}
-        pms = PropertyManagerService()
-        pms.add_states_to_pmds(states)
-        self.assertEqual(len(PropertyManagerDataService.getObjectNames()),  2)
-        self._remove_all_property_managers()
-
-    def test_that_removes_sans_property_managers_from_pmds(self):
-        self.assertEqual(len(PropertyManagerDataService.getObjectNames()),  0)
-        states = {0: get_example_state(), 1: get_example_state()}
-        pms = PropertyManagerService()
-        pms.add_states_to_pmds(states)
-        pms.remove_sans_property_managers()
-        self.assertEqual(len(PropertyManagerDataService.getObjectNames()),  0)
-        self._remove_all_property_managers()
-
-    def test_that_can_retrieve_states_from_pmds(self):
-        self.assertEqual(len(PropertyManagerDataService.getObjectNames()),  0)
-        states = {0: get_example_state(), 1: get_example_state()}
-        pms = PropertyManagerService()
-        pms.add_states_to_pmds(states)
-        retrieved_states = pms.get_states_from_pmds()
-        self.assertEqual(len(retrieved_states),  2)
-        self.assertTrue(isinstance(retrieved_states[0], type(states[0])))
-        self.assertEqual(len(PropertyManagerDataService.getObjectNames()),  2)
-        self._remove_all_property_managers()
-
-    def test_that_does_not_delete_pms_which_are_not_sans(self):
-        property_manager = self._get_property_manager()
-        PropertyManagerDataService.addOrReplace("test", property_manager)
-        pms = PropertyManagerService()
-        pms.remove_sans_property_managers()
-        self.assertEqual(len(PropertyManagerDataService.getObjectNames()),  1)
-        self._remove_all_property_managers()
-
-    def test_that_it_adds_nothing_when_empty_list_is_passed_in(self):
-        pms = PropertyManagerService()
-        number_of_elements_on_pmds_after = len(PropertyManagerDataService.getObjectNames())
-        pms.add_states_to_pmds({})
-        self.assertEqual(number_of_elements_on_pmds_after,  0)
-        self._remove_all_property_managers()
-
-    def test_that_it_returns_empty_list_if_no_states_have_been_added(self):
-        pms = PropertyManagerService()
-        states = pms.get_states_from_pmds()
-        self.assertEqual(states,  [])
-        self._remove_all_property_managers()
-
-    @staticmethod
-    def _remove_all_property_managers():
-        for element in PropertyManagerDataService.getObjectNames():
-            PropertyManagerDataService.remove(element)
-
-    @staticmethod
-    def _get_property_manager():
-        alg = FakeAlgorithm()
-        alg.initialize()
-        alg.setProperty("Args", {"test": 1})
-        return alg.getProperty("Args").value
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/scripts/test/SANS/gui_logic/sans_data_processor_gui_algorithm_test.py b/scripts/test/SANS/gui_logic/sans_data_processor_gui_algorithm_test.py
deleted file mode 100644
index b035399cdd6c8adf4eb40fcbcdc3da1d5ac9be32..0000000000000000000000000000000000000000
--- a/scripts/test/SANS/gui_logic/sans_data_processor_gui_algorithm_test.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# Mantid Repository : https://github.com/mantidproject/mantid
-#
-# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-#     NScD Oak Ridge National Laboratory, European Spallation Source
-#     & Institut Laue - Langevin
-# SPDX - License - Identifier: GPL - 3.0 +
-from __future__ import (absolute_import, division, print_function)
-
-import unittest
-
-from sans.common.enums import (SANSFacility)
-from sans.gui_logic.sans_data_processor_gui_algorithm import (create_properties, create_option_column_properties,
-                                                              get_gui_algorithm_name, get_white_list, get_black_list)
-
-
-class SANSGuiDataProcessorAlgorithmTest(unittest.TestCase):
-    def test_that_all_option_columns_are_there(self):
-        options = create_option_column_properties()
-        self.assertEqual(len(options), 3)
-        self.assertEqual(options[0].algorithm_property, "WavelengthMin")
-        self.assertEqual(options[1].algorithm_property, "WavelengthMax")
-        self.assertEqual(options[2].algorithm_property, "EventSlices")
-
-    def test_that_the_properties_with_periods_can_be_provided(self):
-        props = create_properties()
-        self.assertEqual(len(props), 20)
-
-        expected = [{"algorithm_property": "SampleScatter", "column_name": "SampleScatter"},
-                    {"algorithm_property": "SampleScatterPeriod", "column_name": "ssp"},
-                    {"algorithm_property": "SampleTransmission", "column_name": "SampleTrans"},
-                    {"algorithm_property": "SampleTransmissionPeriod", "column_name": "stp"},
-                    {"algorithm_property": "SampleDirect", "column_name": "SampleDirect"},
-                    {"algorithm_property": "SampleDirectPeriod", "column_name": "sdp"},
-                    {"algorithm_property": "CanScatter", "column_name": "CanScatter"},
-                    {"algorithm_property": "CanScatterPeriod", "column_name": "csp"},
-                    {"algorithm_property": "CanTransmission", "column_name": "CanTrans"},
-                    {"algorithm_property": "CanTransmissionPeriod", "column_name": "ctp"},
-                    {"algorithm_property": "CanDirect", "column_name": "CanDirect"},
-                    {"algorithm_property": "CanDirectPeriod", "column_name": "cdp"},
-                    {"algorithm_property": "UseOptimizations", "column_name": ""},
-                    {"algorithm_property": "PlotResults", "column_name": ""},
-                    {"algorithm_property": "OutputName", "column_name": "OutputName"},
-                    {"algorithm_property": "UserFile", "column_name": "User File"},
-                    {"algorithm_property": "SampleThickness", "column_name": "Sample Thickness"},
-                    {"algorithm_property": "RowIndex", "column_name": ""},
-                    {"algorithm_property": "OutputMode", "column_name": ""},
-                    {"algorithm_property": "OutputGraph", "column_name": ""}]
-
-        for index, element in enumerate(props):
-            self.assertEqual(element.algorithm_property, expected[index]["algorithm_property"])
-            self.assertEqual(element.column_name, expected[index]["column_name"])
-
-    def test_that_the_properties_without_periods_can_be_provided(self):
-        props = create_properties(show_periods=False)
-        self.assertEqual(len(props), 14)
-
-        expected = [{"algorithm_property": "SampleScatter", "column_name": "SampleScatter"},
-                    {"algorithm_property": "SampleTransmission", "column_name": "SampleTrans"},
-                    {"algorithm_property": "SampleDirect", "column_name": "SampleDirect"},
-                    {"algorithm_property": "CanScatter", "column_name": "CanScatter"},
-                    {"algorithm_property": "CanTransmission", "column_name": "CanTrans"},
-                    {"algorithm_property": "CanDirect", "column_name": "CanDirect"},
-                    {"algorithm_property": "UseOptimizations", "column_name": ""},
-                    {"algorithm_property": "PlotResults", "column_name": ""},
-                    {"algorithm_property": "OutputName", "column_name": "OutputName"},
-                    {"algorithm_property": "UserFile", "column_name": "User File"},
-                    {"algorithm_property": "SampleThickness", "column_name": "Sample Thickness"},
-                    {"algorithm_property": "RowIndex", "column_name": ""},
-                    {"algorithm_property": "OutputMode", "column_name": ""},
-                    {"algorithm_property": "OutputGraph", "column_name": ""}]
-
-        for index, element in enumerate(props):
-            self.assertEqual(element.algorithm_property, expected[index]["algorithm_property"])
-            self.assertEqual(element.column_name, expected[index]["column_name"])
-
-    def test_that_gets_gui_algorithm_name(self):
-        alg_name = get_gui_algorithm_name(SANSFacility.ISIS)
-        self.assertEqual(alg_name, "SANSGuiDataProcessorAlgorithm")
-
-    def test_that_getting_algorithm_name_raises_when_facility_is_not_known(self):
-        args = ["teskdfsd"]
-        self.assertRaises(RuntimeError, get_gui_algorithm_name, *args)
-
-    def test_that_white_list_contains_all_properties(self):
-        white_list = get_white_list()
-        self.assertEqual(len(white_list), 20)
-
-    def test_that_black_list_contains_input_and_output_ws(self):
-        black_list = get_black_list()
-        self.assertTrue(black_list)
-        self.assertTrue(black_list.startswith("InputWorkspace,OutputWorkspace"))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/scripts/test/SANS/gui_logic/settings_diagnostic_presenter_test.py b/scripts/test/SANS/gui_logic/settings_diagnostic_presenter_test.py
index 46a6854740190e325b68fc5c7d3a22b48a62773a..6ecf7140242976740722a939ef73e61a613d5f13 100644
--- a/scripts/test/SANS/gui_logic/settings_diagnostic_presenter_test.py
+++ b/scripts/test/SANS/gui_logic/settings_diagnostic_presenter_test.py
@@ -13,6 +13,7 @@ import unittest
 
 from mantid.py3compat import mock
 from sans.gui_logic.presenter.settings_diagnostic_presenter import SettingsDiagnosticPresenter
+from sans.state.Serializer import Serializer
 from sans.test_helper.mock_objects import (create_run_tab_presenter_mock, FakeState,
                                            create_mock_settings_diagnostic_tab)
 
@@ -58,9 +59,8 @@ class SettingsDiagnosticPresenterTest(unittest.TestCase):
         # Assert
         self.assertTrue(os.path.exists(dummy_file_path))
 
-        with open(dummy_file_path) as f:
-            data = json.load(f)
-        self.assertEqual(data,  "dummy_state")
+        obj = Serializer.load_file(dummy_file_path)
+        self.assertEqual("dummy_state", obj.dummy_state)
 
         if os.path.exists(dummy_file_path):
             os.remove(dummy_file_path)
diff --git a/scripts/test/SANS/state/CMakeLists.txt b/scripts/test/SANS/state/CMakeLists.txt
index 4dc96390f8d42338ca4b6874e63799959b5c339e..37989207431f5a9ca7a84871831a156c422625ab 100644
--- a/scripts/test/SANS/state/CMakeLists.txt
+++ b/scripts/test/SANS/state/CMakeLists.txt
@@ -2,7 +2,7 @@
 
 set(TEST_PY_FILES
     adjustment_test.py
-    state_base_test.py
+    JsonSerializerTest.py
     calculate_transmission_test.py
     convert_to_q_test.py
     data_test.py
diff --git a/scripts/test/SANS/state/JsonSerializerTest.py b/scripts/test/SANS/state/JsonSerializerTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..d77d4cbfb5fc7310a5407d23db61751a112ee65a
--- /dev/null
+++ b/scripts/test/SANS/state/JsonSerializerTest.py
@@ -0,0 +1,176 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+
+from six import with_metaclass
+
+from mantid.api import Algorithm
+from mantid.py3compat import Enum
+
+
+# ----------------------------------------------------------------------------------------------------------------------
+# ----------------------------------------------------------------------------------------------------------------------
+# Test the typed parameters
+# ----------------------------------------------------------------------------------------------------------------------
+# ----------------------------------------------------------------------------------------------------------------------
+from sans.state.JsonSerializable import JsonSerializable, json_serializable
+from sans.state.Serializer import Serializer
+
+
+class TestClass(with_metaclass(JsonSerializable)):
+    string_parameter = None  # : Str()
+    bool_parameter = None  # : Bool
+    float_parameter = None  # : Float
+    positive_float_parameter = None  # : Float (Positive)
+    positive_integer_parameter = None  # : Int (Positive)
+    dict_parameter = None  # : Dict
+    float_with_none_parameter = None  # : Float
+    positive_float_with_none_parameter = None  # : Float (Optional)
+    float_list_parameter = None  # : List[Float]
+    string_list_parameter = None  # : List[Str]
+    positive_integer_list_parameter = None  # : List[Int] (Positive)
+
+    def __init__(self):
+        super(TestClass, self).__init__()
+
+    def validate(self):
+        pass
+
+
+@json_serializable
+class FakeEnumClass(Enum):
+    FOO = 1
+    BAR = "2"
+
+
+class ExampleWrapper(with_metaclass(JsonSerializable)):
+    # This has to be at the top module level, else the module name finding will fail
+    def __init__(self):
+        self._foo = FakeEnumClass.FOO
+        self.bar = FakeEnumClass.BAR
+
+    def validate(self):
+        return True
+
+
+class VerySimpleState(with_metaclass(JsonSerializable)):
+    string_parameter = None  # : Str()
+
+    def __init__(self):
+        super(VerySimpleState, self).__init__()
+        self.string_parameter = "test_in_very_simple"
+
+    def validate(self):
+        pass
+
+
+class SimpleState(with_metaclass(JsonSerializable)):
+    def __init__(self):
+        super(SimpleState, self).__init__()
+        self.string_parameter = "String_in_SimpleState"
+        self.bool_parameter = False
+        # We explicitly leave out the float_parameter
+        self.positive_float_parameter = 1.
+        self.positive_integer_parameter = 6
+        self.dict_parameter = {"1": 123, "2": "test"}
+        self.float_with_none_parameter = 325.
+        # We expliclty leave out the positive_float_with_none_parameter
+        self.float_list_parameter = [123., 234.]
+        self.string_list_parameter = ["test1", "test2"]
+        self.positive_integer_list_parameter = [1, 2, 3]
+        self.sub_state_very_simple = VerySimpleState()
+
+    def validate(self):
+        pass
+
+
+class ComplexState(with_metaclass(JsonSerializable)):
+    def __init__(self):
+        super(ComplexState, self).__init__()
+        self.float_parameter = 23.
+        self.positive_float_with_none_parameter = 234.
+        self.sub_state_1 = SimpleState()
+        self.dict_parameter = {"A": SimpleState(), "B": SimpleState()}
+
+    def validate(self):
+        pass
+
+
+class JsonSerializerTest(unittest.TestCase):
+    class FakeAlgorithm(Algorithm):
+        def PyInit(self):
+            self.declareProperty("Args", '')
+
+        def PyExec(self):
+            pass
+
+    def test_that_enum_can_be_serialized(self):
+        original_obj = ExampleWrapper()
+
+        # Serializing test
+        serialized = Serializer.to_json(original_obj)
+        self.assertTrue("bar" in serialized)
+        self.assertTrue("_foo" in serialized)
+        self.assertTrue(isinstance(serialized, str), "The type was not converted to a string")
+
+        # Deserializing Test
+        fake = JsonSerializerTest.FakeAlgorithm()
+        fake.initialize()
+        fake.setProperty("Args", serialized)
+        property_manager = fake.getProperty("Args").value
+
+        new_obj = Serializer.from_json(property_manager)
+        self.assertEqual(FakeEnumClass.BAR, new_obj.bar)
+        self.assertEqual(FakeEnumClass.FOO, new_obj._foo)
+
+    def test_that_enum_list_can_be_serialized(self):
+        original_obj = ExampleWrapper()
+        original_obj.bar = [FakeEnumClass.BAR, FakeEnumClass.BAR]
+
+        # Serializing test
+        serialized = Serializer.to_json(original_obj)
+        self.assertTrue("bar" in serialized)
+        self.assertTrue("_foo" in serialized)
+        self.assertTrue(isinstance(serialized, str))
+
+        # Deserializing Test
+        fake = JsonSerializerTest.FakeAlgorithm()
+        fake.initialize()
+        fake.setProperty("Args", serialized)
+        property_manager = fake.getProperty("Args").value
+
+        new_obj = Serializer.from_json(property_manager)
+        self.assertEqual(original_obj.bar, new_obj.bar)
+        self.assertEqual(original_obj._foo, new_obj._foo)
+
+    def test_that_sans_state_can_be_serialized_and_deserialized_when_going_through_an_algorithm(self):
+        # Arrange
+        state = ComplexState()
+
+        # Act
+        serialized = Serializer.to_json(state)
+        fake = JsonSerializerTest.FakeAlgorithm()
+        fake.initialize()
+        fake.setProperty("Args", serialized)
+        property_manager = fake.getProperty("Args").value
+
+        # Assert
+        self.assertEqual(type(serialized),  str)
+        state_2 = Serializer.from_json(property_manager)
+
+        # The direct sub state
+        self.assertEqual(state.sub_state_1.float_list_parameter, state_2.sub_state_1.float_list_parameter)
+
+        # The regular parameters
+        self.assertEqual(state_2.float_parameter,  23.)
+        self.assertEqual(state_2.positive_float_with_none_parameter,  234.)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/scripts/test/SANS/state/calculate_transmission_test.py b/scripts/test/SANS/state/calculate_transmission_test.py
index e3a26dade8cd3b604bbc84b8cdd144b71bfe82ef..cf75cbe5ab0ca22e10baa5b749fd14d559a5501c 100644
--- a/scripts/test/SANS/state/calculate_transmission_test.py
+++ b/scripts/test/SANS/state/calculate_transmission_test.py
@@ -26,7 +26,7 @@ class StateCalculateTransmissionTest(unittest.TestCase):
                 value = custom_settings[key]
 
             if value is not None:  # If the value is None, then don't set it
-                setattr(state.fit[fit_key], key, value)
+                setattr(state.fit[fit_key.value], key, value)
 
     @staticmethod
     def _get_calculate_transmission_state(trans_entries, fit_entries):
@@ -274,15 +274,15 @@ class StateCalculateTransmissionBuilderTest(unittest.TestCase):
         self.assertEqual(state.background_TOF_roi_start,  1.4)
         self.assertEqual(state.background_TOF_roi_stop,  34.4)
 
-        self.assertEqual(state.fit[DataType.SAMPLE].fit_type, FitType.LINEAR)
-        self.assertEqual(state.fit[DataType.SAMPLE].polynomial_order, 0)
-        self.assertEqual(state.fit[DataType.SAMPLE].wavelength_low, 10.)
-        self.assertEqual(state.fit[DataType.SAMPLE].wavelength_high, 20.)
+        self.assertEqual(state.fit[DataType.SAMPLE.value].fit_type, FitType.LINEAR)
+        self.assertEqual(state.fit[DataType.SAMPLE.value].polynomial_order, 0)
+        self.assertEqual(state.fit[DataType.SAMPLE.value].wavelength_low, 10.)
+        self.assertEqual(state.fit[DataType.SAMPLE.value].wavelength_high, 20.)
 
-        self.assertEqual(state.fit[DataType.CAN].fit_type, FitType.POLYNOMIAL)
-        self.assertEqual(state.fit[DataType.CAN].polynomial_order, 3)
-        self.assertEqual(state.fit[DataType.CAN].wavelength_low, 10.)
-        self.assertEqual(state.fit[DataType.CAN].wavelength_high, 20.)
+        self.assertEqual(state.fit[DataType.CAN.value].fit_type, FitType.POLYNOMIAL)
+        self.assertEqual(state.fit[DataType.CAN.value].polynomial_order, 3)
+        self.assertEqual(state.fit[DataType.CAN.value].wavelength_low, 10.)
+        self.assertEqual(state.fit[DataType.CAN.value].wavelength_high, 20.)
 
 
 if __name__ == '__main__':
diff --git a/scripts/test/SANS/state/state_base_test.py b/scripts/test/SANS/state/state_base_test.py
deleted file mode 100644
index bddcbcff2fcc1fd9201c22210cdf5f6b4d1f8bc3..0000000000000000000000000000000000000000
--- a/scripts/test/SANS/state/state_base_test.py
+++ /dev/null
@@ -1,326 +0,0 @@
-# Mantid Repository : https://github.com/mantidproject/mantid
-#
-# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-#     NScD Oak Ridge National Laboratory, European Spallation Source
-#     & Institut Laue - Langevin
-# SPDX - License - Identifier: GPL - 3.0 +
-from __future__ import (absolute_import, division, print_function)
-
-import unittest
-
-from mantid.api import Algorithm
-from mantid.kernel import (PropertyManagerProperty, PropertyManager)
-from mantid.py3compat import Enum
-from sans.state.state_base import (StringParameter, BoolParameter, FloatParameter, PositiveFloatParameter,
-                                   PositiveIntegerParameter, DictParameter, FloatWithNoneParameter,
-                                   PositiveFloatWithNoneParameter, FloatListParameter,
-                                   StringListParameter, PositiveIntegerListParameter, StateBase,
-                                   rename_descriptor_names, TypedParameter, validator_sub_state,
-                                   create_deserialized_sans_state_from_property_manager)
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-# ----------------------------------------------------------------------------------------------------------------------
-# Test the typed parameters
-# ----------------------------------------------------------------------------------------------------------------------
-# ----------------------------------------------------------------------------------------------------------------------
-@rename_descriptor_names
-class StateBaseTestClass(StateBase):
-    string_parameter = StringParameter()
-    bool_parameter = BoolParameter()
-    float_parameter = FloatParameter()
-    positive_float_parameter = PositiveFloatParameter()
-    positive_integer_parameter = PositiveIntegerParameter()
-    dict_parameter = DictParameter()
-    float_with_none_parameter = FloatWithNoneParameter()
-    positive_float_with_none_parameter = PositiveFloatWithNoneParameter()
-    float_list_parameter = FloatListParameter()
-    string_list_parameter = StringListParameter()
-    positive_integer_list_parameter = PositiveIntegerListParameter()
-
-    def __init__(self):
-        super(StateBaseTestClass, self).__init__()
-
-    def validate(self):
-        pass
-
-
-class FakeEnumClass(Enum):
-    FOO = 1
-    BAR = "2"
-
-
-class ExampleWrapper(StateBase):
-    # This has to be at the top module level, else the module name finding will fail
-    _foo = FakeEnumClass.FOO
-    bar = FakeEnumClass.BAR
-
-    def validate(self):
-        return True
-
-
-class TypedParameterTest(unittest.TestCase):
-    def _check_that_raises(self, error_type, obj, descriptor_name, value):
-        try:
-            setattr(obj, descriptor_name, value)
-            self.fail()
-        except error_type:
-            pass
-        except:  # noqa
-            self.fail()
-
-    def test_that_can_set_to_valid_value_of_correct_type(self):
-        test_class = StateBaseTestClass()
-        try:
-            test_class.string_parameter = "Test"
-            test_class.bool_parameter = True
-            test_class.float_parameter = -23.5768
-            test_class.positive_float_parameter = 234.5
-            test_class.positive_integer_parameter = 12
-            test_class.dict_parameter = {}
-            test_class.dict_parameter = {"test": 12, "test2": 13}
-            test_class.float_with_none_parameter = None
-            test_class.float_with_none_parameter = -123.67
-            test_class.positive_float_with_none_parameter = None
-            test_class.positive_float_with_none_parameter = 123.67
-            test_class.float_list_parameter = [12., -123., 2355.]
-            test_class.string_list_parameter = ["test", "test"]
-            test_class.positive_integer_list_parameter = [1, 2, 4]
-
-        except ValueError:
-            self.fail()
-
-    def test_that_will_raise_type_error_if_set_with_wrong_type(self):
-        test_class = StateBaseTestClass()
-        self._check_that_raises(TypeError, test_class, "string_parameter", 1.)
-        self._check_that_raises(TypeError, test_class, "bool_parameter", 1.)
-        self._check_that_raises(TypeError, test_class, "float_parameter", "test")
-        self._check_that_raises(TypeError, test_class, "positive_float_parameter", "test")
-        self._check_that_raises(TypeError, test_class, "positive_integer_parameter", "test")
-        self._check_that_raises(TypeError, test_class, "dict_parameter", "test")
-        self._check_that_raises(TypeError, test_class, "float_with_none_parameter", "test")
-        self._check_that_raises(TypeError, test_class, "positive_float_with_none_parameter", "test")
-        self._check_that_raises(TypeError, test_class, "float_list_parameter", [1.23, "test"])
-        self._check_that_raises(TypeError, test_class, "string_list_parameter", ["test", "test", 123.])
-        self._check_that_raises(TypeError, test_class, "positive_integer_list_parameter", [1, "test"])
-
-    def test_that_will_raise_if_set_with_wrong_value(self):
-        # Note that this check does not apply to all parameter, it checks the validator
-        test_class = StateBaseTestClass()
-        self._check_that_raises(ValueError, test_class, "positive_float_parameter", -1.2)
-        self._check_that_raises(ValueError, test_class, "positive_integer_parameter", -1)
-        self._check_that_raises(ValueError, test_class, "positive_float_with_none_parameter", -234.)
-        self._check_that_raises(ValueError, test_class, "positive_integer_list_parameter", [1, -2, 4])
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-# ----------------------------------------------------------------------------------------------------------------------
-# Test the sans_parameters decorator
-# ----------------------------------------------------------------------------------------------------------------------
-# ----------------------------------------------------------------------------------------------------------------------
-
-class SANSParameterTest(unittest.TestCase):
-    @rename_descriptor_names
-    class SANSParameterTestClass(object):
-        my_string_parameter = StringParameter()
-        my_bool_parameter = BoolParameter()
-
-    class SANSParameterTestClass2(object):
-        my_string_parameter = StringParameter()
-        my_bool_parameter = BoolParameter()
-
-    def test_that_name_is_in_readable_format_in_instance_dictionary(self):
-        test_class = SANSParameterTest.SANSParameterTestClass()
-        test_class.my_string_parameter = "test"
-        test_class.my_bool_parameter = True
-        keys = list(test_class.__dict__.keys())
-        # We don't have a sensible name in the instance dictionary
-        self.assertTrue("_BoolParameter#my_bool_parameter" in keys)
-        self.assertTrue("_StringParameter#my_string_parameter" in keys)
-
-    def test_that_name_cannot_be_found_in_instance_dictionary_when_sans_parameters_decorator_is_not_applied(self):
-        test_class = SANSParameterTest.SANSParameterTestClass2()
-        test_class.my_string_parameter = "test"
-        test_class.my_bool_parameter = True
-        keys = list(test_class.__dict__.keys())
-        # We don't have a sensible name in the instance dictionary.
-        # It will be rather stored as something like: _BoolParameter#2 etc.
-        self.assertTrue("_BoolParameter#my_bool_parameter" not in keys)
-        self.assertTrue("_StringParameter#my_string_parameter" not in keys)
-
-
-# ----------------------------------------------------------------------------------------------------------------------
-# ----------------------------------------------------------------------------------------------------------------------
-# StateBase
-# This will mainly test serialization
-# ----------------------------------------------------------------------------------------------------------------------
-# ----------------------------------------------------------------------------------------------------------------------
-
-@rename_descriptor_names
-class VerySimpleState(StateBase):
-    string_parameter = StringParameter()
-
-    def __init__(self):
-        super(VerySimpleState, self).__init__()
-        self.string_parameter = "test_in_very_simple"
-
-    def validate(self):
-        pass
-
-
-@rename_descriptor_names
-class SimpleState(StateBase):
-    string_parameter = StringParameter()
-    bool_parameter = BoolParameter()
-    float_parameter = FloatParameter()
-    positive_float_parameter = PositiveFloatParameter()
-    positive_integer_parameter = PositiveIntegerParameter()
-    dict_parameter = DictParameter()
-    float_with_none_parameter = FloatWithNoneParameter()
-    positive_float_with_none_parameter = PositiveFloatWithNoneParameter()
-    float_list_parameter = FloatListParameter()
-    string_list_parameter = StringListParameter()
-    positive_integer_list_parameter = PositiveIntegerListParameter()
-    sub_state_very_simple = TypedParameter(VerySimpleState, validator_sub_state)
-
-    def __init__(self):
-        super(SimpleState, self).__init__()
-        self.string_parameter = "String_in_SimpleState"
-        self.bool_parameter = False
-        # We explicitly leave out the float_parameter
-        self.positive_float_parameter = 1.
-        self.positive_integer_parameter = 6
-        self.dict_parameter = {"1": 123, "2": "test"}
-        self.float_with_none_parameter = 325.
-        # We expliclty leave out the positive_float_with_none_parameter
-        self.float_list_parameter = [123., 234.]
-        self.string_list_parameter = ["test1", "test2"]
-        self.positive_integer_list_parameter = [1, 2, 3]
-        self.sub_state_very_simple = VerySimpleState()
-
-    def validate(self):
-        pass
-
-
-@rename_descriptor_names
-class ComplexState(StateBase):
-    float_parameter = FloatParameter()
-    positive_float_with_none_parameter = PositiveFloatWithNoneParameter()
-    sub_state_1 = TypedParameter(SimpleState, validator_sub_state)
-    dict_parameter = DictParameter()
-
-    def __init__(self):
-        super(ComplexState, self).__init__()
-        self.float_parameter = 23.
-        self.positive_float_with_none_parameter = 234.
-        self.sub_state_1 = SimpleState()
-        self.dict_parameter = {"A": SimpleState(), "B": SimpleState()}
-
-    def validate(self):
-        pass
-
-
-class TestStateBase(unittest.TestCase):
-    class FakeAlgorithm(Algorithm):
-        def PyInit(self):
-            self.declareProperty(PropertyManagerProperty("Args"))
-
-        def PyExec(self):
-            pass
-
-    def _assert_simple_state(self, state):
-        self.assertEqual(state.string_parameter,  "String_in_SimpleState")
-        self.assertFalse(state.bool_parameter)
-        self.assertEqual(state.float_parameter, None)  # We did not set it on the instance
-        self.assertEqual(state.positive_float_parameter,  1.)
-        self.assertEqual(state.positive_integer_parameter,  6)
-        self.assertEqual(state.dict_parameter["1"],  123)
-        self.assertEqual(state.dict_parameter["2"],  "test")
-        self.assertEqual(state.float_with_none_parameter,  325.)
-        self.assertEqual(state.positive_float_with_none_parameter, None)
-
-        self.assertEqual(len(state.float_list_parameter),  2)
-        self.assertEqual(state.float_list_parameter[0],  123.)
-        self.assertEqual(state.float_list_parameter[1],  234.)
-
-        self.assertEqual(len(state.string_list_parameter),  2)
-        self.assertEqual(state.string_list_parameter[0],  "test1")
-        self.assertEqual(state.string_list_parameter[1],  "test2")
-
-        self.assertEqual(len(state.positive_integer_list_parameter),  3)
-        self.assertEqual(state.positive_integer_list_parameter[0],  1)
-        self.assertEqual(state.positive_integer_list_parameter[1],  2)
-        self.assertEqual(state.positive_integer_list_parameter[2],  3)
-
-        self.assertEqual(state.sub_state_very_simple.string_parameter,  "test_in_very_simple")
-
-    def test_that_enum_can_be_serialized(self):
-        original_obj = ExampleWrapper()
-
-        # Serializing test
-        serialized = original_obj.property_manager
-        self.assertTrue("bar" in serialized)
-        self.assertFalse("_foo" in serialized)
-        self.assertTrue(isinstance(serialized["bar"], str), "The type was not converted to a string")
-
-        # Deserializing Test
-        fake = TestStateBase.FakeAlgorithm()
-        fake.initialize()
-        fake.setProperty("Args", serialized)
-        property_manager = fake.getProperty("Args").value
-
-        new_obj = create_deserialized_sans_state_from_property_manager(property_manager)
-        self.assertEqual(FakeEnumClass.BAR, new_obj.bar)
-        self.assertEqual(FakeEnumClass.FOO, new_obj._foo)
-
-    def test_that_enum_list_can_be_serialized(self):
-        original_obj = ExampleWrapper()
-        original_obj.bar = [FakeEnumClass.BAR, FakeEnumClass.BAR]
-
-        # Serializing test
-        serialized = original_obj.property_manager
-        self.assertTrue("bar" in serialized)
-        self.assertFalse("_foo" in serialized)
-        self.assertTrue(isinstance(serialized["bar"], list), "The type was not converted to a list of strings")
-
-        # Deserializing Test
-        fake = TestStateBase.FakeAlgorithm()
-        fake.initialize()
-        fake.setProperty("Args", serialized)
-        property_manager = fake.getProperty("Args").value
-
-        new_obj = create_deserialized_sans_state_from_property_manager(property_manager)
-        self.assertEqual(original_obj.bar, new_obj.bar)
-        self.assertEqual(original_obj._foo, new_obj._foo)
-
-    def test_that_sans_state_can_be_serialized_and_deserialized_when_going_through_an_algorithm(self):
-        # Arrange
-        state = ComplexState()
-
-        # Act
-        serialized = state.property_manager
-        fake = TestStateBase.FakeAlgorithm()
-        fake.initialize()
-        fake.setProperty("Args", serialized)
-        property_manager = fake.getProperty("Args").value
-
-        # Assert
-        self.assertEqual(type(serialized),  dict)
-        self.assertEqual(type(property_manager),  PropertyManager)
-        state_2 = create_deserialized_sans_state_from_property_manager(property_manager)
-        state_2.property_manager = property_manager
-
-        # The direct sub state
-        self._assert_simple_state(state_2.sub_state_1)
-
-        # The two states in the dictionary
-        self._assert_simple_state(state_2.dict_parameter["A"])
-        self._assert_simple_state(state_2.dict_parameter["B"])
-
-        # The regular parameters
-        self.assertEqual(state_2.float_parameter,  23.)
-        self.assertEqual(state_2.positive_float_with_none_parameter,  234.)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/scripts/test/SANS/user_file/state_director_test.py b/scripts/test/SANS/user_file/state_director_test.py
index a512b3c5a882fb7da74e385d76bef9211ce5443a..127ef6bfcb477151b0b114ac9c95869f5a4f6b8a 100644
--- a/scripts/test/SANS/user_file/state_director_test.py
+++ b/scripts/test/SANS/user_file/state_director_test.py
@@ -147,14 +147,14 @@ class UserFileStateDirectorISISTest(unittest.TestCase):
         self.assertEqual(calculate_transmission.background_TOF_monitor_stop["2"],  98000)
         self.assertEqual(calculate_transmission.background_TOF_roi_start,  123)
         self.assertEqual(calculate_transmission.background_TOF_roi_stop,  466)
-        self.assertEqual(calculate_transmission.fit[DataType.SAMPLE].fit_type, FitType.LOGARITHMIC)
-        self.assertEqual(calculate_transmission.fit[DataType.SAMPLE].wavelength_low, 1.5)
-        self.assertEqual(calculate_transmission.fit[DataType.SAMPLE].wavelength_high, 12.5)
-        self.assertEqual(calculate_transmission.fit[DataType.SAMPLE].polynomial_order, 0)
-        self.assertEqual(calculate_transmission.fit[DataType.CAN].fit_type, FitType.LOGARITHMIC)
-        self.assertEqual(calculate_transmission.fit[DataType.CAN].wavelength_low, 1.5)
-        self.assertEqual(calculate_transmission.fit[DataType.CAN].wavelength_high, 12.5)
-        self.assertEqual(calculate_transmission.fit[DataType.CAN].polynomial_order, 0)
+        self.assertEqual(calculate_transmission.fit[DataType.SAMPLE.value].fit_type, FitType.LOGARITHMIC)
+        self.assertEqual(calculate_transmission.fit[DataType.SAMPLE.value].wavelength_low, 1.5)
+        self.assertEqual(calculate_transmission.fit[DataType.SAMPLE.value].wavelength_high, 12.5)
+        self.assertEqual(calculate_transmission.fit[DataType.SAMPLE.value].polynomial_order, 0)
+        self.assertEqual(calculate_transmission.fit[DataType.CAN.value].fit_type, FitType.LOGARITHMIC)
+        self.assertEqual(calculate_transmission.fit[DataType.CAN.value].wavelength_low, 1.5)
+        self.assertEqual(calculate_transmission.fit[DataType.CAN.value].wavelength_high, 12.5)
+        self.assertEqual(calculate_transmission.fit[DataType.CAN.value].polynomial_order, 0)
 
         # Wavelength and Pixel Adjustment
         wavelength_and_pixel_adjustment = adjustment.wavelength_and_pixel_adjustment
@@ -163,11 +163,11 @@ class UserFileStateDirectorISISTest(unittest.TestCase):
         self.assertEqual(wavelength_and_pixel_adjustment.wavelength_step,  0.125)
         self.assertEqual(wavelength_and_pixel_adjustment.wavelength_step_type, RangeStepType.LIN)
         self.assertTrue(wavelength_and_pixel_adjustment.adjustment_files[
-                        DetectorType.LAB.value].wavelength_adjustment_file ==
-                        "DIRECTM1_15785_12m_31Oct12_v12.dat")
+                        DetectorType.LAB.value].wavelength_adjustment_file
+                        == "DIRECTM1_15785_12m_31Oct12_v12.dat")
         self.assertTrue(wavelength_and_pixel_adjustment.adjustment_files[
-                        DetectorType.HAB.value].wavelength_adjustment_file ==
-                        "DIRECTM1_15785_12m_31Oct12_v12.dat")
+                        DetectorType.HAB.value].wavelength_adjustment_file
+                        == "DIRECTM1_15785_12m_31Oct12_v12.dat")
 
         # Assert wide angle correction
         self.assertTrue(state.adjustment.wide_angle_correction)