diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 0c7fa28..d124edd 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -157,6 +157,18 @@
   protected $loadedRevisionId;
 
   /**
+   * Whether to serialize the complete entity structure.
+   *
+   * If set to TRUE when the entity is being serialized a deep serialization
+   * will be performed and the flag will be automatically set back to FALSE.
+   *
+   * @var bool
+   *
+   * @see \Drupal\Core\Entity\ContentEntityInterface::setDeepSerialization()
+   */
+  protected $deepSerialization = FALSE;
+
+  /**
    * {@inheritdoc}
    */
   public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = []) {
@@ -443,7 +455,7 @@ public function __sleep() {
     // Get the values of instantiated field objects, only serialize the values.
     foreach ($this->fields as $name => $fields) {
       foreach ($fields as $langcode => $field) {
-        $this->values[$name][$langcode] = $field->getValue();
+        $this->values[$name][$langcode] = $field->getSerializationValue($this->deepSerialization);
       }
     }
     $this->fields = [];
@@ -451,12 +463,26 @@ public function __sleep() {
     $this->languages = NULL;
     $this->clearTranslationCache();
 
+    // As during the serialization process referenced entities might also have
+    // the "deepSerialization" flag set the only place of unsetting it remains
+    // in the sleep method right after the entity is prepared for the
+    // serialization and before executing it.
+    // @see \Drupal\Core\Field\EntityReferenceFieldItemList::getSerializationValue().
+    $this->deepSerialization = FALSE;
+
     return parent::__sleep();
   }
 
   /**
    * {@inheritdoc}
    */
+  public function setDeepSerialization($deep_serialization) {
+    $this->deepSerialization = $deep_serialization;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function id() {
     return $this->getEntityKey('id');
   }
@@ -826,6 +852,7 @@ protected function initializeTranslation($langcode) {
     $translation->enforceIsNew = &$this->enforceIsNew;
     $translation->newRevision = &$this->newRevision;
     $translation->entityKeys = &$this->entityKeys;
+    $translation->deepSerialization = &$this->deepSerialization;
     $translation->translatableEntityKeys = &$this->translatableEntityKeys;
     $translation->translationInitialize = FALSE;
     $translation->typedData = NULL;
@@ -1095,7 +1122,7 @@ public function __clone() {
     // Ensure that the following properties are actually cloned by
     // overwriting the original references with ones pointing to copies of
     // them: enforceIsNew, newRevision, loadedRevisionId, fields, entityKeys,
-    // translatableEntityKeys and values.
+    // translatableEntityKeys, values and deepSerialization.
     $enforce_is_new = $this->enforceIsNew;
     $this->enforceIsNew = &$enforce_is_new;
 
@@ -1117,6 +1144,9 @@ public function __clone() {
     $values = $this->values;
     $this->values = &$values;
 
+    $deepSerialization = $this->deepSerialization;
+    $this->deepSerialization = &$deepSerialization;
+
     foreach ($this->fields as $name => $fields_by_langcode) {
       $this->fields[$name] = [];
       // Untranslatable fields may have multiple references for the same field
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
index d045152..a809cc4 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
@@ -71,4 +71,34 @@ public function getLoadedRevisionId();
    */
   public function updateLoadedRevisionId();
 
+  /**
+   * Flags the entity for deep serialization.
+   *
+   * When an entity is deeply serialized then its field items will have the
+   * opportunity to return also e.g. computed properties in order to serialize
+   * the whole entity structure and cover changes made on computed properties,
+   * which are not yet saved instead of only returning the metadata needed to
+   * re-compute/load the computed property. An example for this are entity
+   * reference fields, which during deep serialization will return their
+   * referenced entities. In this case not only the changes on the main entity
+   * will be serialized, but also the changes made on the referenced entities.
+   *
+   * Example usage:
+   * - Alter the title of a referenced entity and serialize the parent entity
+   * through deep serialization in order to serialize the change made on the
+   * referenced entity.
+   * @code
+   *   $entity->entity_reference_field->entity->setTitle('new entity reference title');
+   *   $entity->setDeepSerialization(TRUE);
+   *   serialize($entity);
+   * @endcode
+   *
+   * @param bool $deep_serialization
+   *   TRUE if deep serialization should be performed when the entity is being
+   *   serialized, FALSE otherwise.
+   *
+   * @return $this
+   */
+  public function setDeepSerialization($deep_serialization);
+
 }
diff --git a/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php b/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php
index 0a53d98..372cf2f 100644
--- a/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php
+++ b/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Field;
 
+use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 
@@ -13,6 +14,22 @@ class EntityReferenceFieldItemList extends FieldItemList implements EntityRefere
   /**
    * {@inheritdoc}
    */
+  public function getSerializationValue($deep_serialization) {
+    $values = parent::getSerializationValue($deep_serialization);
+    if ($deep_serialization) {
+      foreach ($values as $delta => $item_values) {
+        if (isset($values[$delta]['entity']) && ($values[$delta]['entity'] instanceof ContentEntityInterface)) {
+          $values[$delta]['entity']->setDeepSerialization(TRUE);
+        }
+      }
+    }
+
+    return $values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getConstraints() {
     $constraints = parent::getConstraints();
     $constraint_manager = $this->getTypedDataManager()->getValidationConstraintManager();
diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php
index 84d73ec..2183ad0 100644
--- a/core/lib/Drupal/Core/Field/FieldItemBase.php
+++ b/core/lib/Drupal/Core/Field/FieldItemBase.php
@@ -101,6 +101,46 @@ protected function getSetting($setting_name) {
   /**
    * {@inheritdoc}
    */
+  public function getValue($include_computed = FALSE) {
+    $value = parent::getValue();
+
+    // Allow field types to opt-in or opt-out of including computed properties
+    // in certain cases.
+    if ($this->includeComputedProperties($include_computed)) {
+      // Iterate only over computed properties and include them, as the parent
+      // method is not including them.
+      foreach ($this->properties as $name => $property) {
+        if ($property->getDataDefinition()->isComputed()) {
+          $value[$name] = $property->getValue();
+        }
+      }
+    }
+    return $value;
+  }
+
+  /**
+   * Checks if included properties have to be included.
+   *
+   * Helper method for ::getValue() to check if included properties should be
+   * included.
+   *
+   * @param bool $include_computed
+   *   Whether to include computed properties as provided to ::getValue().
+   *
+   * @return bool
+   *   TRUE if computed properties have to be included, FALSE otherwise.
+   *
+   * @see ::getValue()
+   */
+  protected function includeComputedProperties($include_computed) {
+    // Even if computed fields were requested, do not attempt to compute
+    // anything if there are no actual values.
+    return $include_computed && !$this->isEmpty();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function setValue($values, $notify = TRUE) {
     // Treat the values as property value of the first property, if no array is
     // given.
diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php
index a1a1ebd..02ba043 100644
--- a/core/lib/Drupal/Core/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -121,6 +121,13 @@ public function setValue($values, $notify = TRUE) {
   /**
    * {@inheritdoc}
    */
+  public function getSerializationValue($deep_serialization) {
+    return $this->getValue($deep_serialization);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function __get($property_name) {
     // For empty fields, $entity->field->property is NULL.
     if ($item = $this->first()) {
diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
index c350432..898356f 100644
--- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
@@ -271,4 +271,19 @@ public static function processDefaultValue($default_value, FieldableEntityInterf
    */
   public function equals(FieldItemListInterface $list_to_compare);
 
+  /**
+   * Gets the data value prepared for serialization.
+   *
+   * @param bool $deep_serialization
+   *   Whether a deep serialization should be performed.
+   *
+   * @return array[]
+   *   Depending on whether deep serialization is being performed or not the
+   *   returned value might be either the data value as returned by ::getValue()
+   *   if deep serialization is not being performed or additionally contain
+   *   computed properties next to the data values if deep serialization is
+   *   being performed.
+   */
+  public function getSerializationValue($deep_serialization);
+
 }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
index f9922b9..c39e92a 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
@@ -208,15 +208,10 @@ public function setValue($values, $notify = TRUE) {
   /**
    * {@inheritdoc}
    */
-  public function getValue() {
-    $values = parent::getValue();
-
+  protected function includeComputedProperties($include_computed) {
     // If there is an unsaved entity, return it as part of the field item values
     // to ensure idempotency of getValue() / setValue().
-    if ($this->hasNewEntity()) {
-      $values['entity'] = $this->entity;
-    }
-    return $values;
+    return $this->hasNewEntity() ?: parent::includeComputedProperties($include_computed);
   }
 
   /**
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/ContentEntitySerializationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntitySerializationTest.php
new file mode 100644
index 0000000..07dad80
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntitySerializationTest.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Entity;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\language\Entity\ConfigurableLanguage;
+
+/**
+ * Tests the serialization of content entities.
+ *
+ * @group Entity
+ */
+class ContentEntitySerializationTest extends EntityKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['user', 'system', 'field', 'entity_test', 'language'];
+
+  /**
+   * The entity type id.
+   *
+   * @var string
+   */
+  protected $entityTypeId = 'entity_test_mul';
+
+  /**
+   * The EntityTestMul entity type storage.
+   *
+   * @var \Drupal\Core\Entity\ContentEntityStorageInterface
+   */
+  protected $entityStorage;
+
+  /**
+   * The entity reference field name.
+   *
+   * @var string
+   */
+  protected $entityReferenceFieldName = 'field_test_entity_reference';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installEntitySchema($this->entityTypeId);
+    $this->installConfig('language');
+    $this->entityStorage = $this->entityManager->getStorage($this->entityTypeId);
+
+    // Create the test entity reference field.
+    FieldStorageConfig::create([
+      'field_name' => $this->entityReferenceFieldName,
+      'entity_type' => $this->entityTypeId,
+      'type' => 'entity_reference',
+      'settings' => [
+        'target_type' => $this->entityTypeId,
+      ],
+    ])->save();
+    FieldConfig::create([
+      'entity_type' => $this->entityTypeId,
+      'field_name' => $this->entityReferenceFieldName,
+      'bundle' => $this->entityTypeId,
+      'translatable' => TRUE,
+    ])->save();
+  }
+
+  /**
+   * Tests regular and deep serialization.
+   */
+  public function testSerialization() {
+    $this->doTestSerialization(FALSE);
+    $this->doTestSerialization(TRUE);
+  }
+
+  /**
+   * Tests regular or deep serialization based on the parameter.
+   *
+   * @param bool $deep_serialization
+   *   Whether to test regular or deep serialization.
+   */
+  protected function doTestSerialization($deep_serialization) {
+    $field_name = $this->entityReferenceFieldName;
+    $user = $this->createUser();
+    $initial_entity_name = 'test test';
+
+    // Create the test entities.
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity_level_zero */
+    $entity_level_zero = $this->entityStorage->create([
+      'name' => $initial_entity_name,
+      'user_id' => $user->id(),
+      'language' => 'en',
+    ]);
+    $entity_level_zero->save();
+    $entity_level_one = $entity_level_zero->createDuplicate();
+    $entity_level_one->save();
+    $entity_level_two = $entity_level_zero->createDuplicate();
+    $entity_level_two->save();
+
+    $entity_level_zero->$field_name->appendItem($entity_level_one);
+    $entity_level_zero->save();
+    $entity_level_one->$field_name->appendItem($entity_level_two);
+    $entity_level_one->save();
+
+    // Change the entity values in memory but do not persist them into the
+    // storage.
+    $entity_level_zero->name = 'entity level zero';
+    $entity_level_zero->$field_name->entity->name = 'entity level one';
+    $entity_level_zero->$field_name->entity->$field_name->entity->name = 'entity level two';
+
+    // Alter a computed property beside the entity property in order to assure
+    // that all computed properties are being serialized through deep
+    // serialization.
+    $entity_type = $entity_level_two->getEntityType();
+    $this->assertTrue($entity_type->hasKey('langcode'));
+    $langcode_field_name = $entity_type->getKey('langcode');
+    $langcode_field = $entity_level_zero->$field_name->entity->$field_name->entity->$langcode_field_name;
+    $test_language = ConfigurableLanguage::load($langcode_field->language->getId());
+    $original_language_name = $test_language->getName();
+    $test_language->setName('test deep serialization on language property');
+    $langcode_field->language = $test_language;
+    $this->assertEquals('test deep serialization on language property', $langcode_field->language->getName());
+
+    if ($deep_serialization) {
+      $entity_level_zero->setDeepSerialization(TRUE);
+    }
+
+    // Serialize and then unserialize the entity in order to check that the
+    // entity has been properly serialized depending on the used serialization
+    // method - regular or deep serialization.
+    $entity_level_zero = unserialize(serialize($entity_level_zero));
+
+    // If regular serialization is tested then the changes on the referenced
+    // entities should be lost, but kept if deep serialization is tested.
+    $this->assertEquals('entity level zero', $entity_level_zero->label());
+    $this->assertEquals($deep_serialization ? 'entity level one' : $initial_entity_name, $entity_level_zero->$field_name->entity->label());
+    $this->assertEquals($deep_serialization ? 'entity level two' : $initial_entity_name, $entity_level_zero->$field_name->entity->$field_name->entity->label());
+    $language = $entity_level_zero->$field_name->entity->$field_name->entity->$langcode_field_name->language;
+    $this->assertEquals($deep_serialization ? 'test deep serialization on language property' : $original_language_name, $language->getName());
+  }
+
+  /**
+   * Tests deep serialization with circular entity references.
+   */
+  public function testDeepSerializationWithCircularEntityReferences() {
+    $field_name = $this->entityReferenceFieldName;
+    $user = $this->createUser();
+
+    // Case 1: new_entity->existing_entity->parent_new_entity
+    /** @var \Drupal\entity_test\Entity\EntityTestMul $entity_level_zero */
+    $entity_level_zero = $this->entityStorage->create([
+      'name' => 'entity zero',
+      'user_id' => $user->id(),
+      'language' => 'en',
+    ]);
+    $entity_level_zero->save();
+    $entity_level_one = $entity_level_zero->createDuplicate();
+    $entity_level_one->setName('entity one');
+    $entity_level_one->save();
+
+    // Case 1:
+    // Create a circular reference, where the parent entity is referenced at the
+    // end of the chain back again with a different object reference than the
+    // one at the beginning of the chain.
+    $entity_level_zero->$field_name->appendItem($entity_level_one);
+    // Ensure that the main entity has different object references by cloning
+    // it, otherwise PHP will not serialize the same object twice.
+    $entity_level_zero_clone = clone $entity_level_zero;
+    $this->assertNotSame($entity_level_zero, $entity_level_zero_clone);
+    $entity_level_one->$field_name->appendItem($entity_level_zero_clone);
+
+    // Make sure all the entities are initialized in the structure, otherwise
+    // they will not be contained in the serialization.
+    $entity_level_zero->$field_name->entity->$field_name->entity;
+
+    // Now serialize the entity structure through deep serialization.
+    $entity_level_zero->setDeepSerialization(TRUE);
+    $serialized_main_entity = serialize($entity_level_zero);
+    $this->assertEquals(2, substr_count($serialized_main_entity, 'entity zero'));
+    $this->assertEquals(1, substr_count($serialized_main_entity, 'entity one'));
+
+
+    // Case 2:
+    // Reset the entities for the next test.
+    $entity_level_zero = $this->entityStorage->loadUnchanged($entity_level_zero->id());
+    $entity_level_one = $this->entityStorage->loadUnchanged($entity_level_one->id());
+
+    // Create a circular reference, where the parent entity is referenced at the
+    // end of the chain back again with the same object reference as at the
+    // beginning of the chain.
+    $entity_level_zero->$field_name->appendItem($entity_level_one);
+    $entity_level_one->$field_name->appendItem($entity_level_zero);
+
+    // Make sure all the entities are initialized in the structure, otherwise
+    // they will not be contained in the serialization.
+    $entity_level_zero->$field_name->entity->$field_name->entity;
+
+    // Now serialize the entity structure through deep serialization.
+    $entity_level_zero->setDeepSerialization(TRUE);
+    $serialized_main_entity = serialize($entity_level_zero);
+    $this->assertEquals(2, substr_count($serialized_main_entity, 'entity zero'));
+    $this->assertEquals(2, substr_count($serialized_main_entity, 'entity one'));
+  }
+
+}
