diff --git a/core/modules/comment/src/Tests/Views/CommentRestExportTest.php b/core/modules/comment/src/Tests/Views/CommentRestExportTest.php
index a3d7e2d..31d4fac 100644
--- a/core/modules/comment/src/Tests/Views/CommentRestExportTest.php
+++ b/core/modules/comment/src/Tests/Views/CommentRestExportTest.php
@@ -54,8 +54,8 @@ public function testCommentRestExport() {
     $this->drupalGetWithFormat(sprintf('node/%d/comments', $this->nodeUserCommented->id()), 'hal_json');
     $this->assertResponse(200);
     $contents = Json::decode($this->getRawContent());
-    $this->assertEqual($contents[0]['subject'], 'How much wood would a woodchuck chuck');
-    $this->assertEqual($contents[1]['subject'], 'A lot, apparently');
+    $this->assertEqual($contents['_embedded']['item'][0]['subject'], 'How much wood would a woodchuck chuck');
+    $this->assertEqual($contents['_embedded']['item'][1]['subject'], 'A lot, apparently');
     $this->assertEqual(count($contents), 2);
 
     // Ensure field-level access is respected - user shouldn't be able to see
diff --git a/core/modules/hal/hal.services.yml b/core/modules/hal/hal.services.yml
index e817fbf..03b66ef 100644
--- a/core/modules/hal/hal.services.yml
+++ b/core/modules/hal/hal.services.yml
@@ -30,3 +30,8 @@ services:
     class: Drupal\hal\EventSubscriber\ExceptionHalJsonSubscriber
     tags:
       - { name: event_subscriber }
+  serializer.normalizer.collection.hal:
+    class: Drupal\hal\Normalizer\CollectionNormalizer
+    arguments: ['@rest.link_manager']
+    tags:
+      - { name: normalizer, priority: 10 }
diff --git a/core/modules/hal/src/Normalizer/CollectionNormalizer.php b/core/modules/hal/src/Normalizer/CollectionNormalizer.php
new file mode 100644
index 0000000..025d9aa
--- /dev/null
+++ b/core/modules/hal/src/Normalizer/CollectionNormalizer.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\hal\Normalizer;
+
+use Drupal\rest\LinkManager\LinkManagerInterface;
+use Drupal\serialization\Collection;
+
+/**
+ * Converts the Drupal entity object structure to a HAL array structure.
+ */
+class CollectionNormalizer extends NormalizerBase {
+
+  /**
+   * The interface or class that this Normalizer supports.
+   *
+   * @var string
+   */
+  protected $supportedInterfaceOrClass = 'Drupal\serialization\Collection';
+
+  /**
+   * The collection link manager.
+   *
+   * @var \Drupal\rest\LinkManager\CollectionLinkManagerInterface
+   */
+  protected $linkManager;
+
+  /**
+   * Constructs an CollectionNormalizer object.
+   *
+   * @param \Drupal\rest\LinkManager\LinkManagerInterface $link_manager
+   *   The hypermedia link manager.
+   */
+  public function __construct(LinkManagerInterface $link_manager) {
+    $this->linkManager = $link_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function normalize($object, $format = NULL, array $context = []) {
+    /** @var $entity \Drupal\serialization\Collection */
+    // Create the array of normalized properties, starting with the URI.
+    $normalized = [
+      '_links' => [
+        'self' => [
+          'href' => $object->getUri(),
+        ],
+      ],
+    ];
+
+    // If we have additional hypermedia links add them here.
+    $links = $object->getLinks();
+    if (is_array($links) && count($links)) {
+      foreach ($links as $key => $link) {
+        $normalized['_links'][$key] = ['href' => $link];
+      }
+    }
+
+    // Add the list of items.
+    $link_relation = $this->linkManager->getCollectionItemRelation($object->getCollectionId());
+    $normalized['_embedded'][$link_relation] = $this->serializer->normalize($object->getItems(), $format, $context);
+
+    return $normalized;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @todo Implement denormalization once normalization has settled.
+   */
+  public function denormalize($data, $class, $format = NULL, array $context = []) {
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @todo Implement denormalization once normalization has settled.
+   */
+  public function supportsDenormalization($data, $type, $format = NULL) {
+    return FALSE;
+  }
+}
diff --git a/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php b/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php
index 42d7a36..1e62f54 100644
--- a/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php
+++ b/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php
@@ -11,7 +11,7 @@
 use Drupal\rest\LinkManager\RelationLinkManager;
 use Drupal\rest\LinkManager\TypeLinkManager;
 use Symfony\Component\Serializer\Serializer;
-
+use Drupal\rest\LinkManager\CollectionLinkManager;
 
 /**
  * Tests that file entities can be normalized in HAL.
@@ -35,7 +35,7 @@ protected function 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')));
+    $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')), new CollectionLinkManager());
 
     // Set up the mock serializer.
     $normalizers = array(
diff --git a/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php b/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php
index 5867476..1847030 100644
--- a/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php
+++ b/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php
@@ -10,6 +10,7 @@
 use Drupal\hal\Normalizer\FieldItemNormalizer;
 use Drupal\hal\Normalizer\FieldNormalizer;
 use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\rest\LinkManager\CollectionLinkManager;
 use Drupal\rest\LinkManager\LinkManager;
 use Drupal\rest\LinkManager\RelationLinkManager;
 use Drupal\rest\LinkManager\TypeLinkManager;
@@ -131,7 +132,10 @@ protected function setUp() {
     ])->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')));
+    $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')),
+      new CollectionLinkManager());
 
     $chain_resolver = new ChainEntityResolver(array(new UuidResolver($entity_manager), new TargetIdResolver()));
 
diff --git a/core/modules/hal/tests/src/Unit/CollectionNormalizerTest.php b/core/modules/hal/tests/src/Unit/CollectionNormalizerTest.php
new file mode 100644
index 0000000..30c6031
--- /dev/null
+++ b/core/modules/hal/tests/src/Unit/CollectionNormalizerTest.php
@@ -0,0 +1,195 @@
+<?php
+
+namespace Drupal\Tests\hal\Unit;
+
+use Drupal\hal\Normalizer\CollectionNormalizer;
+use Drupal\serialization\Collection;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the CollectionNormalizer's normalize supports.
+ *
+ * @coversDefaultClass \Drupal\hal\Normalizer\CollectionNormalizer
+ * @group HAL
+ */
+class CollectionNormalizerTest extends UnitTestCase {
+
+  /**
+   * Tests the supportsNormalization method.
+   */
+  public function testSupportsNormalization() {
+    $collection = $this->getCollection();
+    $normalizer = new CollectionNormalizer($this->getLinkManagerStub());
+    $this->assertTrue($normalizer->supportsNormalization($collection, 'hal_json'));
+    $this->assertFalse($normalizer->supportsNormalization($collection, 'json'));
+    $this->assertFalse($normalizer->supportsNormalization(new \stdClass(), 'hal_json'));
+  }
+
+  /**
+   * Tests the normalize method.
+   */
+  public function testNormalize() {
+    $test_values = $this->getTestValues();
+    $collection = $this->getCollection();
+
+    // Create the normalizer and inject the LinkManagerStub.
+    $normalizer = new CollectionNormalizer($this->getLinkManagerStub());
+    // Inject the Serializer. Handle the call to Serializer::normalize,
+    // ensuring that the items array is passed in.
+    $serializer = $this->getSerializerStub();
+    $serializer->expects($this->any())
+      ->method('normalize')
+      ->with($collection->getItems())
+      ->will($this->returnValue($test_values['items']));
+    $normalizer->setSerializer($serializer);
+    // Get the normalized array.
+    $normalized = $normalizer->normalize($collection, 'hal_json');
+
+    // Test that self link points to collection URI.
+    $this->assertEquals($normalized['_links']['self']['href'], $test_values['uri']);
+    // There should only be a self-key.
+    $this->assertEquals(array_keys($normalized['_links']), array('self'));
+
+    // Test that the correct link relation was retrieved from the LinkManager
+    // and added to _embedded.
+    $this->assertArrayHasKey($test_values['item_link_relation'], $normalized['_embedded']);
+    // Test that the item link relation points to the serialized item array.
+    $this->assertEquals($normalized['_embedded'][$test_values['item_link_relation']], $test_values['items']);
+  }
+
+  /**
+   * Tests the normalize method on pageable collection.
+   */
+  public function testNormalizePageableCollection() {
+    $test_values = $this->getTestValues();
+    $collection = $this->getPageableCollection();
+
+    // Create the normalizer and inject the LinkManagerStub.
+    $normalizer = new CollectionNormalizer($this->getLinkManagerStub());
+    // Inject the Serializer. Handle the call to Serializer::normalize,
+    // ensuring that the items array is passed in.
+    $serializer = $this->getSerializerStub();
+    $serializer->expects($this->any())
+      ->method('normalize')
+      ->with($collection->getItems())
+      ->will($this->returnValue($test_values['items']));
+    $normalizer->setSerializer($serializer);
+    // Get the normalized array.
+    $normalized = $normalizer->normalize($collection, 'hal_json');
+
+    // Test that self link points to collection URI.
+    $this->assertEquals($normalized['_links']['self']['href'], $test_values['uri']);
+    // There should be the self-key, _first, _prev, _next and _last-keys.
+    $this->assertArrayHasKey('self', $normalized['_links']);
+    $this->assertArrayHasKey('first', $normalized['_links']);
+    $this->assertArrayHasKey('prev', $normalized['_links']);
+    $this->assertArrayHasKey('next', $normalized['_links']);
+    $this->assertArrayHasKey('last', $normalized['_links']);
+    $this->assertEquals(array_keys($normalized['_links']),
+      array('self', 'first', 'prev', 'next', 'last')
+    );
+
+    // Test that the correct link relation was retrieved from the LinkManager
+    // and added to _embedded.
+    $this->assertArrayHasKey($test_values['item_link_relation'], $normalized['_embedded']);
+    // Test that the item link relation points to the serialized item array.
+    $this->assertEquals($normalized['_embedded'][$test_values['item_link_relation']], $test_values['items']);
+  }
+
+  /**
+   * Get an \Drupal\serialization\Collection for testing.
+   *
+   * @return \Drupal\serialization\Collection
+   *   The Collection object, configured with test values.
+   */
+  protected function getCollection() {
+    $test_values = $this->getTestValues();
+
+    // Get a mock node.
+    $node = $this->getMockBuilder('Drupal\node\Entity\Node')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $collection = new Collection('test_id');
+    $collection->setUri($test_values['uri']);
+    $collection->setItems(array($node));
+
+    return $collection;
+  }
+
+  /**
+   * Get an pageable \Drupal\serialization\Collection for testing.
+   *
+   * @return \Drupal\serialization\Collection
+   *   The Collection object, configured with test values.
+   */
+  protected function getPageableCollection() {
+    $test_values = $this->getTestValues();
+
+    // Get a dummy node.
+    $node = $this->getMockBuilder('Drupal\node\Entity\Node')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $collection = new Collection('test_id');
+    $collection->setUri($test_values['uri']);
+    $collection->setItems(array($node));
+
+    $collection->setLinks(array(
+      'first' => $test_values['uri'] . '?page=0',
+      'prev' => $test_values['uri'] . '?page=0',
+      'next' => $test_values['uri'] . '?page=2',
+      'last' => $test_values['uri'] . '?page=2',
+    ));
+
+    return $collection;
+  }
+
+  /**
+   * Get a stub LinkManager for testing.
+   *
+   * @return \Drupal\rest\LinkManager\LinkManagerInterface
+   *   The LinkManager stub.
+   */
+  protected function getLinkManagerStub() {
+    $test_values = $this->getTestValues();
+
+    $link_manager = $this->getMockBuilder('Drupal\rest\LinkManager\LinkManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $link_manager->expects($this->any())
+      ->method('getCollectionItemRelation')
+      ->will($this->returnValue($test_values['item_link_relation']));
+
+    return $link_manager;
+  }
+
+  /**
+   * Get a stub Serializer for testing.
+   *
+   * @return \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Normalizer\NormalizerInterface|\Symfony\Component\Serializer\Normalizer\DenormalizerInterface|\Symfony\Component\Serializer\Encoder\EncoderInterface|\Symfony\Component\Serializer\Encoder\DecoderInterface|\PHPUnit_Framework_MockObject_MockObject
+   *   The Serializer mock.
+   */
+  protected function getSerializerStub() {
+    $serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    return $serializer;
+  }
+
+  /**
+   * Get the array of test values.
+   *
+   * @return array
+   *   An array of test values, used for configuring stub methods and testing.
+   */
+  protected function getTestValues() {
+    return array(
+      'item_link_relation' => 'item',
+      'items' => 'Array of serialized entities goes here',
+      'uri' => 'http://example.com/test-path',
+    );
+  }
+}
diff --git a/core/modules/rest/rest.services.yml b/core/modules/rest/rest.services.yml
index 1cc34c1..729040a 100644
--- a/core/modules/rest/rest.services.yml
+++ b/core/modules/rest/rest.services.yml
@@ -13,13 +13,15 @@ services:
     alias: access_check.header.csrf
   rest.link_manager:
     class: Drupal\rest\LinkManager\LinkManager
-    arguments: ['@rest.link_manager.type', '@rest.link_manager.relation']
+    arguments: ['@rest.link_manager.type', '@rest.link_manager.relation', '@rest.link_manager.collection']
   rest.link_manager.type:
     class: Drupal\rest\LinkManager\TypeLinkManager
     arguments: ['@cache.default', '@module_handler', '@config.factory', '@request_stack', '@entity_type.bundle.info']
   rest.link_manager.relation:
     class: Drupal\rest\LinkManager\RelationLinkManager
     arguments: ['@cache.default', '@entity.manager', '@module_handler', '@config.factory', '@request_stack']
+  rest.link_manager.collection:
+    class: Drupal\rest\LinkManager\CollectionLinkManager
   rest.resource_routes:
     class: Drupal\rest\Routing\ResourceRoutes
     arguments: ['@plugin.manager.rest', '@entity_type.manager', '@logger.channel.rest']
diff --git a/core/modules/rest/src/LinkManager/CollectionLinkManager.php b/core/modules/rest/src/LinkManager/CollectionLinkManager.php
new file mode 100644
index 0000000..f6acdac
--- /dev/null
+++ b/core/modules/rest/src/LinkManager/CollectionLinkManager.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\rest\LinkManager;
+
+/**
+ * Default collection link relation mapper.
+ */
+class CollectionLinkManager implements CollectionLinkManagerInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCollectionItemRelation($collection_id) {
+    // By default, use the item IANA Link Relation, which is a generic way to
+    // link to items from a collection.
+    // @see http://tools.ietf.org/html/rfc6573.
+    return 'item';
+  }
+
+}
diff --git a/core/modules/rest/src/LinkManager/CollectionLinkManagerInterface.php b/core/modules/rest/src/LinkManager/CollectionLinkManagerInterface.php
new file mode 100644
index 0000000..a1c3b7e
--- /dev/null
+++ b/core/modules/rest/src/LinkManager/CollectionLinkManagerInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\rest\LinkManager;
+
+/**
+ * Interface for mapping collection (e.g. Views) link relations.
+ */
+interface CollectionLinkManagerInterface {
+
+  /**
+   * Get link relations to items in a collection.
+   *
+   * @param string $collection_id
+   *   The identifier of a collection (e.g. View name).
+   *
+   * @return string
+   *   The link relation.
+   */
+  public function getCollectionItemRelation($collection_id);
+}
diff --git a/core/modules/rest/src/LinkManager/LinkManager.php b/core/modules/rest/src/LinkManager/LinkManager.php
index bd94a65..3a7e656 100644
--- a/core/modules/rest/src/LinkManager/LinkManager.php
+++ b/core/modules/rest/src/LinkManager/LinkManager.php
@@ -19,16 +19,26 @@ class LinkManager implements LinkManagerInterface {
   protected $relationLinkManager;
 
   /**
+   * The collection link manager.
+   *
+   * @var \Drupal\rest\LinkManager\CollectionLinkManagerInterface
+   */
+  protected $collectionLinkManager;
+
+  /**
    * Constructor.
    *
    * @param \Drupal\rest\LinkManager\TypeLinkManagerInterface $type_link_manager
-   *   Manager for handling bundle URIs.
+   *   Manager for handling type links corresponding to bundles.
    * @param \Drupal\rest\LinkManager\RelationLinkManagerInterface $relation_link_manager
-   *   Manager for handling bundle URIs.
+   *   Manager for handling link relations corresponding to fields.
+   * @param \Drupal\rest\LinkManager\CollectionLinkManagerInterface $collection_link_manager
+   *   Manager for handling collection links.
    */
-  public function __construct(TypeLinkManagerInterface $type_link_manager, RelationLinkManagerInterface $relation_link_manager) {
+  public function __construct(TypeLinkManagerInterface $type_link_manager, RelationLinkManagerInterface $relation_link_manager, CollectionLinkManagerInterface $collection_link_manager) {
     $this->typeLinkManager = $type_link_manager;
     $this->relationLinkManager = $relation_link_manager;
+    $this->collectionLinkManager = $collection_link_manager;
   }
 
   /**
@@ -68,4 +78,11 @@ public function setLinkDomain($domain) {
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCollectionItemRelation($collection_id) {
+    return $this->collectionLinkManager->getCollectionItemRelation($collection_id);
+  }
+
 }
diff --git a/core/modules/rest/src/LinkManager/LinkManagerInterface.php b/core/modules/rest/src/LinkManager/LinkManagerInterface.php
index 1bd04ff..92b9a25 100644
--- a/core/modules/rest/src/LinkManager/LinkManagerInterface.php
+++ b/core/modules/rest/src/LinkManager/LinkManagerInterface.php
@@ -14,5 +14,5 @@
  * custom logic, it is expected to be more common for plugin managers to proxy
  * the method invocations to the respective components.
  */
-interface LinkManagerInterface extends TypeLinkManagerInterface, RelationLinkManagerInterface {
+interface LinkManagerInterface extends TypeLinkManagerInterface, RelationLinkManagerInterface, CollectionLinkManagerInterface {
 }
diff --git a/core/modules/rest/src/Plugin/views/display/RestExport.php b/core/modules/rest/src/Plugin/views/display/RestExport.php
index 1a7a7f1..7291f3a 100644
--- a/core/modules/rest/src/Plugin/views/display/RestExport.php
+++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
@@ -320,7 +320,7 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function collectRoutes(RouteCollection $collection) {
-    parent::collectRoutes($collection);
+    $result = parent::collectRoutes($collection);
     $view_id = $this->view->storage->id();
     $display_id = $this->display['id'];
 
@@ -345,6 +345,8 @@ public function collectRoutes(RouteCollection $collection) {
         $route->setOption('_auth', $auth);
       }
     }
+
+    return $result;
   }
 
   /**
diff --git a/core/modules/rest/src/Plugin/views/style/Serializer.php b/core/modules/rest/src/Plugin/views/style/Serializer.php
index 7e9b433..18030a6 100644
--- a/core/modules/rest/src/Plugin/views/style/Serializer.php
+++ b/core/modules/rest/src/Plugin/views/style/Serializer.php
@@ -8,6 +8,9 @@
 use Drupal\views\Plugin\views\style\StylePluginBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Serializer\SerializerInterface;
+use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\serialization\Collection;
 
 /**
  * The style plugin for serialized output formats.
@@ -45,7 +48,7 @@ class Serializer extends StylePluginBase implements CacheableDependencyInterface
    *
    * @var array
    */
-  protected $formats = array();
+  protected $formats = [];
 
   /**
    * The serialization format providers, keyed by format.
@@ -55,6 +58,20 @@ class Serializer extends StylePluginBase implements CacheableDependencyInterface
   protected $formatProviders;
 
   /**
+   * The URL generator service.
+   *
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface
+   */
+  protected $urlGenerator;
+
+  /**
+   * The state service.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
@@ -64,20 +81,24 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_definition,
       $container->get('serializer'),
       $container->getParameter('serializer.formats'),
-      $container->getParameter('serializer.format_providers')
+      $container->getParameter('serializer.format_providers'),
+      $container->get('url_generator'),
+      $container->get('state')
     );
   }
 
   /**
    * Constructs a Plugin object.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, array $serializer_formats, array $serializer_format_providers) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, array $serializer_formats, array $serializer_format_providers, UrlGeneratorInterface $url_generator, StateInterface $state) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->definition = $plugin_definition + $configuration;
     $this->serializer = $serializer;
     $this->formats = $serializer_formats;
     $this->formatProviders = $serializer_format_providers;
+    $this->urlGenerator = $url_generator;
+    $this->state = $state;
   }
 
   /**
@@ -139,7 +160,7 @@ public function render() {
     else {
       $content_type = !empty($this->options['formats']) ? reset($this->options['formats']) : 'json';
     }
-    return $this->serializer->serialize($rows, $content_type, ['views_style_plugin' => $this]);
+    return $this->serializer->serialize($this->getCollection(), $content_type, ['views_style_plugin' => $this]);
   }
 
   /**
@@ -177,6 +198,99 @@ public function getCacheTags() {
   }
 
   /**
+   * Instantiate Collection object needed to encapsulate serialization.
+   *
+   * @return \Drupal\serialization\Collection
+   *   Collection object wrapping items/rows of the view.
+   */
+  public function getCollection() {
+
+    $display = $this->view->getDisplay();
+    // Build full view-display id.
+    $view_id = $this->view->storage->id();
+    $display_id = $display->display['id'];
+
+    // Instantiate collection object.
+    $collection = new Collection($view_id . '_' . $display_id);
+
+    $collection->setTitle($this->view->getTitle());
+    $collection->setDescription($display->getOption('display_description'));
+
+    // Route as defined in e.g. \Drupal\rest\Plugin\views\display\RestExport.
+    $route_names = $this->state->get('views.view_route_names');
+    $route_name = $route_names["$view_id.$display_id"];
+
+    // Get base url path for the view; getUrl returns a path not an absolute
+    // URL (and no page information).
+    /** @var \Drupal\Core\Url $view_base_url */
+    $view_base_url = $this->view->getUrl();
+
+    // Inject the page into the canonical URI of the view.
+    if ($this->view->getCurrentPage() > 0) {
+      $uri = $this->urlGenerator->generateFromRoute($route_name, $view_base_url->getRouteParameters(), array('query' => array('page' => $this->view->getCurrentPage()), 'absolute' => TRUE));
+    }
+    else {
+      $uri = $this->urlGenerator->generateFromRoute($route_name, $view_base_url->getRouteParameters(), array('absolute' => TRUE));
+    }
+    $collection->setUri($uri);
+
+    $rows = [];
+    foreach ($this->view->result as $row_index => $row) {
+      $this->view->row_index = $row_index;
+      $rows[] = $this->view->rowPlugin->render($row);
+    }
+
+    unset($this->view->row_index);
+    $collection->setItems($rows);
+
+    $pager = $this->view->getPager();
+    $pager_plugin = $pager->getPluginId();
+
+    // Determine whether we have more items than we are showing, in that case
+    // we are a pageable collection.
+    if ($pager->getTotalItems() > $pager->getItemsPerPage()) {
+      // Calculate pager links.
+      $current_page = $pager->getCurrentPage();
+      // Starting at page=0 we need to decrement.
+      $total = ceil($pager->getTotalItems() / $pager->getItemsPerPage()) - 1;
+      // The total number of page for mini pager generates a big float number.
+      $total = number_format($total, 0, NULL, '');
+
+      if ($pager_plugin != 'mini') {
+        $collection->setLink('first', $this->urlGenerator->generateFromRoute($route_name, [], array(
+          'query' => array('page' => 0),
+          'absolute' => TRUE,
+        )));
+      }
+
+      // If we are not on the first page add a previous link.
+      if ($current_page > 0) {
+        $collection->setLink('prev', $this->urlGenerator->generateFromRoute($route_name, [], array(
+          'query' => array('page' => $current_page - 1),
+          'absolute' => TRUE,
+        )));
+      }
+
+      // If we are not on the last page add a next link.
+      if ($current_page < $total) {
+        $collection->setLink('next', $this->urlGenerator->generateFromRoute($route_name, [], array(
+          'query' => array('page' => $current_page + 1),
+          'absolute' => TRUE,
+        )));
+      }
+
+      if ($pager_plugin != 'mini') {
+        $collection->setLink('last', $this->urlGenerator->generateFromRoute($route_name, [], array(
+          'query' => array('page' => $total),
+          'absolute' => TRUE,
+        )));
+      }
+    }
+
+    return $collection;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function calculateDependencies() {
diff --git a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
index 4c004fc..ca91eb8 100644
--- a/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
+++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
@@ -2,9 +2,11 @@
 
 namespace Drupal\rest\Tests\Views;
 
+use Drupal\Component\Serialization\Json;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Url;
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
@@ -12,6 +14,7 @@
 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 use Drupal\views\Entity\View;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
+use Drupal\views\ViewExecutable;
 use Drupal\views\Views;
 use Drupal\views\Tests\Plugin\PluginTestBase;
 use Drupal\views\Tests\ViewTestData;
@@ -103,6 +106,182 @@ public function testRestViewsAuthentication() {
   }
 
   /**
+   * Retrieves a Drupal path or an absolute path with hal+json.
+   *
+   * Sets Accept-Header to application/hal+json and decodes the result.
+   *
+   * @param string $path
+   *   Path to request HAL+JSON from.
+   * @param array $options
+   *   Array of options to pass to the URL generator.
+   * @param array $headers
+   *   Array of headers.
+   *
+   * @return array
+   *   Decoded json.
+   * Requests a Drupal path in HAL+JSON format, and JSON decodes the response.
+   */
+  protected function drupalGetHalJson($path, array $options = array(), array $headers = array()) {
+    $headers[] = 'Accept: application/hal+json';
+    return Json::decode($this->drupalGet($path, $options, $headers));
+  }
+
+  /**
+   * Retrieve the Collection object for given view.
+   *
+   * @param \Drupal\views\ViewExecutable $view
+   *   The View being executed.
+   *
+   * @return \Drupal\serialization\Collection
+   *   The collection object to pass into the serializer.
+   */
+  protected function getCollectionFromView(ViewExecutable $view) {
+    return $view->getStyle()->getCollection();
+  }
+
+
+
+  /**
+   * Tests the Serializer paths and responses for field-based views.
+   */
+  public function testSerializerFieldDisplayResponse() {
+
+    $view = Views::getView('test_serializer_display_field');
+    $view->setDisplay('rest_export_1');
+    // Mock the request content type by setting it on the display handler.
+    $view->display_handler->setContentType('hal_json');
+    $this->executeView($view);
+
+    $view_output = $view->preview();
+    $view_result = array();
+    foreach ($view->result as $row) {
+      $expected_row = array();
+      foreach ($view->field as $id => $field) {
+        $expected_row[$id] = $field->render($row);
+      }
+      $view_result[] = $expected_row;
+    }
+
+    $serializer = $this->container->get('serializer');
+    $collection = $this->getCollectionFromView($view);
+    $expected = $serializer->serialize($collection, 'hal_json');
+    $actual_json = $this->drupalGetWithFormat('test/serialize/field', 'hal_json');
+
+    $this->assertIdentical($actual_json, $view_output['#markup']->jsonSerialize(), 'Preview output matches the (reserialized) JSON returned from the view via HTTP GET.');
+    $this->assertIdentical($actual_json, $expected, 'HAL serializer output matches the (reserialized) JSON returned from the view via HTTP GET.');
+    // @TODO needs review. the structure is not the same at all.
+    //$this->assertIdentical($actual_json['_embedded']['item'], $view_result, 'View result matches JSON returned from the view via HTTP GET');
+  }
+
+  /**
+   * Tests the Serializer paths and responses for field-based views with paging.
+   */
+  public function testSerializerFieldDisplayPagingResponse() {
+    $view = Views::getView('test_serializer_display_field');
+    $view->setDisplay('rest_export_paging');
+    // Mock the request content type by setting it on the display handler.
+    $view->display_handler->setContentType('hal_json');
+    $this->executeView($view);
+
+    $view_output = $view->preview();
+    $view_result = array();
+    foreach ($view->result as $row) {
+      $expected_row = array();
+      foreach ($view->field as $id => $field) {
+        $expected_row[$id] = $field->render($row);
+      }
+      $view_result[] = $expected_row;
+    }
+
+    $serializer = $this->container->get('serializer');
+    $collection = $this->getCollectionFromView($view);
+    $expected = $serializer->serialize($collection, 'hal_json');
+
+    $actual_json = $this->drupalGetWithFormat('test/serialize/field-paging', 'hal_json');
+    $actual_json_decoded = Json::decode($actual_json);
+    $this->assertIdentical($actual_json, $view_output['#markup']->jsonSerialize(), 'Preview output matches the (reserialized) JSON returned from the view via HTTP GET.');
+    $this->assertIdentical($actual_json, $expected, 'HAL serializer output matches the (reserialized) JSON returned from the view via HTTP GET.');
+    // @TODO Remove? the structure is not the same at all.
+    //$this->assertIdentical(Json::decode($actual_json)['_embedded']['item'], $view_result, 'View result matches JSON returned from the view via HTTP GET');
+
+    // Make assertions on the structure of the response.
+    $this->assertTrue(isset($actual_json_decoded['_embedded']) && isset($actual_json_decoded['_links']), 'Has _links and _embedded keys');
+
+    $this->assertEqual(count($actual_json_decoded['_embedded']['item']), 1);
+    $this->assertEqual($actual_json_decoded['_links']['self']['href'], $this->viewUrl($view));
+    $this->assertEqual($actual_json_decoded['_links']['first']['href'], $this->viewUrl($view, 0));
+    $this->assertEqual($actual_json_decoded['_links']['next']['href'], $this->viewUrl($view, 1));
+    $this->assertEqual($actual_json_decoded['_links']['last']['href'], $this->viewUrl($view, 4));
+    $this->assertEqual(array_keys($actual_json_decoded['_links']), array(
+      'self',
+      'first',
+      'next',
+      'last',
+    ));
+
+    // Load the second page.
+    $actual_json_page_1 = $this->drupalGetHalJson($actual_json_decoded['_links']['next']['href']);
+
+    $this->assertTrue(isset($actual_json_page_1['_embedded']) && isset($actual_json_page_1['_links']), 'Has _links and _embedded keys');
+
+    $this->assertEqual(count($actual_json_page_1['_embedded']['item']), 1);
+    $this->assertEqual($actual_json_page_1['_links']['self']['href'], $this->viewUrl($view, 1));
+    $this->assertEqual($actual_json_page_1['_links']['first']['href'], $this->viewUrl($view, 0));
+    $this->assertEqual($actual_json_page_1['_links']['prev']['href'], $this->viewUrl($view, 0));
+    $this->assertEqual($actual_json_page_1['_links']['next']['href'], $this->viewUrl($view, 2));
+    $this->assertEqual($actual_json_page_1['_links']['last']['href'], $this->viewUrl($view, 4));
+    $this->assertEqual(array_keys($actual_json_page_1['_links']), array(
+      'self',
+      'first',
+      'prev',
+      'next',
+      'last',
+    ));
+
+    // Test the entity rows - with paging.
+    $view = Views::getView('test_serializer_display_field');
+    $view->setDisplay('rest_export_paging');
+    $view->setCurrentPage(1);
+    $this->executeView($view);
+
+    // Create the entity collection.
+    $collection = $this->getCollectionFromView($view);
+    $this->assertTrue($collection->hasLinks(), 'Collection created from a paging view has (hypermedia) link relations');
+    $expected = $serializer->serialize($collection, 'hal_json');
+
+    $this->assertEqual(Json::encode($actual_json_page_1), $expected, 'The expected HAL output for page=1 was found.');
+
+    // Load the last page.
+    $actual_json_page_last = $this->drupalGetWithFormat($actual_json_decoded['_links']['last']['href'], 'hal_json');//$this->drupalGetHalJson($actual_json_decoded['_links']['last']['href']);
+    $actual_json_page_last_decoded = Json::decode($actual_json_page_last);
+    $this->assertTrue(isset($actual_json_page_last_decoded['_embedded']) && isset($actual_json_page_last_decoded['_links']), 'Has _links and _embedded keys');
+
+    $this->assertEqual(count($actual_json_page_last_decoded['_embedded']['item']), 1);
+    $this->assertEqual($actual_json_page_last_decoded['_links']['self']['href'], $this->viewUrl($view, 4));
+    $this->assertEqual($actual_json_page_last_decoded['_links']['first']['href'], $this->viewUrl($view, 0));
+    $this->assertEqual($actual_json_page_last_decoded['_links']['prev']['href'], $this->viewUrl($view, 3));
+    $this->assertEqual($actual_json_page_last_decoded['_links']['last']['href'], $this->viewUrl($view, 4));
+    $this->assertEqual(array_keys($actual_json_page_last_decoded['_links']), array(
+      'self',
+      'first',
+      'prev',
+      'last',
+    ));
+
+    // Test the entity rows - with paging.
+    $view = Views::getView('test_serializer_display_field');
+    $view->setDisplay('rest_export_paging');
+    $view->setCurrentPage(4);
+    $this->executeView($view);
+
+    // Create the entity collection.
+    $collection = $this->getCollectionFromView($view);
+    $this->assertTrue($collection->hasLinks(), 'Collection created from a paging view has (hypermedia) link relations');
+
+    $expected = $serializer->serialize($collection, 'hal_json');
+    $this->assertEqual($actual_json_page_last, $expected, 'The expected HAL output for last page was found.');
+  }
+  /**
    * Checks the behavior of the Serializer callback paths and row plugins.
    */
   public function testSerializerResponses() {
@@ -148,7 +327,7 @@ public function testSerializerResponses() {
 
     // Test the entity rows.
     $view = Views::getView('test_serializer_display_entity');
-    $view->initDisplay();
+    $view->setDisplay('rest_export_1');
     $this->executeView($view);
 
     // Get the serializer service.
@@ -173,11 +352,22 @@ public function testSerializerResponses() {
     $this->assertCacheTags($expected_cache_tags);
     $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
 
-    $expected = $serializer->serialize($entities, 'hal_json');
+    // Create the entity collection.
+    $collection = $this->getCollectionFromView($view);
+    $expected = $serializer->serialize($collection, 'hal_json');
     $actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'hal_json');
     $this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.');
     $this->assertCacheTags($expected_cache_tags);
 
+    // Make assertions on the structure of the response.
+    $actual_json_decoded = JSON::decode($actual_json);
+    $this->assertTrue(isset($actual_json_decoded['_embedded']), 'Has _embedded key.');
+    $this->assertTrue(isset($actual_json_decoded['_links']), 'Has _links key.');
+
+    $this->assertEqual(count($actual_json_decoded['_embedded']['item']), 10);
+    $this->assertEqual($actual_json_decoded['_links']['self']['href'], $this->viewUrl($view));
+    $this->assertEqual(array_keys($actual_json_decoded['_links']), ['self']);
+
     // Change the default format to xml.
     $view->setDisplay('rest_export_1');
     $view->getDisplay()->setOption('style', array(
@@ -233,6 +423,110 @@ protected function testSiteMaintenance() {
   }
 
   /**
+   * Build an absolute URL from a view path, accounting for paging.
+   *
+   * @param ViewExecutable $view
+   *   The View being executed.
+   * @param null $page
+   *
+   * @return string
+   */
+  protected function viewUrl(ViewExecutable $view, $page = NULL) {
+    $base_url = 'base://'  . $view->getPath();
+    $options = array(
+      'absolute' => TRUE,
+    );
+    if (isset($page)) {
+      $options += array('query' => array('page' => $page));
+    }
+    return Url::fromUri($base_url, $options)->toString();
+  }
+
+  /**
+   * Checks the paging behavior of callback paths and row plugins.
+   */
+  protected function testSerializerPageableCollectionHalJsonResponses() {
+    // Test the entity rows - with paging.
+    $view = Views::getView('test_serializer_display_entity');
+    $view->setDisplay('rest_export_paging');
+    $this->executeView($view);
+
+    // Get the serializer service.
+    $serializer = $this->container->get('serializer');
+
+    // Create the entity collection from the current view.
+    $collection = $this->getCollectionFromView($view);
+    $this->assertTrue($collection->hasLinks(), 'Collection created from a paging view has (hypermedia) link relations');
+
+    $expected = $serializer->serialize($collection, 'hal_json');
+    $actual_json = $this->drupalGetHalJson('test/serialize/entity_paging');
+    $this->assertIdentical(Json::encode($actual_json), $expected, 'The expected HAL output was found.');
+
+    // Make assertions on the structure of the response.
+    $this->assertTrue(isset($actual_json['_embedded']) && isset($actual_json['_links']), 'Has _links and _embedded keys');
+
+    $this->assertEqual(count($actual_json['_embedded']['item']), 1);
+    $this->assertEqual($actual_json['_links']['self']['href'], $this->viewUrl($view));
+    $this->assertEqual($actual_json['_links']['first']['href'], $this->viewUrl($view, 0));
+    $this->assertEqual($actual_json['_links']['next']['href'], $this->viewUrl($view, 1));
+    $this->assertEqual($actual_json['_links']['last']['href'], $this->viewUrl($view, 9));
+    $this->assertEqual(array_keys($actual_json['_links']), array(
+      'self',
+      'first',
+      'next',
+      'last',
+    ));
+
+    // Load the second page.
+    $actual_json_page_1 = $this->drupalGetHalJson($actual_json['_links']['next']['href']);
+    $this->assertTrue(isset($actual_json_page_1['_embedded']) && isset($actual_json_page_1['_links']), 'Has _links and _embedded keys');
+
+    $this->assertEqual(count($actual_json_page_1['_embedded']['item']), 1);
+    $this->assertEqual($actual_json_page_1['_links']['self']['href'], $this->viewUrl($view, 1));
+    $this->assertEqual($actual_json_page_1['_links']['first']['href'], $this->viewUrl($view, 0));
+    $this->assertEqual($actual_json_page_1['_links']['prev']['href'], $this->viewUrl($view, 0));
+    $this->assertEqual($actual_json_page_1['_links']['next']['href'], $this->viewUrl($view, 2));
+    $this->assertEqual($actual_json_page_1['_links']['last']['href'], $this->viewUrl($view, 9));
+    $this->assertEqual(array_keys($actual_json_page_1['_links']), array(
+      'self',
+      'first',
+      'prev',
+      'next',
+      'last',
+    ));
+
+    // Test the entity rows - with paging.
+    $view = Views::getView('test_serializer_display_entity');
+    $view->setDisplay('rest_export_paging');
+    $view->setCurrentPage(1);
+    $this->executeView($view);
+
+    // Create the entity collection.
+    $collection = $this->getCollectionFromView($view);
+    $this->assertTrue($collection->hasLinks(), 'Collection created from a paging view has (hypermedia) link relations');
+    $expected = $serializer->serialize($collection, 'hal_json');
+
+    $this->assertEqual(Json::encode($actual_json_page_1), $expected, 'The expected HAL output for page=1 was found.');
+
+    // Load the last page.
+    $actual_json_page_last = $this->drupalGetHalJSON($actual_json['_links']['last']['href']);
+
+    $this->assertTrue(isset($actual_json_page_last['_embedded']) && isset($actual_json_page_last['_links']), 'Has _links and _embedded keys');
+
+    $this->assertEqual(count($actual_json_page_last['_embedded']['item']), 1);
+    $this->assertEqual($actual_json_page_last['_links']['self']['href'], $this->viewUrl($view, 9));
+    $this->assertEqual($actual_json_page_last['_links']['first']['href'], $this->viewUrl($view, 0));
+    $this->assertEqual($actual_json_page_last['_links']['prev']['href'], $this->viewUrl($view, 8));
+    $this->assertEqual($actual_json_page_last['_links']['last']['href'], $this->viewUrl($view, 9));
+    $this->assertEqual(array_keys($actual_json_page_last['_links']), array(
+      'self',
+      'first',
+      'prev',
+      'last',
+    ));
+  }
+
+  /**
    * Sets up a request on the request stack with a specified format.
    *
    * @param string $format
@@ -557,6 +851,7 @@ public function testLivePreview() {
 
     $view = Views::getView('test_serializer_display_entity');
     $view->setDisplay('rest_export_1');
+    $view->display_handler->setContentType('json');
     $this->executeView($view);
 
     // Get the serializer service.
@@ -580,20 +875,15 @@ public function testLivePreview() {
 
     // Change the request format to xml.
     $view->setDisplay('rest_export_1');
-    $view->getDisplay()->setOption('style', array(
-      'type' => 'serializer',
-      'options' => array(
-        'uses_fields' => FALSE,
-        'formats' => array(
-          'xml' => 'xml',
-        ),
-      ),
-    ));
+    $view->display_handler->setContentType('xml');
 
     $this->executeView($view);
     $build = $view->preview();
     $rendered_xml = $build['#plain_text'];
     $this->assertEqual($rendered_xml, $expected, 'Ensure we preview xml when we change the request format.');
+
+    // @TODO add HAL+JSON and paging.
+
   }
 
   /**
diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity.yml
index 2a5818e..2eb605a 100644
--- a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity.yml
+++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_entity.yml
@@ -53,3 +53,24 @@ display:
       defaults:
         access: false
       path: test/serialize/entity
+  rest_export_paging:
+    display_plugin: rest_export
+    id: rest_export_paging
+    display_title: serializer
+    position: null
+    display_options:
+      defaults:
+        access: false
+      path: test/serialize/entity_paging
+      pager:
+        type: full
+        options:
+          items_per_page: 1
+          offset: 0
+          id: 0
+      style:
+        type: serializer
+        options:
+          uses_fields: false
+          formats:
+            hal_json: hal_json
diff --git a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_field.yml b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_field.yml
index 2b981f0..c9e78d0 100644
--- a/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_field.yml
+++ b/core/modules/rest/tests/modules/rest_test_views/test_views/views.view.test_serializer_display_field.yml
@@ -108,3 +108,31 @@ display:
         type: serializer
       row:
         type: data_field
+  rest_export_paging:
+    display_plugin: rest_export
+    id: rest_export_paging
+    display_title: 'serializer - paging'
+    position: null
+    display_options:
+      defaults:
+        access: false
+        row: false
+      style:
+        type: serializer
+        options:
+          uses_fields: false
+          formats:
+            hal_json: hal_json
+      path: test/serialize/field-paging
+      access:
+        type: none
+      style:
+        type: serializer
+      row:
+        type: data_field
+      pager:
+        type: full
+        options:
+          items_per_page: 1
+          offset: 0
+          id: 0
diff --git a/core/modules/rest/tests/src/Unit/Plugin/views/style/SerializerTest.php b/core/modules/rest/tests/src/Unit/Plugin/views/style/SerializerTest.php
index f9753b7..b4a61dc 100644
--- a/core/modules/rest/tests/src/Unit/Plugin/views/style/SerializerTest.php
+++ b/core/modules/rest/tests/src/Unit/Plugin/views/style/SerializerTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\rest\Unit\Plugin\views\style;
 
+use Drupal\Core\State\State;
+use Drupal\Core\Routing\UrlGenerator;
 use Drupal\rest\Plugin\views\display\RestExport;
 use Drupal\rest\Plugin\views\style\Serializer;
 use Drupal\Tests\UnitTestCase;
@@ -30,6 +32,16 @@ class SerializerTest extends UnitTestCase {
   protected $displayHandler;
 
   /**
+   * @var \Symfony\Component\Routing\Generator\UrlGenerator
+   */
+  protected $urlGenerator;
+
+  /**
+   * @var \Drupal\Core\State\State
+   */
+  protected $state;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
@@ -50,6 +62,14 @@ protected function setUp() {
     $this->displayHandler->expects($this->any())
       ->method('getContentType')
       ->willReturn('json');
+
+    $this->urlGenerator = $this->getMockBuilder(UrlGenerator::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->state = $this->getMockBuilder(State::class)
+      ->disableOriginalConstructor()
+      ->getMock();
   }
 
   /**
@@ -68,7 +88,7 @@ public function testSerializerReceivesOptions() {
       ->willReturn()
       ->shouldBeCalled();
 
-    $view_serializer_style = new Serializer([], 'dummy_serializer', [], $mock_serializer->reveal(), ['json', 'xml'], ['json' => 'serialization', 'xml' => 'serialization']);
+    $view_serializer_style = new Serializer([], 'dummy_serializer', [], $mock_serializer->reveal(), ['json', 'xml'], ['json' => 'serialization', 'xml' => 'serialization'], $this->urlGenerator, $this->state);
     $view_serializer_style->options = ['formats' => ['xml', 'json']];
     $view_serializer_style->view = $this->view;
     $view_serializer_style->displayHandler = $this->displayHandler;
diff --git a/core/modules/serialization/src/Collection.php b/core/modules/serialization/src/Collection.php
new file mode 100644
index 0000000..6022c50
--- /dev/null
+++ b/core/modules/serialization/src/Collection.php
@@ -0,0 +1,211 @@
+<?php
+
+namespace Drupal\serialization;
+
+/**
+ * Provides a wrapper for a collection of entities, e.g. a feed channel.
+ */
+class Collection implements \IteratorAggregate {
+
+  /**
+   * An internal identifier for this collection (e.g. view name).
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The items of the collection.
+   *
+   * @var array
+   */
+  protected $items = [];
+
+  /**
+   * The title of the collection.
+   *
+   * @var string
+   */
+  protected $title;
+
+  /**
+   * The URI of the collection.
+   *
+   * @var string
+   */
+  protected $uri;
+
+  /**
+   * The description of the collection.
+   *
+   * @var string
+   */
+  protected $description;
+
+  /**
+   * Hypermedia links (prev, next, first, last for paging collections)
+   *
+   * @var array
+   */
+  protected $links = [];
+
+  /**
+   * Constructor.
+   *
+   * @param string $collection_id
+   *   The internal identifier for the collection (e.g. view name).
+   */
+  public function __construct($collection_id) {
+    $this->id = $collection_id;
+  }
+
+  /**
+   * Get the internal ID of the collection.
+   */
+  public function getCollectionId() {
+    return $this->id;
+  }
+
+  /**
+   * Get the items in the collection.
+   *
+   * @return array
+   *   The items of the collection.
+   */
+  public function getItems() {
+    return $this->items;
+  }
+
+  /**
+   * Set the items list.
+   *
+   * @param array $items
+   *   The items of the collection.
+   */
+  public function setItems($items) {
+    $this->items = $items;
+  }
+
+  /**
+   * Get the collection title.
+   *
+   * @return string
+   *   The title of the collection.
+   */
+  public function getTitle() {
+    return $this->title;
+  }
+
+  /**
+   * Set the collection title.
+   *
+   * @param string $title
+   *   The items of the collection.
+   */
+  public function setTitle($title) {
+    $this->title = $title;
+  }
+
+  /**
+   * Get the collection URI.
+   *
+   * @return string
+   *   The URI ("self") of the collection.
+   */
+  public function getUri() {
+    return $this->uri;
+  }
+
+  /**
+   * Set the collection URI.
+   *
+   * @param string $uri
+   *   The URI ("self") of the collection.
+   */
+  public function setUri($uri) {
+    $this->uri = $uri;
+  }
+
+  /**
+   * Get the collection description.
+   *
+   * @return string
+   *   The description of the collection.
+   */
+  public function getDescription() {
+    return $this->description;
+  }
+
+  /**
+   * Set the collection URI.
+   *
+   * @param string $description
+   *   The description of the collection.
+   */
+  public function setDescription($description) {
+    $this->description = $description;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->getItems());
+  }
+
+  /**
+   * Sets a link URI for a given type.
+   *
+   * @param string $type
+   *   The link relation type.
+   * @param string $uri
+   *   The link relation URI.
+   */
+  public function setLink($type, $uri) {
+    $this->links[$type] = $uri;
+  }
+
+  /**
+   * Gets a link URI for a given type.
+   *
+   * @param string $type
+   *   The link relation type.
+   *
+   * @return NULL|String
+   *   The URI of the link relation type or NULL.
+   */
+  public function getLink($type) {
+    return isset($this->links[$type]) ? $this->links[$type] : NULL;
+  }
+
+  /**
+   * Sets all link URIs.
+   *
+   * @param array $links
+   *   Associative array of link relation types and URIs.
+   */
+  public function setLinks(array $links) {
+    $this->links = $links;
+  }
+
+  /**
+   * Gets all link URIs.
+   *
+   * @return array
+   *   Associative array of link relation types and URIs.
+   */
+  public function getLinks() {
+    return $this->links;
+  }
+
+  /**
+   * Returns true if (hypermedia) link relations have been added.
+   *
+   * @return bool
+   *   True if link relations have been added.
+   */
+  public function hasLinks() {
+    // $this->links should be an array, so no type checking is needed.
+    return (bool) count($this->links);
+  }
+}
diff --git a/core/modules/serialization/tests/src/Unit/CollectionTest.php b/core/modules/serialization/tests/src/Unit/CollectionTest.php
new file mode 100644
index 0000000..978e995
--- /dev/null
+++ b/core/modules/serialization/tests/src/Unit/CollectionTest.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Drupal\Tests\serialization\Unit;
+
+use Drupal\serialization\Collection;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the Collection class.
+ *
+ * @group Serialization
+ *
+ * @coversDefaultClass \Drupal\serialization\Collection
+ */
+class CollectionTest extends UnitTestCase {
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'CollectionTest',
+      'description' => 'Tests the Collection class used for serializing collections.',
+      'group' => 'Serialization',
+    );
+  }
+
+  /**
+   * Tests the constructor, as well as getters and setters.
+   *
+   * @covers ::__construct
+   * @covers ::getTitle
+   * @covers ::setTitle
+   * @covers ::getURI
+   * @covers ::setURI
+   * @covers ::getDescription
+   * @covers ::setDescription
+   */
+  public function testConstructor() {
+    $collection_id = $this->randomMachineName();
+    $collection = new Collection($collection_id);
+    $this->assertSame($collection_id, $collection->getCollectionId(), 'Id has been set accordingly');
+
+    $this->assertEquals($collection->getTitle(), NULL, 'Collection title is not set');
+    $collection->setTitle($collection_id);
+    $this->assertEquals($collection->getTitle(), $collection_id, 'Collection title has been set');
+
+    $this->assertEquals($collection->getUri(), NULL, 'Collection URI is not set');
+    $collection->setURI('http://example.com/' . $collection_id);
+    $this->assertEquals($collection->getUri(), 'http://example.com/' . $collection_id, 'Collection URI has been set');
+
+    $this->assertEquals($collection->getDescription(), NULL, 'Collection description is not set');
+    $collection->setDescription($collection_id);
+    $this->assertEquals($collection->getDescription(), $collection_id, 'Collection description has been set');
+  }
+
+  /**
+   * Tests items setter and getter as well as iteration.
+   *
+   * @covers ::setItems
+   * @covers ::getItems
+   * @covers ::getIterator
+   */
+  public function testSettingItemsAndIterating() {
+    $collection = new Collection('example');
+
+    $this->assertEquals($collection->getItems(), array(), 'By default the items are an empty array');
+
+    $example = array(1, 2, 3);
+    $collection->setItems($example);
+    $this->assertEquals($collection->getItems(), $example, 'Setters and getters work on the Collection');
+
+    // Iterating over collection returns the same array.
+    $array = array();
+    foreach ($collection as $k => $v) {
+      $array[$k] = $v;
+    }
+    $this->assertEquals($array, $collection->getItems(), 'Array filled via iteration matches the array of the getter');
+  }
+
+  /**
+   * Tests setters and getters for link relations.
+   *
+   * @covers ::setLinks
+   * @covers ::getLinks
+   * @covers ::hasLinks
+   * @covers ::getLink
+   * @covers ::setLink
+   */
+  public function testSetGetHasLinks() {
+    $collection = new Collection('example');
+    $this->assertFalse($collection->hasLinks(), 'By default a Collection does not have any link relations');
+
+    $collection->setURI('http://example.com/collection');
+    $this->assertFalse($collection->hasLinks(), 'Setting the main URI (self) of the collection does not count as a link relation');
+
+    $this->assertEquals($collection->getLink('next'), NULL, 'Before setting a link for a type the getter returns NULL');
+    $collection->setLink('next', 'http://example.com/collection/?page=1');
+    $this->assertEquals($collection->getLink('next'), 'http://example.com/collection/?page=1', 'After setting a link for a type the getter returns correctly');
+    $this->assertTrue($collection->hasLinks(), 'Setting another URI (self) of the collection does not count as a link relation');
+    $this->assertEquals($collection->getLinks(), array('next' => 'http://example.com/collection/?page=1'));
+
+    $collection->setLinks(array());
+    $this->assertFalse($collection->hasLinks(), 'After resetting links to an empty array, hasLinks should return false');
+  }
+}
