diff --git a/config_normalizer.services.yml b/config_normalizer.services.yml
index dca7b3450b6b35d15bf46994b73868484a5d87b8..d7bfc5d62e418852eb7826604d1dd5f2c3f24901 100644
--- a/config_normalizer.services.yml
+++ b/config_normalizer.services.yml
@@ -1,4 +1,11 @@
 services:
+
+  config_normalizer.normalizer:
+    class: Drupal\config_normalizer\ConfigNormalizer
+    arguments:
+      - "@config.typed"
+
+  # deprecated
   plugin.manager.config_normalizer:
     class: Drupal\config_normalizer\Plugin\ConfigNormalizerManager
     parent: default_plugin_manager
diff --git a/src/Annotation/ConfigNormalizer.php b/src/Annotation/ConfigNormalizer.php
index 4dff6dc848835a37164b85459ba570425c177b30..ef50a2c31e0f456b219a2744973fa6cf1f645045 100644
--- a/src/Annotation/ConfigNormalizer.php
+++ b/src/Annotation/ConfigNormalizer.php
@@ -10,6 +10,9 @@ use Drupal\Component\Annotation\Plugin;
  * @see \Drupal\config_normalizer\Plugin\ConfigNormalizerManager
  * @see plugin_api
  *
+ * @deprecated in config_normalizer:2.0.0-alpha1 and is removed from config_normalizer:2.0.0. No replacement.
+ * @see https://www.drupal.org/project/config_normalizer/issues/3230398
+ *
  * @Annotation
  */
 class ConfigNormalizer extends Plugin {
diff --git a/src/Config/NormalizedStorageComparerTrait.php b/src/Config/NormalizedStorageComparerTrait.php
index 8151236afe752f21451d7f9880853922a21d84ab..f72204ce4d22393e545ff3b8ce78a1115ca9fd5f 100644
--- a/src/Config/NormalizedStorageComparerTrait.php
+++ b/src/Config/NormalizedStorageComparerTrait.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\config_normalizer\Config;
 
+use Drupal\config_normalizer\ConfigNormalizerInterface;
 use Drupal\config_normalizer\Plugin\ConfigNormalizerManager;
 use Drupal\Core\Config\ConfigManagerInterface;
 use Drupal\Core\Config\StorageComparer;
@@ -11,25 +12,16 @@ use Drupal\Core\Config\StorageInterface;
  * Using this trait will add a ::createStorageComparer() method to the class.
  *
  * If the class is capable of injecting services from the container, it should
- * inject the 'config.manager' service by calling $this->setConfigManager() and
- * the 'plugin.manager.config_normalizer' service by calling
- * $this->setNormalizerManager().
+ * inject the 'config_normalizer.normalizer' service and call setNormalizer()
  */
 trait NormalizedStorageComparerTrait {
 
   /**
-   * The normalizer plugin manager.
+   * The config normalizer service.
    *
-   * @var \Drupal\config_normalizer\Plugin\ConfigNormalizerManager
+   * @var \Drupal\config_normalizer\ConfigNormalizerInterface
    */
-  protected $normalizerManager;
-
-  /**
-   * The configuration manager.
-   *
-   * @var \Drupal\Core\Config\ConfigManagerInterface
-   */
-  protected $configManager;
+  protected $configNormalizer;
 
   /**
    * Creates and returns a storage comparer.
@@ -39,43 +31,61 @@ trait NormalizedStorageComparerTrait {
    * @param \Drupal\Core\Config\StorageInterface $target_storage
    *   The target storage.
    * @param string $mode
-   *   (optional) The normalization mode.
+   *   (optional, deprecated) The normalization mode.
    *
    * @return \Drupal\Core\Config\StorageComparer
    *   A storage comparer.
    */
-  protected function createStorageComparer(StorageInterface $source_storage, StorageInterface $target_storage, $mode = NormalizedReadOnlyStorageInterface::DEFAULT_NORMALIZATION_MODE) {
-    $source_context = [
-      'normalization_mode' => $mode,
-      'reference_storage_service' => $target_storage,
-    ];
-
-    $target_context = [
-      'normalization_mode' => $mode,
-      'reference_storage_service' => $source_storage,
-    ];
-
+  protected function createStorageComparer(StorageInterface $source_storage, StorageInterface $target_storage, $mode = NULL) {
     // Set up a storage comparer using normalized storages.
     $storage_comparer = new StorageComparer(
-      new NormalizedReadOnlyStorage($source_storage, $this->getNormalizerManager(), $source_context),
-      new NormalizedReadOnlyStorage($target_storage, $this->getNormalizerManager(), $target_context),
-      $this->getConfigManager()
+      new NormalizedReadOnlyStorage($source_storage, $this->getNormalizer()),
+      new NormalizedReadOnlyStorage($target_storage, $this->getNormalizer())
     );
 
     return $storage_comparer;
   }
 
+  /**
+   * Gets the normalizer service.
+   *
+   * @return \Drupal\config_normalizer\ConfigNormalizerInterface
+   *   The configuration normalizer.
+   */
+  protected function getNormalizer() {
+    if (!$this->configNormalizer) {
+      $this->configNormalizer = \Drupal::service('config_normalizer.normalizer');
+    }
+    return $this->configNormalizer;
+  }
+
+  /**
+   * Sets the normalizer manager service to use.
+   *
+   * @param \Drupal\config_normalizer\ConfigNormalizerInterface $normalizer
+   *   The normalizer service.
+   *
+   * @return $this
+   *
+   * @deprecated in config_normalizer:2.0.0-alpha1 and is removed from config_normalizer:2.0.0. No replacement.
+   * @see https://www.drupal.org/project/config_normalizer/issues/3230398
+   */
+  public function setNormalizer(ConfigNormalizerInterface $normalizer) {
+    $this->configNormalizer = $normalizer;
+    return $this;
+  }
+
   /**
    * Gets the configuration manager service.
    *
    * @return \Drupal\Core\Config\ConfigManagerInterface
    *   The configuration manager.
+   *
+   * @deprecated in config_normalizer:2.0.0-alpha1 and is removed from config_normalizer:2.0.0. No replacement.
+   * @see https://www.drupal.org/project/config_normalizer/issues/3230398
    */
   protected function getConfigManager() {
-    if (!$this->configManager) {
-      $this->configManager = \Drupal::service('config.manager');
-    }
-    return $this->configManager;
+    return \Drupal::service('config.manager');
   }
 
   /**
@@ -85,9 +95,11 @@ trait NormalizedStorageComparerTrait {
    *   The configuration manager service.
    *
    * @return $this
+   *
+   * @deprecated in config_normalizer:2.0.0-alpha1 and is removed from config_normalizer:2.0.0. No replacement.
+   * @see https://www.drupal.org/project/config_normalizer/issues/3230398
    */
   public function setConfigManager(ConfigManagerInterface $config_manager) {
-    $this->configManager = $config_manager;
     return $this;
   }
 
@@ -96,12 +108,12 @@ trait NormalizedStorageComparerTrait {
    *
    * @return \Drupal\config_normalizer\Plugin\ConfigNormalizerManager
    *   The normalizer manager.
+   *
+   * @deprecated in config_normalizer:2.0.0-alpha1 and is removed from config_normalizer:2.0.0. No replacement.
+   * @see https://www.drupal.org/project/config_normalizer/issues/3230398
    */
   protected function getNormalizerManager() {
-    if (!$this->normalizerManager) {
-      $this->normalizerManager = \Drupal::service('plugin.manager.config_normalizer');
-    }
-    return $this->normalizerManager;
+    return \Drupal::service('plugin.manager.config_normalizer');
   }
 
   /**
@@ -111,9 +123,11 @@ trait NormalizedStorageComparerTrait {
    *   The normalizer manager service.
    *
    * @return $this
+   *
+   * @deprecated in config_normalizer:2.0.0-alpha1 and is removed from config_normalizer:2.0.0. No replacement.
+   * @see https://www.drupal.org/project/config_normalizer/issues/3230398
    */
   public function setNormalizerManager(ConfigNormalizerManager $normalizer_manager) {
-    $this->normalizerManager = $normalizer_manager;
     return $this;
   }
 
diff --git a/src/ConfigNormalizer.php b/src/ConfigNormalizer.php
new file mode 100644
index 0000000000000000000000000000000000000000..5ad4a27b92680e19397abf6bc7383d5028d0aec4
--- /dev/null
+++ b/src/ConfigNormalizer.php
@@ -0,0 +1,203 @@
+<?php
+
+namespace Drupal\config_normalizer;
+
+use Drupal\Core\Config\Schema\Ignore;
+use Drupal\Core\Config\Schema\Mapping;
+use Drupal\Core\Config\Schema\Sequence;
+use Drupal\Core\Config\Schema\SequenceDataDefinition;
+use Drupal\Core\Config\Schema\Undefined;
+use Drupal\Core\Config\StorableConfigBase;
+use Drupal\Core\Config\TypedConfigManagerInterface;
+use Drupal\Core\Config\UnsupportedDataTypeConfigException;
+use Drupal\Core\TypedData\PrimitiveInterface;
+use Drupal\Core\TypedData\Type\FloatInterface;
+use Drupal\Core\TypedData\Type\IntegerInterface;
+
+/**
+ * Class responsible for performing configuration normalization.
+ */
+class ConfigNormalizer implements ConfigNormalizerInterface {
+
+  /**
+   * The typed config manager to get the schema from.
+   *
+   * @var \Drupal\Core\Config\TypedConfigManagerInterface
+   */
+  protected $typedConfigManager;
+
+  /**
+   * ConfigCaster constructor.
+   *
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager to look up the schema.
+   */
+  public function __construct(TypedConfigManagerInterface $typedConfigManager) {
+    $this->typedConfigManager = $typedConfigManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function normalize($name, array $data) {
+    // The sorter is an anonymous class extending from StorableConfigBase.
+    // We need to do this because the logic for sorting is in a class meant
+    // for config objects and not for services.
+    $sorter = new class($this->typedConfigManager) extends StorableConfigBase {
+
+      /**
+       * Sort the config.
+       *
+       * This method is named to make it unlikely that it is overriding a core
+       * method.
+       *
+       * @param string $name
+       *   The config name.
+       * @param array $data
+       *   The data.
+       *
+       * @return array
+       *   The sorted array.
+       */
+      public function anonymousSort(string $name, array $data): array {
+        // Set the object up.
+        self::validateName($name);
+        $this->validateKeys($data);
+        $this->setName($name)->initWithData($data);
+
+        // This is essentially what \Drupal\Core\Config\Config::save does when
+        // there is untrusted data before persisting it and dispatching events.
+        if ($this->typedConfigManager->hasConfigSchema($this->name)) {
+          // We use the patched version of the method.
+          $this->data = $this->castValue2852557(NULL, $this->data);
+        }
+        else {
+          foreach ($this->data as $key => $value) {
+            $this->validateValue($key, $value);
+          }
+        }
+
+        // That should be it.
+        return $this->data;
+      }
+
+      /**
+       * Casts the value to correct data type using the configuration schema.
+       *
+       * This is the patched version from
+       * https://www.drupal.org/project/drupal/issues/2852557
+       *
+       * @param string|null $key
+       *   A string that maps to a key within the configuration data. If NULL
+       *   the top level mapping will be processed.
+       * @param mixed $value
+       *   Value to associate with the key.
+       *
+       * @return mixed
+       *   The value cast to the type indicated in the schema.
+       *
+       * @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException
+       *   If the value is unsupported in configuration.
+       */
+      protected function castValue2852557($key, $value) {
+        $element = $this->getSchemaWrapper();
+        if ($key !== NULL) {
+          $element = $element->get($key);
+        }
+
+        // Do not cast value if it is unknown or defined to be ignored.
+        if ($element && ($element instanceof Undefined || $element instanceof Ignore)) {
+          $this->validateValue($key, $value);
+          return $value;
+        }
+        if (is_scalar($value) || $value === NULL) {
+          if ($element && $element instanceof PrimitiveInterface) {
+            $empty_value = $value === '' && ($element instanceof IntegerInterface || $element instanceof FloatInterface);
+
+            if ($value === NULL || $empty_value) {
+              $value = NULL;
+            }
+            else {
+              $value = $element->getCastedValue();
+            }
+          }
+        }
+        else {
+          // Throw exception on any non-scalar or non-array value.
+          if (!is_array($value)) {
+            throw new UnsupportedDataTypeConfigException("Invalid data type for config element {$this->getName()}:$key");
+          }
+          // Recurse into any nested keys.
+          foreach ($value as $nested_value_key => $nested_value) {
+            $lookup_key = $key ? $key . '.' . $nested_value_key : $nested_value_key;
+            $value[$nested_value_key] = $this->castValue2852557($lookup_key, $nested_value);
+          }
+
+          // Only sort maps when we have more than 1 element to sort.
+          if ($element instanceof Mapping && count($value) > 1) {
+            $mapping = $element->getDataDefinition()['mapping'];
+            if (is_array($mapping)) {
+              // Only sort the keys in $value.
+              $mapping = array_intersect_key($mapping, $value);
+              // Sort the array in $value using the mapping definition.
+              $value = array_replace($mapping, $value);
+            }
+          }
+
+          if ($element instanceof Sequence) {
+            $data_definition = $element->getDataDefinition();
+            if ($data_definition instanceof SequenceDataDefinition) {
+              // Apply any sorting defined on the schema.
+              switch ($data_definition->getOrderBy()) {
+                case 'key':
+                  ksort($value);
+                  break;
+
+                case 'value':
+                  // The PHP documentation notes that "Be careful when sorting
+                  // arrays with mixed types values because sort() can produce
+                  // unpredictable results". There is no risk here because
+                  // \Drupal\Core\Config\StorableConfigBase::castValue() has
+                  // already cast all values to the same type using the
+                  // configuration schema.
+                  sort($value);
+                  break;
+
+              }
+            }
+          }
+        }
+        return $value;
+      }
+
+      /**
+       * The constructor for passing the TypedConfigManager.
+       *
+       * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+       *   The taped config manager.
+       */
+      public function __construct(TypedConfigManagerInterface $typedConfigManager) {
+        $this->typedConfigManager = $typedConfigManager;
+      }
+
+      /**
+       * {@inheritdoc}
+       */
+      public function save($has_trusted_data = FALSE) {
+        throw new \LogicException();
+      }
+
+      /**
+       * {@inheritdoc}
+       */
+      public function delete() {
+        throw new \LogicException();
+      }
+
+    };
+
+    // Sort the data using the core class we extended.
+    return $sorter->anonymousSort($name, $data);
+  }
+
+}
diff --git a/src/ConfigNormalizerInterface.php b/src/ConfigNormalizerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..7460da29564cc7e4d591f457b9e41763c9cec4de
--- /dev/null
+++ b/src/ConfigNormalizerInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\config_normalizer;
+
+/**
+ * Defines an interface for config item normalizers.
+ *
+ * @api This is the main API of this module.
+ */
+interface ConfigNormalizerInterface {
+
+  /**
+   * Normalizes config for comparison.
+   *
+   * Normalization can help ensure that config from different storages can be
+   * compared meaningfully.
+   *
+   * @param string $name
+   *   The name of a configuration object to normalize.
+   * @param array $data
+   *   Configuration array to normalize.
+   *
+   * @return array
+   *   Normalized configuration array.
+   */
+  public function normalize($name, array $data);
+
+}