 .../hal/src/Normalizer/FieldItemNormalizer.php     |  7 +-
 .../hal/tests/src/Kernel/FileNormalizeTest.php     | 14 ----
 .../hal/tests/src/Kernel/NormalizerTestBase.php    | 18 +----
 .../EntityResource/Node/NodeResourceTestBase.php   |  2 +-
 .../config/install/serialization.settings.yml      |  8 +++
 .../config/schema/serialization.schema.yml         | 10 +++
 core/modules/serialization/serialization.install   | 25 +++++++
 .../serialization/serialization.services.yml       |  4 ++
 .../src/Normalizer/ComplexDataNormalizer.php       |  1 +
 .../src/Normalizer/PrimitiveDataNormalizer.php     | 26 +++++++
 .../RegisterSerializationClassesCompilerPass.php   | 35 +++++++++
 .../tests/src/Kernel/EntitySerializationTest.php   |  5 +-
 .../Normalizer/PrimitiveDataNormalizerTest.php     | 83 ++++++++++++++++++++++
 13 files changed, 203 insertions(+), 35 deletions(-)

diff --git a/core/modules/hal/src/Normalizer/FieldItemNormalizer.php b/core/modules/hal/src/Normalizer/FieldItemNormalizer.php
index 376a444..cea8c5e 100644
--- a/core/modules/hal/src/Normalizer/FieldItemNormalizer.php
+++ b/core/modules/hal/src/Normalizer/FieldItemNormalizer.php
@@ -21,7 +21,12 @@ class FieldItemNormalizer extends NormalizerBase {
    * {@inheritdoc}
    */
   public function normalize($field_item, $format = NULL, array $context = array()) {
-    $values = $field_item->toArray();
+    $values = [];
+    /** @var \Drupal\Core\TypedData\TypedDataInterface $data_item */
+    foreach ($field_item as $property => $data_item) {
+      $values[$property] = $this->serializer->normalize($data_item, $format, $context);
+    }
+
     if (isset($context['langcode'])) {
       $values['lang'] = $context['langcode'];
     }
diff --git a/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php b/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php
index 42d7a36..199c39e 100644
--- a/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php
+++ b/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php
@@ -33,20 +33,6 @@ class FileNormalizeTest extends NormalizerTestBase {
   protected function setUp() {
     parent::setUp();
     $this->installEntitySchema('file');
-
-    $entity_manager = \Drupal::entityManager();
-    $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend(), \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack'), \Drupal::service('entity_type.bundle.info')), new RelationLinkManager(new MemoryBackend(), $entity_manager, \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack')));
-
-    // Set up the mock serializer.
-    $normalizers = array(
-      new FieldItemNormalizer(),
-      new FileEntityNormalizer($entity_manager, \Drupal::httpClient(), $link_manager, \Drupal::moduleHandler()),
-    );
-
-    $encoders = array(
-      new JsonEncoder(),
-    );
-    $this->serializer = new Serializer($normalizers, $encoders);
   }
 
 
diff --git a/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php b/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php
index 5867476..34745e3 100644
--- a/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php
+++ b/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php
@@ -130,23 +130,7 @@ protected function setUp() {
       'translatable' => TRUE,
     ])->save();
 
-    $entity_manager = \Drupal::entityManager();
-    $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend(), \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack'), \Drupal::service('entity_type.bundle.info')), new RelationLinkManager(new MemoryBackend(), $entity_manager, \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack')));
-
-    $chain_resolver = new ChainEntityResolver(array(new UuidResolver($entity_manager), new TargetIdResolver()));
-
-    // Set up the mock serializer.
-    $normalizers = array(
-      new ContentEntityNormalizer($link_manager, $entity_manager, \Drupal::moduleHandler()),
-      new EntityReferenceItemNormalizer($link_manager, $chain_resolver),
-      new FieldItemNormalizer(),
-      new FieldNormalizer(),
-    );
-
-    $encoders = array(
-      new JsonEncoder(),
-    );
-    $this->serializer = new Serializer($normalizers, $encoders);
+    $this->serializer = $this->container->get('serializer');
   }
 
 }
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
index d7651c4..18d7bbd 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
@@ -136,7 +136,7 @@ protected function getExpectedNormalizedEntity() {
       ],
       'sticky' => [
         [
-          'value' => '0',
+          'value' => FALSE,
         ],
       ],
       'revision_timestamp' => [
diff --git a/core/modules/serialization/config/install/serialization.settings.yml b/core/modules/serialization/config/install/serialization.settings.yml
new file mode 100644
index 0000000..6af2c71
--- /dev/null
+++ b/core/modules/serialization/config/install/serialization.settings.yml
@@ -0,0 +1,8 @@
+# Before Drupal 8.3, typed data primitive values were normalized as strings, as
+# this was usually returned from database storage. A primitive data normalizer
+# has been introduced to get the casted value instead. If this module is
+# installed the new behaviour will be the default. For modules updating this BC
+# option will be enabled.
+# @see serialization_update_8301()
+# @see https://www.drupal.org/node/2751325
+bc_primitive_data_normalizer: false
diff --git a/core/modules/serialization/config/schema/serialization.schema.yml b/core/modules/serialization/config/schema/serialization.schema.yml
new file mode 100644
index 0000000..31a46d9
--- /dev/null
+++ b/core/modules/serialization/config/schema/serialization.schema.yml
@@ -0,0 +1,10 @@
+serialization.settings:
+  type: config_object
+  label: 'Serialization settings'
+  mapping:
+    bc_primitive_data_normalizer:
+      type: boolean
+      label: 'Whether the pre Drupal 8.3.x behavior of returning non-casted values from typed data (primitive) items.'
+    bc_timestamp_normalizer_unix:
+      type: boolean
+      label: 'Whether the pre Drupal 8.3.x behavior of returning non-casted values from typed data (primitive) items.'
diff --git a/core/modules/serialization/serialization.install b/core/modules/serialization/serialization.install
new file mode 100644
index 0000000..9f712ef
--- /dev/null
+++ b/core/modules/serialization/serialization.install
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the serialization module.
+ */
+
+/**
+ * @defgroup updates-8.2.x-to-8.3.x
+ * @{
+ * Update functions from 8.2.x to 8.3.x.
+ */
+
+/**
+ * Enable BC for non-casted values: continue to return string values.
+ */
+function serialization_update_8301() {
+  $config_factory = \Drupal::configFactory();
+  $rest_settings = $config_factory->getEditable('serialization.settings');
+  $rest_settings->set('bc_primitive_data_normalizer', TRUE)->save(TRUE);
+}
+
+/**
+ * @} End of "defgroup updates-8.2.x-to-8.3.x".
+ */
diff --git a/core/modules/serialization/serialization.services.yml b/core/modules/serialization/serialization.services.yml
index 8b570c0..6bfabdb 100644
--- a/core/modules/serialization/serialization.services.yml
+++ b/core/modules/serialization/serialization.services.yml
@@ -17,6 +17,10 @@ services:
     tags:
       - { name: normalizer }
     arguments: ['@entity.manager']
+  serializer.normalizer.primitive_data:
+    class: Drupal\serialization\Normalizer\PrimitiveDataNormalizer
+    tags:
+      - { name: normalizer, priority: 5 }
   serializer.normalizer.complex_data:
     class: Drupal\serialization\Normalizer\ComplexDataNormalizer
     tags:
diff --git a/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php b/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php
index 5062738..ef07d11 100644
--- a/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php
+++ b/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php
@@ -26,6 +26,7 @@ class ComplexDataNormalizer extends NormalizerBase {
    */
   public function normalize($object, $format = NULL, array $context = array()) {
     $attributes = array();
+    /** @var \Drupal\Core\TypedData\TypedDataInterface $field */
     foreach ($object as $name => $field) {
       $attributes[$name] = $this->serializer->normalize($field, $format, $context);
     }
diff --git a/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php b/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php
new file mode 100644
index 0000000..0bffd0e
--- /dev/null
+++ b/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\serialization\Normalizer;
+
+use Drupal\Core\TypedData\PrimitiveInterface;
+
+/**
+ * Converts primitive data objects to their casted values.
+ */
+class PrimitiveDataNormalizer extends NormalizerBase {
+
+  /**
+   * The interface or class that this Normalizer supports.
+   *
+   * @var string
+   */
+  protected $supportedInterfaceOrClass = PrimitiveInterface::class;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function normalize($object, $format = NULL, array $context = array()) {
+    return $object->getValue() === NULL ? NULL : $object->getCastedValue();
+  }
+
+}
diff --git a/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php b/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php
index f4611f3..028dbd0 100644
--- a/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php
+++ b/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\serialization;
 
+use Drupal\Core\Config\BootstrapConfigStorageFactory;
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -22,6 +23,10 @@ public function process(ContainerBuilder $container) {
 
     // Retrieve registered Normalizers and Encoders from the container.
     foreach ($container->findTaggedServiceIds('normalizer') as $id => $attributes) {
+      if ($this->normalizerShouldBeSkipped($id)) {
+        continue;
+      }
+
       $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
       $normalizers[$priority][] = new Reference($id);
     }
@@ -54,6 +59,36 @@ public function process(ContainerBuilder $container) {
   }
 
   /**
+   * Determines whether a given normalizer should be skipped and not added.
+   *
+   * @param string $id
+   *   The normalizer ID.
+   *
+   * @return bool
+   */
+  protected function normalizerShouldBeSkipped($id) {
+    switch($id) {
+      case 'serializer.normalizer.primitive_data':
+        return $this->normalizerBcSettingIsEnabled('bc_primitive_data_normalizer');
+      default:
+        // Default to FALSE as most normalizers will be added.
+        return FALSE;
+    }
+  }
+
+  /**
+   * Returns whether a normalizer BC setting is disabled or not.
+   *
+   * @param string $key
+   *
+   * @return bool
+   */
+  protected function normalizerBcSettingIsEnabled($key) {
+    $settings = BootstrapConfigStorageFactory::get()->read('serialization.settings');
+    return !empty($settings[$key]);
+  }
+
+  /**
    * Sorts by priority.
    *
    * Order services from highest priority number to lowest (reverse sorting).
diff --git a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
index 0f84d3d..18c652e 100644
--- a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
+++ b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
@@ -110,7 +110,8 @@ public function testNormalize() {
       ),
       'user_id' => array(
         array(
-          'target_id' => $this->user->id(),
+          // id() will return the string value as it comes from the database.
+          'target_id' => (int) $this->user->id(),
           'target_type' => $this->user->getEntityTypeId(),
           'target_uuid' => $this->user->uuid(),
           'url' => $this->user->url(),
@@ -134,7 +135,7 @@ public function testNormalize() {
     $normalized = $this->serializer->normalize($this->entity);
 
     foreach (array_keys($expected) as $fieldName) {
-      $this->assertEqual($expected[$fieldName], $normalized[$fieldName], "ComplexDataNormalizer produces expected array for $fieldName.");
+      $this->assertSame($expected[$fieldName], $normalized[$fieldName], "Normalization produces expected array for $fieldName.");
     }
     $this->assertEqual(array_diff_key($normalized, $expected), array(), 'No unexpected data is added to the normalized array.');
   }
diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/PrimitiveDataNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/PrimitiveDataNormalizerTest.php
new file mode 100644
index 0000000..ff75d89
--- /dev/null
+++ b/core/modules/serialization/tests/src/Unit/Normalizer/PrimitiveDataNormalizerTest.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\Tests\serialization\Unit\Normalizer;
+
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\TypedData\Plugin\DataType\BooleanData;
+use Drupal\Core\TypedData\Plugin\DataType\IntegerData;
+use Drupal\Core\TypedData\Plugin\DataType\StringData;
+use Drupal\Tests\UnitTestCase;
+use Drupal\serialization\Normalizer\PrimitiveDataNormalizer;
+
+/**
+ * @coversDefaultClass \Drupal\serialization\Normalizer\PrimitiveDataNormalizer
+ * @group serialization
+ */
+class PrimitiveDataNormalizerTest extends UnitTestCase {
+
+  /**
+   * The TypedDataNormalizer instance.
+   *
+   * @var \Drupal\serialization\Normalizer\TypedDataNormalizer
+   */
+  protected $normalizer;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->normalizer = new PrimitiveDataNormalizer();
+  }
+
+  /**
+   * @covers ::supportsNormalization
+   * @dataProvider dataProviderPrimitiveData
+   */
+  public function testSupportsNormalization($primitive_data, $value) {
+    $this->assertTrue($this->normalizer->supportsNormalization($primitive_data));
+  }
+
+  /**
+   * @covers ::supportsNormalization
+   */
+  public function testSupportsNormalizationFail() {
+    // Test that an object not implementing PrimitiveInterface fails.
+    $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
+  }
+
+  /**
+   * @covers ::normalize
+   * @dataProvider dataProviderPrimitiveData
+   */
+  public function testNormalize($primitive_data, $value) {
+    $this->assertSame($value, $this->normalizer->normalize($primitive_data));
+  }
+
+  /**
+   * Data provider for testNormalize().
+   */
+  public function dataProviderPrimitiveData() {
+    $data = [];
+
+    $definition = DataDefinition::createFromDataType('string');
+    $string = new StringData($definition, 'string');
+    $string->setValue('test');
+
+    $data['string'] = [$string, 'test'];
+
+    $definition = DataDefinition::createFromDataType('integer');
+    $integer = new IntegerData($definition, 'integer');
+    $integer->setValue(5);
+
+    $data['integer'] = [$integer, 5];
+
+    $definition = DataDefinition::createFromDataType('boolean');
+    $boolean = new BooleanData($definition, 'boolean');
+    $boolean->setValue(TRUE);
+
+    $data['boolean'] = [$boolean, TRUE];
+
+    return $data;
+  }
+
+}
