diff --git a/core/core.services.yml b/core/core.services.yml
index 40c6a8a6b44e688509b5c73529ca6ed1ef49f59e..31733463b43082420be95b476b36858915f46610 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -331,9 +331,11 @@ services:
     arguments: ['@config.storage', 'config/schema', '', true, '%install_profile%']
   config.typed:
     class: Drupal\Core\Config\TypedConfigManager
-    arguments: ['@config.storage', '@config.storage.schema', '@cache.discovery', '@module_handler']
+    arguments: ['@config.storage', '@config.storage.schema', '@cache.discovery', '@module_handler', '@class_resolver']
     tags:
       - { name: plugin_manager_cache_clear }
+    calls:
+      - [setValidationConstraintManager, ['@validation.constraint']]
   context.handler:
     class: Drupal\Core\Plugin\Context\ContextHandler
     arguments: ['@typed_data_manager']
diff --git a/core/lib/Drupal/Core/Config/Schema/ArrayElement.php b/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
index c5076554475dd4a79933ac399207b31d34b7be42..b38a94b3c22a0aded5ee388735b13307e11790e1 100644
--- a/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
+++ b/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
@@ -2,10 +2,12 @@
 
 namespace Drupal\Core\Config\Schema;
 
+use Drupal\Core\TypedData\ComplexDataInterface;
+
 /**
  * Defines a generic configuration element that contains multiple properties.
  */
-abstract class ArrayElement extends Element implements \IteratorAggregate, TypedConfigInterface {
+abstract class ArrayElement extends Element implements \IteratorAggregate, TypedConfigInterface, ComplexDataInterface {
 
   /**
    * Parsed elements.
@@ -161,4 +163,25 @@ public function isNullable() {
     return isset($this->definition['nullable']) && $this->definition['nullable'] == TRUE;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function set($property_name, $value, $notify = TRUE) {
+    $this->value[$property_name] = $value;
+    // Config schema elements do not make use of notifications. Thus, we skip
+    // notifying parents.
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProperties($include_computed = FALSE) {
+    $properties = [];
+    foreach (array_keys($this->value) as $name) {
+      $properties[$name] = $this->get($name);
+    }
+    return $properties;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Config/Schema/Sequence.php b/core/lib/Drupal/Core/Config/Schema/Sequence.php
index ce8dc1bc0b2690412b504d46922cc61b87330e92..547969bbccecc7b98d32f16e1d508b305ea37da0 100644
--- a/core/lib/Drupal/Core/Config/Schema/Sequence.php
+++ b/core/lib/Drupal/Core/Config/Schema/Sequence.php
@@ -10,6 +10,12 @@
  *
  * Read https://www.drupal.org/node/1905070 for more details about configuration
  * schema, types and type resolution.
+ *
+ * Note that sequences implement the typed data ComplexDataInterface (via the
+ * parent ArrayElement) rather than the ListInterface. This is because sequences
+ * may have named keys, which is not supported by ListInterface. From the typed
+ * data API perspective sequences are handled as ordered mappings without
+ * metadata about existing properties.
  */
 class Sequence extends ArrayElement {
 
diff --git a/core/lib/Drupal/Core/Config/TypedConfigManager.php b/core/lib/Drupal/Core/Config/TypedConfigManager.php
index 22795ad6fffdc4554a568de3ffa25cd1a4c4620d..bd5fa99812b16817e2f1d5eadaa893613e5f4f95 100644
--- a/core/lib/Drupal/Core/Config/TypedConfigManager.php
+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
 use Drupal\Core\Config\Schema\ConfigSchemaDiscovery;
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\Core\Config\Schema\Undefined;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\TypedData\TypedDataManager;
@@ -45,13 +46,18 @@ class TypedConfigManager extends TypedDataManager implements TypedConfigManagerI
    *   The storage object to use for reading schema data
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
    *   The cache backend to use for caching the definitions.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   (optional) The class resolver.
    */
-  public function __construct(StorageInterface $configStorage, StorageInterface $schemaStorage, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler) {
+  public function __construct(StorageInterface $configStorage, StorageInterface $schemaStorage, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler, ClassResolverInterface $class_resolver = NULL) {
     $this->configStorage = $configStorage;
     $this->schemaStorage = $schemaStorage;
     $this->setCacheBackend($cache, 'typed_config_definitions');
     $this->alterInfo('config_schema_info');
     $this->moduleHandler = $module_handler;
+    $this->classResolver = $class_resolver ?: \Drupal::service('class_resolver');
   }
 
   /**
@@ -184,6 +190,7 @@ protected function getDefinitionWithReplacements($base_plugin_id, array $replace
     $definition += [
       'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
       'type' => $type,
+      'unwrap_for_canonical_representation' => TRUE,
     ];
     return $definition;
   }
diff --git a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
index 5194d85e9a2a5d4a927fb79442f89e2574ef4538..37cc103875428f65987ddf52196a1e97e8d6916c 100644
--- a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
+++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
@@ -585,7 +585,7 @@ protected function getFieldItemClass() {
   public function __sleep() {
     // Do not serialize the statically cached property definitions.
     $vars = get_object_vars($this);
-    unset($vars['propertyDefinitions']);
+    unset($vars['propertyDefinitions'], $vars['typedDataManager']);
     return array_keys($vars);
   }
 
diff --git a/core/lib/Drupal/Core/TypedData/ComplexDataDefinitionBase.php b/core/lib/Drupal/Core/TypedData/ComplexDataDefinitionBase.php
index 0acdba468d865d5fefae0d6e18aaac9d9ae2d2a1..0002497e9480daf61512bb3ce83895da8a6809b7 100644
--- a/core/lib/Drupal/Core/TypedData/ComplexDataDefinitionBase.php
+++ b/core/lib/Drupal/Core/TypedData/ComplexDataDefinitionBase.php
@@ -42,7 +42,7 @@ public function getMainPropertyName() {
   public function __sleep() {
     // Do not serialize the cached property definitions.
     $vars = get_object_vars($this);
-    unset($vars['propertyDefinitions']);
+    unset($vars['propertyDefinitions'], $vars['typedDataManager']);
     return array_keys($vars);
   }
 
diff --git a/core/lib/Drupal/Core/TypedData/DataDefinition.php b/core/lib/Drupal/Core/TypedData/DataDefinition.php
index 7eec1a90c457736e8ec2a1e3017ae124578633b0..52a4394cd78e95d9e78837530e69cd78e1a54aa0 100644
--- a/core/lib/Drupal/Core/TypedData/DataDefinition.php
+++ b/core/lib/Drupal/Core/TypedData/DataDefinition.php
@@ -7,6 +7,8 @@
  */
 class DataDefinition implements DataDefinitionInterface, \ArrayAccess {
 
+  use TypedDataTrait;
+
   /**
    * The array holding values for all definition keys.
    *
@@ -258,7 +260,7 @@ public function setSetting($setting_name, $value) {
    */
   public function getConstraints() {
     $constraints = isset($this->definition['constraints']) ? $this->definition['constraints'] : [];
-    $constraints += \Drupal::typedDataManager()->getDefaultConstraints($this);
+    $constraints += $this->getTypedDataManager()->getDefaultConstraints($this);
     return $constraints;
   }
 
@@ -340,4 +342,14 @@ public function toArray() {
     return $this->definition;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function __sleep() {
+    // Never serialize the typed data manager.
+    $vars = get_object_vars($this);
+    unset($vars['typedDataManager']);
+    return array_keys($vars);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
index 8c4f265ebdb4dfdb9a242dbab6fa2d29e2af44f0..dbf0d5fec544b28be2684756fe0261d9d08f8a83 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
@@ -117,7 +117,13 @@ public function createDataDefinition($data_type) {
       throw new \InvalidArgumentException("Invalid data type '$data_type' has been given");
     }
     $class = $type_definition['definition_class'];
-    return $class::createFromDataType($data_type);
+    $data_definition = $class::createFromDataType($data_type);
+
+    if (method_exists($data_definition, 'setTypedDataManager')) {
+      $data_definition->setTypedDataManager($this);
+    }
+
+    return $data_definition;
   }
 
   /**
diff --git a/core/modules/config/tests/config_test/config/install/config_test.validation.yml b/core/modules/config/tests/config_test/config/install/config_test.validation.yml
new file mode 100644
index 0000000000000000000000000000000000000000..37414689fd113575d03421a5b02a7575e64fb778
--- /dev/null
+++ b/core/modules/config/tests/config_test/config/install/config_test.validation.yml
@@ -0,0 +1,7 @@
+llama: llama
+cat:
+  type: kitten
+  count: 2
+giraffe:
+  hum1: hum1
+  hum2: hum2
diff --git a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml
index e5b75d14301edcafda102d19f47fd168c48eb6b4..2cd68a4361e5a0c8bdc766966a262829e2148373 100644
--- a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml
+++ b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml
@@ -158,3 +158,39 @@ config_test.foo:
 
 config_test.bar:
   type: config_test.foo
+
+config_test.validation:
+  type: config_object
+  label: 'Configuration type'
+  constraints:
+    Callback:
+      callback: [\Drupal\config_test\ConfigValidation, validateMapping]
+  mapping:
+    llama:
+      type: string
+      constraints:
+        Callback:
+          callback: [\Drupal\config_test\ConfigValidation, validateLlama]
+    cat:
+      type: mapping
+      mapping:
+        type:
+          type: string
+          constraints:
+            Callback:
+              callback: [\Drupal\config_test\ConfigValidation, validateCats]
+        count:
+          type: integer
+          constraints:
+            Callback:
+              callback: [\Drupal\config_test\ConfigValidation, validateCatCount]
+    giraffe:
+      type: sequence
+      constraints:
+        Callback:
+          callback: [\Drupal\config_test\ConfigValidation, validateSequence]
+      sequence:
+        type: string
+        constraints:
+          Callback:
+            callback: [\Drupal\config_test\ConfigValidation, validateGiraffes]
diff --git a/core/modules/config/tests/config_test/src/ConfigValidation.php b/core/modules/config/tests/config_test/src/ConfigValidation.php
new file mode 100644
index 0000000000000000000000000000000000000000..f9493b661706a5c26dcfd5034fe920515918ea4e
--- /dev/null
+++ b/core/modules/config/tests/config_test/src/ConfigValidation.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Drupal\config_test;
+
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+/**
+ * Provides a collection of validation callbacks for testing purposes.
+ */
+class ConfigValidation {
+
+  /**
+   * Validates a llama.
+   *
+   * @param string $string
+   *   The string to validate.
+   * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
+   *   The validation execution context.
+   */
+  public static function validateLlama($string, ExecutionContextInterface $context) {
+    if (!in_array($string, ['llama', 'alpaca', 'guanaco', 'vicuña'], TRUE)) {
+      $context->addViolation('no valid llama');
+    }
+  }
+
+  /**
+   * Validates cats.
+   *
+   * @param string $string
+   *   The string to validate.
+   * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
+   *   The validation execution context.
+   */
+  public static function validateCats($string, ExecutionContextInterface $context) {
+    if (!in_array($string, ['kitten', 'cats', 'nyans'])) {
+      $context->addViolation('no valid cat');
+    }
+  }
+
+  /**
+   * Validates a number.
+   *
+   * @param int $count
+   *   The integer to validate.
+   * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
+   *   The validation execution context.
+   */
+  public static function validateCatCount($count, ExecutionContextInterface $context) {
+    if ($count <= 1) {
+      $context->addViolation('no enough cats');
+    }
+  }
+
+  /**
+   * Validates giraffes.
+   *
+   * @param string $string
+   *   The string to validate.
+   * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
+   *   The validation execution context.
+   */
+  public static function validateGiraffes($string, ExecutionContextInterface $context) {
+    if (strpos($string, 'hum') !== 0) {
+      $context->addViolation('Giraffes just hum');
+    }
+  }
+
+  /**
+   * Validates a mapping.
+   *
+   * @param array $mapping
+   *   The data to validate.
+   * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
+   *   The validation execution context.
+   */
+  public static function validateMapping($mapping, ExecutionContextInterface $context) {
+    if ($diff = array_diff(array_keys($mapping), ['llama', 'cat', 'giraffe', '_core'])) {
+      $context->addViolation('Missing giraffe.');
+    }
+  }
+
+  /**
+   * Validates a sequence.
+   *
+   * @param array $sequence
+   *   The data to validate.
+   * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
+   *   The validation execution context.
+   */
+  public static function validateSequence($sequence, ExecutionContextInterface $context) {
+    if (isset($sequence['invalid-key'])) {
+      $context->addViolation('Invalid giraffe key.');
+    }
+  }
+
+}
diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml
index c23ed7e0aeeb94844f64c11e83dc8c9fd875bac2..dce4e975bcf68a310b1a4ff266271eff39ea536e 100644
--- a/core/modules/system/config/schema/system.schema.yml
+++ b/core/modules/system/config/schema/system.schema.yml
@@ -7,6 +7,8 @@ system.site:
     uuid:
       type: string
       label: 'Site UUID'
+      constraints:
+        NotNull: []
     name:
       type: label
       label: 'Site name'
diff --git a/core/modules/views/tests/src/Kernel/TestViewsTest.php b/core/modules/views/tests/src/Kernel/TestViewsTest.php
index f9aa67342104c28522011d0ba6e9948d51000713..dcd1859be34595eb1779ca4909c802a6535431ce 100644
--- a/core/modules/views/tests/src/Kernel/TestViewsTest.php
+++ b/core/modules/views/tests/src/Kernel/TestViewsTest.php
@@ -34,7 +34,8 @@ public function testDefaultConfig() {
       \Drupal::service('config.storage'),
       new TestInstallStorage(InstallStorage::CONFIG_SCHEMA_DIRECTORY),
       \Drupal::service('cache.discovery'),
-      \Drupal::service('module_handler')
+      \Drupal::service('module_handler'),
+      \Drupal::service('class_resolver')
     );
 
     // Create a configuration storage with access to default configuration in
diff --git a/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php b/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5881a144124ef5233faa468c655a297f49dc07b8
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
@@ -0,0 +1,146 @@
+<?php
+
+namespace Drupal\KernelTests\Config;
+
+use Drupal\Core\Config\Schema\SequenceDataDefinition;
+use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
+use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\Type\IntegerInterface;
+use Drupal\Core\TypedData\Type\StringInterface;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+
+/**
+ * Tests config validation mechanism.
+ *
+ * @group Config
+ */
+class TypedConfigTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['config_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installConfig('config_test');
+  }
+
+  /**
+   * Verifies that the Typed Data API is implemented correctly.
+   */
+  public function testTypedDataAPI() {
+    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
+    $typed_config_manager = \Drupal::service('config.typed');
+    /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
+    $typed_config = $typed_config_manager->get('config_test.validation');
+
+    // Test a primitive.
+    $string_data = $typed_config->get('llama');
+    $this->assertInstanceOf(StringInterface::class, $string_data);
+    $this->assertEquals('llama', $string_data->getValue());
+
+    // Test complex data.
+    $mapping = $typed_config->get('cat');
+    /** @var \Drupal\Core\TypedData\ComplexDataInterface $mapping */
+    $this->assertInstanceOf(ComplexDataInterface::class, $mapping);
+    $this->assertInstanceOf(StringInterface::class, $mapping->get('type'));
+    $this->assertEquals('kitten', $mapping->get('type')->getValue());
+    $this->assertInstanceOf(IntegerInterface::class, $mapping->get('count'));
+    $this->assertEquals(2, $mapping->get('count')->getValue());
+    // Verify the item metadata is available.
+    $this->assertInstanceOf(ComplexDataDefinitionInterface::class, $mapping->getDataDefinition());
+    $this->assertArrayHasKey('type', $mapping->getProperties());
+    $this->assertArrayHasKey('count', $mapping->getProperties());
+
+    // Test accessing sequences.
+    $sequence = $typed_config->get('giraffe');
+    /** @var \Drupal\Core\TypedData\ListInterface $sequence */
+    $this->assertInstanceOf(ComplexDataInterface::class, $sequence);
+    $this->assertInstanceOf(StringInterface::class, $sequence->get('hum1'));
+    $this->assertEquals('hum1', $sequence->get('hum1')->getValue());
+    $this->assertEquals('hum2', $sequence->get('hum2')->getValue());
+    $this->assertEquals(2, count($sequence->getIterator()));
+    // Verify the item metadata is available.
+    $this->assertInstanceOf(SequenceDataDefinition::class, $sequence->getDataDefinition());
+  }
+
+  /**
+   * Tests config validation via the Typed Data API.
+   */
+  public function testSimpleConfigValidation() {
+    $config = \Drupal::configFactory()->getEditable('config_test.validation');
+    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
+    $typed_config_manager = \Drupal::service('config.typed');
+    /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
+    $typed_config = $typed_config_manager->get('config_test.validation');
+
+    $result = $typed_config->validate();
+    $this->assertInstanceOf(ConstraintViolationListInterface::class, $result);
+    $this->assertEmpty($result);
+
+    // Test constraints on primitive types.
+    $config->set('llama', 'elephant');
+    $config->save();
+
+    $typed_config = $typed_config_manager->get('config_test.validation');
+    $result = $typed_config->validate();
+    // Its not a valid llama anymore.
+    $this->assertCount(1, $result);
+    $this->assertEquals('no valid llama', $result->get(0)->getMessage());
+
+    // Test constraints on mapping.
+    $config->set('llama', 'llama');
+    $config->set('cat.type', 'nyans');
+    $config->save();
+
+    $typed_config = $typed_config_manager->get('config_test.validation');
+    $result = $typed_config->validate();
+    $this->assertEmpty($result);
+
+    // Test constrains on nested mapping.
+    $config->set('cat.type', 'miaus');
+    $config->save();
+
+    $typed_config = $typed_config_manager->get('config_test.validation');
+    $result = $typed_config->validate();
+    $this->assertCount(1, $result);
+    $this->assertEquals('no valid cat', $result->get(0)->getMessage());
+
+    // Test constrains on sequences elements.
+    $config->set('cat.type', 'nyans');
+    $config->set('giraffe', ['muh', 'hum2']);
+    $config->save();
+    $typed_config = $typed_config_manager->get('config_test.validation');
+    $result = $typed_config->validate();
+    $this->assertCount(1, $result);
+    $this->assertEquals('Giraffes just hum', $result->get(0)->getMessage());
+
+    // Test constrains on the sequence itself.
+    $config->set('giraffe', ['hum', 'hum2', 'invalid-key' => 'hum']);
+    $config->save();
+
+    $typed_config = $typed_config_manager->get('config_test.validation');
+    $result = $typed_config->validate();
+    $this->assertCount(1, $result);
+    $this->assertEquals('giraffe', $result->get(0)->getPropertyPath());
+    $this->assertEquals('Invalid giraffe key.', $result->get(0)->getMessage());
+
+    // Validates mapping.
+    $typed_config = $typed_config_manager->get('config_test.validation');
+    $value = $typed_config->getValue();
+    unset($value['giraffe']);
+    $value['elephant'] = 'foo';
+    $typed_config->setValue($value);
+    $result = $typed_config->validate();
+    $this->assertCount(1, $result);
+    $this->assertEquals('', $result->get(0)->getPropertyPath());
+    $this->assertEquals('Missing giraffe.', $result->get(0)->getMessage());
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
index 96bd18c0a2ad48060b5724d9d554c586daeaafbe..fb3573eec9d603d10078bd845b96883441e8eb04 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
@@ -47,6 +47,7 @@ public function testSchemaMapping() {
     $expected['class'] = Undefined::class;
     $expected['type'] = 'undefined';
     $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $this->assertEqual($definition, $expected, 'Retrieved the right metadata for nonexistent configuration.');
 
     // Configuration file without schema will return Undefined as well.
@@ -67,6 +68,7 @@ public function testSchemaMapping() {
     $expected['mapping']['testlist'] = ['label' => 'Test list'];
     $expected['type'] = 'config_schema_test.someschema';
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $this->assertEqual($definition, $expected, 'Retrieved the right metadata for configuration with only some schema.');
 
     // Check type detection on elements with undefined types.
@@ -77,6 +79,7 @@ public function testSchemaMapping() {
     $expected['class'] = Undefined::class;
     $expected['type'] = 'undefined';
     $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $this->assertEqual($definition, $expected, 'Automatic type detected for a scalar is undefined.');
     $definition = $config->get('testlist')->getDataDefinition()->toArray();
     $expected = [];
@@ -84,6 +87,7 @@ public function testSchemaMapping() {
     $expected['class'] = Undefined::class;
     $expected['type'] = 'undefined';
     $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $this->assertEqual($definition, $expected, 'Automatic type detected for a list is undefined.');
     $definition = $config->get('testnoschema')->getDataDefinition()->toArray();
     $expected = [];
@@ -91,6 +95,7 @@ public function testSchemaMapping() {
     $expected['class'] = Undefined::class;
     $expected['type'] = 'undefined';
     $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $this->assertEqual($definition, $expected, 'Automatic type detected for an undefined integer is undefined.');
 
     // Simple case, straight metadata.
@@ -109,6 +114,7 @@ public function testSchemaMapping() {
     $expected['mapping']['_core']['type'] = '_core_config_info';
     $expected['type'] = 'system.maintenance';
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $this->assertEqual($definition, $expected, 'Retrieved the right metadata for system.maintenance');
 
     // Mixed schema with ignore elements.
@@ -139,6 +145,7 @@ public function testSchemaMapping() {
       'type' => 'integer',
     ];
     $expected['type'] = 'config_schema_test.ignore';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
 
     $this->assertEqual($definition, $expected);
 
@@ -149,6 +156,7 @@ public function testSchemaMapping() {
     $expected['label'] = 'Irrelevant';
     $expected['class'] = Ignore::class;
     $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $this->assertEqual($definition, $expected);
     $definition = \Drupal::service('config.typed')->get('config_schema_test.ignore')->get('indescribable')->getDataDefinition()->toArray();
     $expected['label'] = 'Indescribable';
@@ -160,6 +168,7 @@ public function testSchemaMapping() {
     $expected['label'] = 'Image style';
     $expected['class'] = Mapping::class;
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $expected['mapping']['name']['type'] = 'string';
     $expected['mapping']['uuid']['type'] = 'string';
     $expected['mapping']['uuid']['label'] = 'UUID';
@@ -193,6 +202,7 @@ public function testSchemaMapping() {
     $expected['label'] = 'Image scale';
     $expected['class'] = Mapping::class;
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $expected['mapping']['width']['type'] = 'integer';
     $expected['mapping']['width']['label'] = 'Width';
     $expected['mapping']['height']['type'] = 'integer';
@@ -220,6 +230,7 @@ public function testSchemaMapping() {
     $expected['label'] = 'Mapping';
     $expected['class'] = Mapping::class;
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $expected['mapping'] = [
       'integer' => ['type' => 'integer'],
       'string' => ['type' => 'string'],
@@ -241,6 +252,7 @@ public function testSchemaMapping() {
     $expected['mapping']['testdescription']['label'] = 'Description';
     $expected['type'] = 'config_schema_test.someschema.somemodule.*.*';
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
 
     $this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.someschema.somemodule.section_one.subsection');
 
@@ -263,6 +275,7 @@ public function testSchemaMappingWithParents() {
       'label' => 'Test item nested one level',
       'class' => StringData::class,
       'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
+      'unwrap_for_canonical_representation' => TRUE,
     ];
     $this->assertEqual($definition, $expected);
 
@@ -274,6 +287,7 @@ public function testSchemaMappingWithParents() {
       'label' => 'Test item nested two levels',
       'class' => StringData::class,
       'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
+      'unwrap_for_canonical_representation' => TRUE,
     ];
     $this->assertEqual($definition, $expected);
 
@@ -285,6 +299,7 @@ public function testSchemaMappingWithParents() {
       'label' => 'Test item nested three levels',
       'class' => StringData::class,
       'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
+      'unwrap_for_canonical_representation' => TRUE,
     ];
     $this->assertEqual($definition, $expected);
   }
@@ -475,6 +490,7 @@ public function testSchemaFallback() {
     $expected['label'] = 'Schema wildcard fallback test';
     $expected['class'] = Mapping::class;
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
+    $expected['unwrap_for_canonical_representation'] = TRUE;
     $expected['mapping']['langcode']['type'] = 'string';
     $expected['mapping']['langcode']['label'] = 'Language code';
     $expected['mapping']['_core']['type'] = '_core_config_info';
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityTypedDataDefinitionTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypedDataDefinitionTest.php
index 9d5095d1494b9712d51bd84c0fdb62a72b9784c7..5e755d95842e30f04dbfb62b014fad7404d6d461 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityTypedDataDefinitionTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypedDataDefinitionTest.php
@@ -101,8 +101,8 @@ public function testEntities() {
 
     // Test that the definition factory creates the right definitions for all
     // entity data types variants.
-    $this->assertEqual($this->typedDataManager->createDataDefinition('entity'), EntityDataDefinition::create());
-    $this->assertEqual($this->typedDataManager->createDataDefinition('entity:node'), EntityDataDefinition::create('node'));
+    $this->assertEqual(serialize($this->typedDataManager->createDataDefinition('entity')), serialize(EntityDataDefinition::create()));
+    $this->assertEqual(serialize($this->typedDataManager->createDataDefinition('entity:node')), serialize(EntityDataDefinition::create('node')));
 
     // Config entities don't support typed data.
     $entity_definition = EntityDataDefinition::create('node_type');
@@ -123,7 +123,7 @@ public function testEntityReferences() {
     // Test that the definition factory creates the right definition object.
     $reference_definition2 = $this->typedDataManager->createDataDefinition('entity_reference');
     $this->assertTrue($reference_definition2 instanceof DataReferenceDefinitionInterface);
-    $this->assertEqual($reference_definition2, $reference_definition);
+    $this->assertEqual(serialize($reference_definition2), serialize($reference_definition));
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataDefinitionTest.php b/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataDefinitionTest.php
index 8aeab575e924fc4541bc8efca6bc5b61c1ba149e..ad266c28523ef05b3f087144a0513507dd6bada2 100644
--- a/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataDefinitionTest.php
+++ b/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataDefinitionTest.php
@@ -77,7 +77,7 @@ public function testMaps() {
     $map_definition2->setPropertyDefinition('one', DataDefinition::create('string'))
       ->setPropertyDefinition('two', DataDefinition::create('string'))
       ->setPropertyDefinition('three', DataDefinition::create('string'));
-    $this->assertEqual($map_definition, $map_definition2);
+    $this->assertEqual(serialize($map_definition), serialize($map_definition2));
   }
 
   /**
@@ -93,7 +93,7 @@ public function testDataReferences() {
     // Test using the definition factory.
     $language_reference_definition2 = $this->typedDataManager->createDataDefinition('language_reference');
     $this->assertTrue($language_reference_definition2 instanceof DataReferenceDefinitionInterface);
-    $this->assertEqual($language_reference_definition, $language_reference_definition2);
+    $this->assertEqual(serialize($language_reference_definition), serialize($language_reference_definition2));
   }
 
 }