diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php
index 49418a0..730be99 100644
--- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php
+++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php
@@ -10,7 +10,7 @@
 use Drupal\Core\Entity\Field\FieldInterface;
 use Drupal\user\Plugin\Core\Entity\User;
 use Drupal\Core\TypedData\ContextAwareInterface;
-use Drupal\Core\TypedData\ContextAwareTypedData;
+use Drupal\Core\TypedData\ItemList;
 use Drupal\Core\TypedData\TypedDataInterface;
 use ArrayIterator;
 use IteratorAggregate;
@@ -27,7 +27,7 @@
  *
  * @see \Drupal\Core\Entity\Field\FieldInterface
  */
-class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInterface {
+class Field extends ItemList implements IteratorAggregate, FieldInterface {
 
   /**
    * Numerically indexed array of field items, implementing the
@@ -50,7 +50,7 @@ public function __construct(array $definition, $name = NULL, ContextAwareInterfa
   }
 
   /**
-   * Overrides \Drupal\Core\TypedData\TypedData::getValue().
+   * Overrides \Drupal\Core\TypedData\ItemList::getValue().
    */
   public function getValue() {
     if (isset($this->list)) {
@@ -68,10 +68,7 @@ public function getValue() {
   }
 
   /**
-   * Overrides \Drupal\Core\TypedData\TypedData::setValue().
-   *
-   * @param array|null $values
-   *   An array of values of the field items, or NULL to unset the field.
+   * Overrides \Drupal\Core\TypedData\ItemList::setValue().
    */
   public function setValue($values) {
     if (!isset($values) || $values === array()) {
@@ -104,111 +101,6 @@ public function setValue($values) {
   }
 
   /**
-   * Overrides \Drupal\Core\TypedData\TypedData::getString().
-   */
-  public function getString() {
-    $strings = array();
-    if (isset($this->list)) {
-      foreach ($this->list as $item) {
-        $strings[] = $item->getString();
-      }
-      return implode(', ', array_filter($strings));
-    }
-  }
-
-  /**
-   * Overrides \Drupal\Core\TypedData\TypedData::getConstraints().
-   */
-  public function getConstraints() {
-    // Apply the constraints to the list items only.
-    return array();
-  }
-
-  /**
-   * Implements \ArrayAccess::offsetExists().
-   */
-  public function offsetExists($offset) {
-    return isset($this->list) && array_key_exists($offset, $this->list);
-  }
-
-  /**
-   * Implements \ArrayAccess::offsetUnset().
-   */
-  public function offsetUnset($offset) {
-    if (isset($this->list)) {
-      unset($this->list[$offset]);
-    }
-  }
-
-  /**
-   * Implements \ArrayAccess::offsetGet().
-   */
-  public function offsetGet($offset) {
-    if (!is_numeric($offset)) {
-      throw new InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.');
-    }
-    // Allow getting not yet existing items as well.
-    // @todo: Maybe add a public createItem() method in addition?
-    elseif (!isset($this->list[$offset])) {
-      $this->list[$offset] = $this->createItem($offset);
-    }
-    return $this->list[$offset];
-  }
-
-  /**
-   * Helper for creating a list item object.
-   *
-   * @return \Drupal\Core\TypedData\TypedDataInterface
-   */
-  protected function createItem($offset = 0, $value = NULL) {
-    return typed_data()->getPropertyInstance($this, $offset, $value);
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ListInterface::getItemDefinition().
-   */
-  public function getItemDefinition() {
-    return array('list' => FALSE) + $this->definition;
-  }
-
-  /**
-   * Implements \ArrayAccess::offsetSet().
-   */
-  public function offsetSet($offset, $value) {
-    if (!isset($offset)) {
-      // The [] operator has been used so point at a new entry.
-      $offset = $this->list ? max(array_keys($this->list)) + 1 : 0;
-    }
-    if (is_numeric($offset)) {
-      // Support setting values via typed data objects.
-      if ($value instanceof TypedDataInterface) {
-        $value = $value->getValue();
-      }
-      $this->offsetGet($offset)->setValue($value);
-    }
-    else {
-      throw new InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.');
-    }
-  }
-
-  /**
-   * Implements \IteratorAggregate::getIterator().
-   */
-  public function getIterator() {
-    if (isset($this->list)) {
-      return new ArrayIterator($this->list);
-    }
-    return new ArrayIterator(array());
-  }
-
-  /**
-   * Implements \Countable::count().
-   */
-  public function count() {
-    return isset($this->list) ? count($this->list) : 0;
-  }
-
-  /**
    * Implements \Drupal\Core\Entity\Field\FieldInterface::getPropertyDefinition().
    */
   public function getPropertyDefinition($name) {
@@ -258,34 +150,6 @@ public function __unset($property_name) {
   }
 
   /**
-   * Implements \Drupal\Core\TypedData\ListInterface::isEmpty().
-   */
-  public function isEmpty() {
-    if (isset($this->list)) {
-      foreach ($this->list as $item) {
-        if (!$item->isEmpty()) {
-          return FALSE;
-        }
-      }
-    }
-    return TRUE;
-  }
-
-  /**
-   * Magic method: Implements a deep clone.
-   */
-  public function __clone() {
-    if (isset($this->list)) {
-      foreach ($this->list as $delta => $item) {
-        $this->list[$delta] = clone $item;
-        if ($item instanceof ContextAwareInterface) {
-          $this->list[$delta]->setContext($delta, $this);
-        }
-      }
-    }
-  }
-
-  /**
    * Implements \Drupal\Core\TypedData\AccessibleInterface::access().
    */
   public function access($operation = 'view', User $account = NULL) {
diff --git a/core/lib/Drupal/Core/TypedData/ItemList.php b/core/lib/Drupal/Core/TypedData/ItemList.php
new file mode 100644
index 0000000..997a4de
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/ItemList.php
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\List.
+ */
+
+namespace Drupal\Core\TypedData;
+
+use ArrayIterator;
+use IteratorAggregate;
+use InvalidArgumentException;
+
+/**
+ * A generic list class.
+ *
+ * This class can serve as list for any type of items.
+ * Note: The class cannot be called "List" as list is a reserved PHP keyword.
+ */
+class ItemList extends ContextAwareTypedData implements IteratorAggregate, ListInterface {
+
+  /**
+   * Numerically indexed array items.
+   *
+   * @var array
+   */
+  protected $list = array();
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::getValue().
+   */
+  public function getValue() {
+    if (isset($this->list)) {
+      $values = array();
+      foreach ($this->list as $delta => $item) {
+        $values[$delta] = $item->getValue();
+      }
+      return $values;
+    }
+  }
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::setValue().
+   *
+   * @param array|null $values
+   *   An array of values of the field items, or NULL to unset the field.
+   */
+  public function setValue($values) {
+    if (!isset($values) || $values === array()) {
+      $this->list = $values;
+    }
+    else {
+      if (!is_array($values)) {
+        throw new InvalidArgumentException('Cannot set a list with a non-array value.');
+      }
+
+      // Clear the values of properties for which no value has been passed.
+      if (isset($this->list)) {
+        $this->list = array_intersect_key($this->list, $values);
+      }
+
+      // Set the values.
+      foreach ($values as $delta => $value) {
+        if (!is_numeric($delta)) {
+          throw new InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.');
+        }
+        elseif (!isset($this->list[$delta])) {
+          $this->list[$delta] = $this->createItem($delta, $value);
+        }
+        else {
+          $this->list[$delta]->setValue($value);
+        }
+      }
+    }
+  }
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::getString().
+   */
+  public function getString() {
+    $strings = array();
+    if (isset($this->list)) {
+      foreach ($this->list as $item) {
+        $strings[] = $item->getString();
+      }
+      return implode(', ', array_filter($strings, 'strlen'));
+    }
+  }
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::getConstraints().
+   */
+  public function getConstraints() {
+    // Apply the constraints to the list items only.
+    return array();
+  }
+
+  /**
+   * Implements \ArrayAccess::offsetExists().
+   */
+  public function offsetExists($offset) {
+    return isset($this->list) && array_key_exists($offset, $this->list) && $this->offsetGet($offset)->getValue() !== NULL;
+  }
+
+  /**
+   * Implements \ArrayAccess::offsetUnset().
+   */
+  public function offsetUnset($offset) {
+    if (isset($this->list)) {
+      unset($this->list[$offset]);
+    }
+  }
+
+  /**
+   * Implements \ArrayAccess::offsetGet().
+   */
+  public function offsetGet($offset) {
+    if (!is_numeric($offset)) {
+      throw new InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.');
+    }
+    // Allow getting not yet existing items as well.
+    // @todo: Maybe add a public createItem() method in addition?
+    elseif (!isset($this->list[$offset])) {
+      $this->list[$offset] = $this->createItem($offset);
+    }
+    return $this->list[$offset];
+  }
+
+  /**
+   * Helper for creating a list item object.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   */
+  protected function createItem($offset = 0, $value = NULL) {
+    return typed_data()->getPropertyInstance($this, $offset, $value);
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ListInterface::getItemDefinition().
+   */
+  public function getItemDefinition() {
+    return array('list' => FALSE) + $this->definition;
+  }
+
+  /**
+   * Implements \ArrayAccess::offsetSet().
+   */
+  public function offsetSet($offset, $value) {
+    if (!isset($offset)) {
+      // The [] operator has been used so point at a new entry.
+      $offset = $this->list ? max(array_keys($this->list)) + 1 : 0;
+    }
+    if (is_numeric($offset)) {
+      // Support setting values via typed data objects.
+      if ($value instanceof TypedDataInterface) {
+        $value = $value->getValue();
+      }
+      $this->offsetGet($offset)->setValue($value);
+    }
+    else {
+      throw new InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.');
+    }
+  }
+
+  /**
+   * Implements \IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    if (isset($this->list)) {
+      return new ArrayIterator($this->list);
+    }
+    return new ArrayIterator(array());
+  }
+
+  /**
+   * Implements \Countable::count().
+   */
+  public function count() {
+    return isset($this->list) ? count($this->list) : 0;
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ListInterface::isEmpty().
+   */
+  public function isEmpty() {
+    if (isset($this->list)) {
+      foreach ($this->list as $item) {
+        if ($item instanceof ComplexDataInterface || $item instanceof ListInterface) {
+          if (!$item->isEmpty()) {
+            return FALSE;
+          }
+        }
+        // Other items are treated as empty if they have no value only.
+        elseif ($item->getValue() !== NULL) {
+          return FALSE;
+        }
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * Magic method: Implements a deep clone.
+   */
+  public function __clone() {
+    if (isset($this->list)) {
+      foreach ($this->list as $delta => $item) {
+        $this->list[$delta] = clone $item;
+        if ($item instanceof ContextAwareInterface) {
+          $this->list[$delta]->setContext($delta, $this);
+        }
+      }
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/TypedData/Type/Any.php b/core/lib/Drupal/Core/TypedData/Type/Any.php
new file mode 100644
index 0000000..21afa99
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Type/Any.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Type\Any.
+ */
+
+namespace Drupal\Core\TypedData\Type;
+
+use Drupal\Core\TypedData\TypedData;
+
+/**
+ * The "any" data type.
+ *
+ * The "any" data type does not implement a list or complex data interface, nor
+ * is it mappable to any primitive type. Thus, it may contain any PHP data for
+ * which no further metadata is available.
+ */
+class Any extends TypedData {
+
+  /**
+   * The data value.
+   *
+   * @var mixed
+   */
+  protected $value;
+}
diff --git a/core/lib/Drupal/Core/TypedData/Type/Map.php b/core/lib/Drupal/Core/TypedData/Type/Map.php
new file mode 100644
index 0000000..a1d474d
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Type/Map.php
@@ -0,0 +1,182 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Type\Map.
+ */
+
+namespace Drupal\Core\TypedData\Type;
+
+use Drupal\Core\TypedData\ContextAwareTypedData;
+use Drupal\Core\TypedData\ComplexDataInterface;
+use ArrayIterator;
+use IteratorAggregate;
+
+/**
+ * The "map" data type.
+ *
+ * The "map" data type represent a simple complex data type, e.g. for
+ * representing associative arrays. It can also serve as base class for any
+ * complex data type.
+ *
+ * By default there is no metadata for contained properties. Extending classes
+ * may want to override Map::getPropertyDefinitions() to define it.
+ */
+class Map extends ContextAwareTypedData implements IteratorAggregate, ComplexDataInterface {
+
+  /**
+   * An array of values for the contained properties.
+   *
+   * @var array
+   */
+  protected $values = array();
+
+  /**
+   * The array of properties, each implementing the TypedDataInterface.
+   *
+   * @var array
+   */
+  protected $properties;
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    $definitions = array();
+    foreach ($this->values as $name => $value) {
+      $definitions[$name] = array(
+        'type' => 'any',
+      );
+    }
+    return $definitions;
+  }
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::getValue().
+   */
+  public function getValue() {
+    return $this->values;
+  }
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::setValue().
+   *
+   * @param array|null $values
+   *   An array of property values.
+   */
+  public function setValue($values) {
+    if (isset($values) && !is_array($values)) {
+      throw new \InvalidArgumentException("Invalid values given. Values must be represented as an associative array.");
+    }
+    $this->values = $values;
+    unset($this->properties);
+  }
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::getString().
+   */
+  public function getString() {
+    $strings = array();
+    foreach ($this->getProperties() as $property) {
+      $strings[] = $property->getString();
+    }
+    return implode(', ', array_filter($strings, 'strlen'));
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::get().
+   */
+  public function get($property_name) {
+    if (!$this->getPropertyDefinition($property_name)) {
+      throw new \InvalidArgumentException('Property ' . check_plain($property_name) . ' is unknown.');
+    }
+    if (!isset($this->properties[$property_name])) {
+      $this->properties[$property_name] = typed_data()->getPropertyInstance($this, $property_name, isset($this->values[$property_name]) ? $this->values[$property_name] : NULL);
+    }
+    return $this->properties[$property_name];
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
+   */
+  public function set($property_name, $value) {
+    $this->get($property_name)->setValue($value);
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getProperties().
+   */
+  public function getProperties($include_computed = FALSE) {
+    $properties = array();
+    foreach ($this->getPropertyDefinitions() as $name => $definition) {
+      if ($include_computed || empty($definition['computed'])) {
+        $properties[$name] = $this->get($name);
+      }
+    }
+    return $properties;
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues().
+   */
+  public function getPropertyValues() {
+    $values = array();
+    foreach ($this->getProperties() as $name => $property) {
+      $values[$name] = $property->getValue();
+    }
+    return $values;
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues().
+   */
+  public function setPropertyValues($values) {
+    foreach ($values as $name => $value) {
+      $this->get($name)->setValue($value);
+    }
+  }
+
+  /**
+   * Implements \IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    return new ArrayIterator($this->getProperties());
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
+   */
+  public function getPropertyDefinition($name) {
+    $definitions = $this->getPropertyDefinitions();
+    if (isset($definitions[$name])) {
+      return $definitions[$name];
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
+   */
+  public function isEmpty() {
+    foreach ($this->getProperties() as $property) {
+      if ($property->getValue() !== NULL) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * Magic method: Implements a deep clone.
+   */
+  public function __clone() {
+    foreach ($this->getProperties() as $name => $property) {
+      $this->properties[$name] = clone $property;
+      if ($property instanceof ContextAwareInterface) {
+        $this->properties[$name]->setContext($name, $this);
+      }
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
index 8b5306d..0f8dc73 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\TypedData;
 
 use InvalidArgumentException;
+use Drupal\Component\Plugin\Discovery\ProcessDecorator;
 use Drupal\Component\Plugin\PluginManagerBase;
 use Drupal\Core\Plugin\Discovery\CacheDecorator;
 use Drupal\Core\Plugin\Discovery\HookDiscovery;
@@ -37,6 +38,17 @@ class TypedDataManager extends PluginManagerBase {
   protected $constraintManager;
 
   /**
+   * Type definition defaults which are merged in by the ProcessDecorator.
+   *
+   * @see \Drupal\Component\Plugin\PluginManagerBase::processDefinition()
+   *
+   * @var array
+   */
+  protected $defaults = array(
+    'list class' => '\Drupal\Core\TypedData\ItemList',
+  );
+
+  /**
    * An array of typed data property prototypes.
    *
    * @var array
@@ -44,7 +56,10 @@ class TypedDataManager extends PluginManagerBase {
   protected $prototypes = array();
 
   public function __construct() {
-    $this->discovery = new CacheDecorator(new HookDiscovery('data_type_info'), 'typed_data:types');
+    $this->discovery = new HookDiscovery('data_type_info');
+    $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
+    $this->discovery = new CacheDecorator($this->discovery, 'typed_data:types');
+
     $this->factory = new TypedDataFactory($this->discovery);
   }
 
@@ -191,14 +206,21 @@ public function getInstance(array $options) {
    * @see \Drupal\Core\TypedData\TypedDataManager::create()
    */
   public function getPropertyInstance(ContextAwareInterface $object, $property_name, $value = NULL) {
-    $key = $object->getRoot()->getType() . ':' . $object->getPropertyPath() . '.';
-    // If we are creating list items, we always use 0 in the key as all list
-    // items look the same.
-    $key .= is_numeric($property_name) ? 0 : $property_name;
+    if ($root = $object->getRoot()) {
+      $key = $root->getType() . ':' . $object->getPropertyPath() . '.';
+      // If we are creating list items, we always use 0 in the key as all list
+      // items look the same.
+      $key .= is_numeric($property_name) ? 0 : $property_name;
+    }
+    else {
+      // Missing context, thus we cannot determine a unique key for prototyping.
+      // Fall back to create a new prototype on each call.
+      $key = FALSE;
+    }
 
     // Make sure we have a prototype. Then, clone the prototype and set object
     // specific values, i.e. the value and the context.
-    if (!isset($this->prototypes[$key])) {
+    if (!isset($this->prototypes[$key]) || !$key) {
       // Create the initial prototype. For that we need to fetch the definition
       // of the to be created property instance from the parent.
       if ($object instanceof ComplexDataInterface) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php
index e7a94f7..583015b 100644
--- a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php
@@ -200,6 +200,189 @@ public function testGetAndSet() {
   }
 
   /**
+   * Tests using typed data lists.
+   */
+  public function testTypedDataLists() {
+    // Test working with an existing list of strings.
+    $value = array('one', 'two', 'three');
+    $typed_data = $this->createTypedData(array(
+      'type' => 'string',
+      'list' => TRUE,
+    ), $value);
+    $this->assertEqual($typed_data->getValue(), $value, 'List value has been set.');
+    // Test iterating.
+    $count = 0;
+    foreach ($typed_data as $item) {
+      $this->assertTrue($item instanceof \Drupal\Core\TypedData\TypedDataInterface);
+      $count++;
+    }
+    $this->assertEqual($count, 3);
+
+    // Test getting the string representation.
+    $this->assertEqual($typed_data->getString(), 'one, two, three');
+    $typed_data[1] = '';
+    $this->assertEqual($typed_data->getString(), 'one, three');
+
+    // Test using array access.
+    $this->assertEqual($typed_data[0]->getValue(), 'one');
+    $typed_data[4] = 'four';
+    $this->assertEqual($typed_data[4]->getValue(), 'four');
+    $typed_data[] = 'five';
+    $this->assertEqual($typed_data[5]->getValue(), 'five');
+    $this->assertEqual($typed_data->count(), 5);
+    $this->assertTrue(isset($typed_data[0]));
+    $this->assertTrue(!isset($typed_data[6]));
+
+    // Test isEmpty and cloning.
+    $this->assertFalse($typed_data->isEmpty());
+    $clone = clone $typed_data;
+    $this->assertTrue($typed_data->getValue() === $clone->getValue());
+    $this->assertTrue($typed_data[0] !== $clone[0]);
+    $clone->setValue(array());
+    $this->assertTrue($clone->isEmpty());
+
+    // Make sure the difference between NULL (not set) and an empty array is
+    // kept.
+    $clone->setValue(array());
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue());
+    $this->assertIdentical($clone->getValue(), array());
+
+    // Test dealing with NULL items.
+    $typed_data[] = NULL;
+    $this->assertTrue($typed_data->isEmpty());
+    $this->assertEqual(count($typed_data), 1);
+    $typed_data[] = '';
+    $this->assertFalse($typed_data->isEmpty());
+    $this->assertEqual(count($typed_data), 2);
+    $typed_data[] = 'three';
+    $this->assertFalse($typed_data->isEmpty());
+    $this->assertEqual(count($typed_data), 3);
+
+    $this->assertEqual($typed_data->getValue(), array(NULL, '', 'three'));
+    // Test unsetting.
+    unset($typed_data[2]);
+    $this->assertEqual(count($typed_data), 2);
+    $this->assertNull($typed_data[3]->getValue());
+
+    // Getting a not set list item sets it.
+    $this->assertNull($typed_data[4]->getValue());
+    $this->assertEqual(count($typed_data), 4);
+
+    // Test setting the list with less values.
+    $typed_data->setValue(array('one'));
+    $this->assertEqual($typed_data->count(), 1);
+
+    // Test setting invalid values.
+    try {
+      $typed_data->setValue(array('not a list' => 'one'));
+      $this->fail('No exception has been thrown when setting an invalid value.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Exception thrown:' . $e->getMessage());
+    }
+    try {
+      $typed_data->setValue('string');
+      $this->fail('No exception has been thrown when setting an invalid value.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Exception thrown:' . $e->getMessage());
+    }
+  }
+
+  /**
+   * Tests using a typed data map.
+   */
+  public function testTypedDataMaps() {
+    // Test working with a simple map.
+    $value = array(
+      'one' => 'eins',
+      'two' => 'zwei',
+      'three' => 'drei',
+    );
+    $typed_data = $this->createTypedData(array(
+      'type' => 'map',
+    ), $value);
+
+    // Test iterating.
+    $count = 0;
+    foreach ($typed_data as $item) {
+      $this->assertTrue($item instanceof \Drupal\Core\TypedData\TypedDataInterface);
+      $count++;
+    }
+    $this->assertEqual($count, 3);
+
+    // Test retrieving metadata.
+    $this->assertEqual(array_keys($typed_data->getPropertyDefinitions()), array_keys($value));
+    $definition = $typed_data->getPropertyDefinition('one');
+    $this->assertEqual($definition['type'], 'any');
+    $this->assertFalse($typed_data->getPropertyDefinition('invalid'));
+
+    // Test getting and setting properties.
+    $this->assertEqual($typed_data->get('one')->getValue(), 'eins');
+    $this->assertEqual($typed_data->getPropertyValues(), $value);
+    $typed_data->set('one', 'uno');
+    $this->assertEqual($typed_data->get('one')->getValue(), 'uno');
+
+    $properties = $typed_data->getProperties();
+    $this->assertEqual(array_keys($properties), array_keys($value));
+    $this->assertIdentical($properties['one'], $typed_data->get('one'));
+
+    $typed_data->setPropertyValues(array('one' => 'eins'));
+    $this->assertEqual($typed_data->get('one')->getValue(), 'eins');
+    $this->assertEqual($typed_data->get('two')->getValue(), 'zwei');
+    $this->assertEqual($typed_data->get('three')->getValue(), 'drei');
+
+    $typed_data->setValue(array('foo' => 'bar'));
+    $this->assertEqual(array_keys($typed_data->getProperties()), array('foo'));
+
+    // Test getting the string representation.
+    $typed_data->setValue(array('one' => 'eins', 'two' => '', 'three' => 'drei'));
+    $this->assertEqual($typed_data->getString(), 'eins, drei');
+
+    // Test isEmpty and cloning.
+    $this->assertFalse($typed_data->isEmpty());
+    $clone = clone $typed_data;
+    $this->assertTrue($typed_data->getValue() === $clone->getValue());
+    $this->assertTrue($typed_data->get('one') !== $clone->get('one'));
+    $clone->setValue(array());
+    $this->assertTrue($clone->isEmpty());
+
+    // Make sure the difference between NULL (not set) and an empty array is
+    // kept.
+    $clone->setValue(array());
+    $typed_data->setValue(NULL);
+    $this->assertNull($typed_data->getValue());
+    $this->assertIdentical($clone->getValue(), array());
+
+    // Test accessing invalid properties.
+    $typed_data->setValue($value);
+    try {
+      $typed_data->get('invalid');
+      $this->fail('No exception has been thrown when getting an invalid value.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Exception thrown:' . $e->getMessage());
+    }
+    try {
+      $typed_data->set('invalid', 'foo');
+      $this->fail('No exception has been thrown when setting an invalid value.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Exception thrown:' . $e->getMessage());
+    }
+
+    // Test setting invalid values.
+    try {
+      $typed_data->setValue('invalid');
+      $this->fail('No exception has been thrown when setting an invalid value.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Exception thrown:' . $e->getMessage());
+    }
+  }
+
+  /**
    * Tests typed data validation.
    */
   public function testTypedDataValidation() {
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 2f98dad..963d454 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -158,7 +158,8 @@ function hook_cron() {
  *     \Drupal\Core\TypedData\TypedDataInterface.
  *   - list class: (optional) A typed data class used for wrapping multiple
  *     data items of the type. Must implement the
- *     \Drupal\Core\TypedData\ListInterface.
+ *     \Drupal\Core\TypedData\ListInterface. Defaults to
+ *     \Drupal\Core\TypedData\ItemList;
  *   - primitive type: (optional) Maps the data type to one of the pre-defined
  *     primitive types in \Drupal\Core\TypedData\Primitive. If set, it must be
  *     a constant defined by \Drupal\Core\TypedData\Primitive such as
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index a21caa6..5016b99 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2227,6 +2227,14 @@ function system_data_type_info() {
       'class' => '\Drupal\Core\TypedData\Type\Binary',
       'primitive type' => Primitive::BINARY,
     ),
+    'map' => array(
+      'label' => t('Map'),
+      'class' =>  '\Drupal\Core\TypedData\Type\Map',
+    ),
+    'any' => array(
+      'label' => t('Any data'),
+      'class' =>  '\Drupal\Core\TypedData\Type\Any',
+    ),
     'language' => array(
       'label' => t('Language'),
       'description' => t('A language object.'),
