diff --git a/src/Iterators/UniqueFilter.php b/src/Iterators/UniqueFilter.php
index 60b35356c8d775bf5b83d638277de43fcb0f723c..a750b8c2b9ba1bbb5daaeb1f816ecc0790e510f3 100644
--- a/src/Iterators/UniqueFilter.php
+++ b/src/Iterators/UniqueFilter.php
@@ -36,7 +36,7 @@ final class UniqueFilter
      */
     public function __invoke($value): bool
     {
-        $key = is_object($value) ? spl_object_hash($value) : (string) $value;
+        $key = \is_object($value) ? \spl_object_hash($value) : (string) $value;
         $alreadySeen = isset($this->seen[$key]);
         $this->seen[$key] = true;
 
diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php
index 40f7ef7bfa3b8ccd0fa1ee46e4e12350359d5092..c2548587aac22d4e90c663096a7d5387d4f84f37 100644
--- a/src/Metadata/MetadataFactory.php
+++ b/src/Metadata/MetadataFactory.php
@@ -25,9 +25,11 @@ use ApiPlatform\Core\Api\OperationType;
 use ApiPlatform\Core\Api\ResourceClassResolverInterface;
 use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
 use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
+use ApiPlatform\Core\Metadata\Property\PropertyMetadata as APIPropertyMetadata;
 use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
 use ApiPlatform\Core\Metadata\Resource\ResourceMetadata as APIResourceMetadata;
 use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
+use Doctrine\Common\Inflector\Inflector;
 use Irstea\NgModelGeneratorBundle\Models\PHPClass;
 use Psr\Container\ContainerInterface;
 use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
@@ -68,6 +70,11 @@ final class MetadataFactory implements MetadataFactoryInterface
     /** @var SerializationMetadata[] */
     private $serializations = [];
 
+    /**
+     * @var ClassHierarchy
+     */
+    private $classHierarchy;
+
     /**
      * MetadataFactory constructor.
      *
@@ -80,6 +87,7 @@ final class MetadataFactory implements MetadataFactoryInterface
      * @param OperationPathResolverInterface         $operationPathResolver
      * @param ContainerInterface                     $filterLocator
      * @param PaginationMetadata                     $paginationMetadata
+     * @param ClassHierarchy                         $classHierarchy
      */
     public function __construct(
         ResourceClassResolverInterface $resourceClassResolver,
@@ -90,7 +98,8 @@ final class MetadataFactory implements MetadataFactoryInterface
         OperationMethodResolverInterface $operationMethodResolver,
         OperationPathResolverInterface $operationPathResolver,
         ContainerInterface $filterLocator,
-        PaginationMetadata $paginationMetadata
+        PaginationMetadata $paginationMetadata,
+        ClassHierarchy $classHierarchy
     ) {
         $this->resourceClassResolver = $resourceClassResolver;
         $this->resourceMetadataFactory = $resourceMetadataFactory;
@@ -101,6 +110,7 @@ final class MetadataFactory implements MetadataFactoryInterface
         $this->operationPathResolver = $operationPathResolver;
         $this->filterLocator = $filterLocator;
         $this->paginationMetadata = $paginationMetadata;
+        $this->classHierarchy = $classHierarchy;
     }
 
     /**
@@ -128,16 +138,6 @@ final class MetadataFactory implements MetadataFactoryInterface
             $metadata->getShortName(),
             $metadata->getDescription(),
             $classMeta->isAbstract(),
-            $this->getSerialization(
-                $class,
-                true,
-                $metadata->getAttribute('normalization_context', [])['groups'] ?? []
-            ),
-            $this->getSerialization(
-                $class,
-                false,
-                $metadata->getAttribute('normalization_context', [])['groups'] ?? []
-            ),
             $this->getOperations($class)
         );
     }
@@ -158,137 +158,6 @@ final class MetadataFactory implements MetadataFactoryInterface
         );
     }
 
-    /**
-     * @param PHPClass $class
-     * @param bool     $normalization
-     * @param string[] $groups
-     *
-     * @return SerializationMetadata
-     */
-    private function getSerialization(PHPClass $class, bool $normalization, array $groups): SerializationMetadata
-    {
-        sort($groups);
-        $key = sprintf('%s:%d:%s', $class->getFullName(), $normalization, implode('+', $groups));
-        if (!isset($this->serializations[$key])) {
-            $this->serializations[$key] = $this->doGetSerialization($class, $normalization, $groups);
-        }
-
-        return $this->serializations[$key];
-    }
-
-    /**
-     * @param PHPClass $class
-     * @param bool     $normalization
-     * @param string[] $groups
-     *
-     * @return SerializationMetadata
-     */
-    private function doGetSerialization(PHPClass $class, bool $normalization, array $groups): SerializationMetadata
-    {
-        /** @var RepresentationMetadata[] $reprs */
-        $representations = [];
-
-        /** @var string[] $queue */
-        $queue = [$class->getFullName()];
-
-        while ($queue) {
-            /** @var string $currentName */
-            $currentName = array_shift($queue);
-            $current = PHPClass::get($currentName);
-
-            if (isset($representations[$currentName]) || !$this->isResource($current)) {
-                continue;
-            }
-
-            $parent = null;
-            $reflParent = $current->getReflection()->getParentClass();
-            if ($reflParent) {
-                $parent = PHPClass::get($reflParent->getName());
-                $queue[] = $reflParent->getName();
-            }
-
-            $properties = $this->getProperties($current, $normalization, $groups);
-            foreach ($properties as $property) {
-                if ($property->getType()->getClassName()) {
-                    $queue[] = $property->getType()->getClassName();
-                }
-            }
-
-            $representations[$currentName] = new RepresentationMetadata($current, $parent, $properties);
-        }
-
-        return new SerializationMetadata($class, $groups, $normalization, $representations);
-    }
-
-    /**
-     * @param PHPClass $class
-     * @param bool     $normalization
-     * @param array    $groups
-     *
-     * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException
-     * @throws \ApiPlatform\Core\Exception\ResourceClassNotFoundException
-     *
-     * @return PropertyMetadata[]
-     */
-    private function getProperties(PHPClass $class, bool $normalization, array $groups): array
-    {
-        $className = $class->getFullName();
-        $properties = [];
-
-        $options = $groups ? ['serializer_groups' => $groups] : [];
-
-        foreach ($this->propertyNameCollectionFactory->create($className, $options) as $propertyName) {
-            \assert(\is_string($propertyName));
-
-            $propertyMeta = $this->propertyMetadataFactory->create($className, $propertyName);
-
-            if ($propertyMeta->isChildInherited()) {
-                continue;
-            }
-
-            $typeMeta = $propertyMeta->getType();
-            if (!$typeMeta) {
-                continue;
-            }
-
-            $readable = $propertyMeta->isReadable() && $this->propertyInfoExtractor->isReadable($className, $propertyName);
-            $writable = ($propertyMeta->isWritable() && $this->propertyInfoExtractor->isWritable($className, $propertyName));
-            $initializable = (bool) $propertyMeta->isInitializable();
-            $identifier = (bool) $propertyMeta->isIdentifier();
-
-            if ($normalization ? !$readable : !($identifier || $writable || $initializable)) {
-                continue;
-            }
-
-            $link = false;
-            $embedded = true;
-
-            $leafType = $typeMeta;
-            while ($leafType && $leafType->isCollection()) {
-                $leafType = $leafType->getCollectionValueType();
-            }
-            $propClass = $leafType ? $leafType->getClassName() : null;
-            if ($propClass && $this->isResource(PHPClass::get($propClass))) {
-                $link = true;
-                $embedded = \count($this->propertyNameCollectionFactory->create($propClass, $options)) > 0;
-            }
-
-            $properties[$propertyName] = new PropertyMetadata(
-                $propertyName,
-                $this->propertyInfoExtractor->getShortDescription($className, $propertyName) ?: '',
-                $typeMeta,
-                $identifier,
-                $readable,
-                $writable,
-                $initializable,
-                $link,
-                $embedded
-            );
-        }
-
-        return $properties;
-    }
-
     /**
      * Get paginationMetadata.
      *
@@ -365,8 +234,11 @@ final class MetadataFactory implements MetadataFactoryInterface
             $pagination = null;
         }
 
-        if (\in_array($method, ['GET', 'PUT', 'POST'], true)) {
-            $normalization = $this->getSerialization(
+        $opDef = new OperationDef($name, $method, $type === OperationType::COLLECTION);
+
+        if ($opDef->hasNormalization()) {
+            $normalization = $this->getOperationSerialization(
+                $opDef,
                 $class,
                 true,
                 $getAttribute('normalization_context', [])['groups'] ?? []
@@ -375,8 +247,9 @@ final class MetadataFactory implements MetadataFactoryInterface
             $normalization = null;
         }
 
-        if (\in_array($method, ['POST', 'PUT'], true)) {
-            $denormalization = $this->getSerialization(
+        if ($opDef->hasDenormalization()) {
+            $denormalization = $this->getOperationSerialization(
+                $opDef,
                 $class,
                 false,
                 $getAttribute('denormalization_context', [])['groups'] ?? []
@@ -386,10 +259,8 @@ final class MetadataFactory implements MetadataFactoryInterface
         }
 
         return new OperationMetadata(
-            $name,
+            $opDef,
             $operation['description'] ?? '',
-            $type,
-            $method,
             $path,
             $getAttribute('requirements', []),
             $filters,
@@ -423,4 +294,221 @@ final class MetadataFactory implements MetadataFactoryInterface
 
         return $filters;
     }
+
+    /**
+     * @parma string $operationName
+     *
+     * @param PHPClass $class
+     * @param bool     $normalization
+     * @param string[] $groups
+     *
+     * @return SerializationMetadata
+     */
+    private function getOperationSerialization(OperationDef $opDef, PHPClass $class, bool $normalization, array $groups): SerializationMetadata
+    {
+        sort($groups);
+        $key = sprintf('%s:%d:%s:%s', $class->getFullName(), $normalization, $opDef->getName(), implode('+', $groups));
+        if (!isset($this->serializations[$key])) {
+            $this->serializations[$key] = $this->doGetSerialization($class, $normalization, $opDef, $groups);
+        }
+
+        return $this->serializations[$key];
+    }
+
+    /**
+     * @param PHPClass     $class
+     * @param bool         $normalization
+     * @param OperationDef $opDef
+     * @param string[]     $groups
+     *
+     * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException
+     * @throws \ApiPlatform\Core\Exception\ResourceClassNotFoundException
+     * @throws \ReflectionException
+     *
+     * @return SerializationMetadata
+     */
+    private function doGetSerialization(PHPClass $class, bool $normalization, OperationDef $opDef, array $groups): SerializationMetadata
+    {
+        $selfNamePrefix = Inflector::classify($opDef->getName());
+        $otherNamePrefix = $selfNamePrefix . $class->getBaseName();
+
+        if ($normalization) {
+            $metadata = $this->resourceMetadataFactory->create($class->getFullName());
+            $defaultGroups = $metadata->getAttribute('normalization_context', [])['groups'] ?? [];
+            sort($defaultGroups);
+            if ($defaultGroups === $groups) {
+                $selfNamePrefix = '';
+            }
+            $propFilter = 'filterGetProperty';
+        } elseif ($opDef->isCreateItem()) {
+            $propFilter = 'filterCreateProperty';
+        } elseif ($opDef->isUpdateItem()) {
+            $propFilter = 'filterUpdateProperty';
+        } else {
+            $propFilter = 'filterAnyProperty';
+        }
+
+        /** @var RepresentationMetadata[] $reprs */
+        $representations = [];
+
+        /** @var string[] $queue */
+        $queue = [$class->getFullName()];
+
+        while ($queue) {
+            /** @var string $currentName */
+            $currentName = array_shift($queue);
+            $current = PHPClass::get($currentName);
+
+            if (isset($representations[$currentName]) || !$this->isResource($current)) {
+                continue;
+            }
+
+            $parent = $this->classHierarchy->getParent($current);
+            if ($parent) {
+                $queue[] = $parent->getFullName();
+            }
+
+            foreach ($this->classHierarchy->getChildren($current) as $children) {
+                $queue[] = $children->getFullName();
+            }
+
+            $propertiesMeta = $this->getPropertiesMeta($current, $groups);
+
+            $properties = [];
+            foreach ($propertiesMeta as $propertyName => $propertyMeta) {
+                if (!$this->$propFilter($class, $propertyName, $propertyMeta)) {
+                    continue;
+                }
+                $property = $this->mapProperty($current, $propertyName, $propertyMeta);
+                $properties[$propertyName] = $property;
+                $type = $property->getLeafType();
+                if ($type->getClassName()) {
+                    $queue[] = $type->getClassName();
+                }
+            }
+
+            $name = ($current->is($class) ? $selfNamePrefix : $otherNamePrefix) . $current->getBaseName();
+
+            $representations[$currentName] = new RepresentationMetadata($name, $current, $parent, $properties);
+        }
+
+        return new SerializationMetadata($class, $groups, $normalization, $representations);
+    }
+
+    /**
+     * @param PHPClass            $class
+     * @param string              $name
+     * @param APIPropertyMetadata $property
+     *
+     * @return bool
+     *
+     * @internal
+     */
+    public function filterGetProperty(PHPClass $class, string $name, APIPropertyMetadata $property): bool
+    {
+        return ($property->isReadable() || $property->isReadableLink()) && $this->propertyInfoExtractor->isReadable($class->getFullName(), $name);
+    }
+
+    /**
+     * @param PHPClass            $class
+     * @param string              $name
+     * @param APIPropertyMetadata $property
+     *
+     * @return bool
+     *
+     * @internal
+     */
+    public function filterCreateProperty(PHPClass $class, string $name, APIPropertyMetadata $property): bool
+    {
+        return $this->filterUpdateProperty($class, $name, $property) || ($property->isInitializable() ?: false);
+    }
+
+    /**
+     * @param PHPClass            $class
+     * @param string              $name
+     * @param APIPropertyMetadata $property
+     *
+     * @return bool
+     *
+     * @internal
+     */
+    public function filterUpdateProperty(PHPClass $class, string $name, APIPropertyMetadata $property): bool
+    {
+        return ($property->isWritable() || $property->isWritableLink()) && $this->propertyInfoExtractor->isWritable($class->getFullName(), $name);
+    }
+
+    /**
+     * @param PHPClass            $class
+     * @param string              $name
+     * @param APIPropertyMetadata $property
+     *
+     * @return bool
+     *
+     * @internal
+     */
+    public function filterAnyProperty(PHPClass $class, string $name, APIPropertyMetadata $property): bool
+    {
+        return true;
+    }
+
+    /**
+     * @param PHPClass $class
+     * @param array    $groups
+     *
+     * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException
+     * @throws \ApiPlatform\Core\Exception\ResourceClassNotFoundException
+     *
+     * @return APIPropertyMetadata[]
+     */
+    private function getPropertiesMeta(PHPClass $class, array $groups): array
+    {
+        $properties = [];
+        $options = $groups ? ['serializer_groups' => $groups] : [];
+
+        foreach ($this->propertyNameCollectionFactory->create($class->getFullName(), $options) as $propertyName) {
+            \assert(\is_string($propertyName));
+
+            $propertyMeta = $this->propertyMetadataFactory->create($class->getFullName(), $propertyName);
+
+            if (!$propertyMeta->getType() || $propertyMeta->isChildInherited()) {
+                continue;
+            }
+
+            $properties[$propertyName] = $propertyMeta;
+        }
+
+        return $properties;
+    }
+
+    /**
+     * @param PHPClass            $class
+     * @param string              $propertyName
+     * @param APIPropertyMetadata $propertyMeta
+     *
+     * @return PropertyMetadata
+     */
+    private function mapProperty(PHPClass $class, string $propertyName, APIPropertyMetadata $propertyMeta): PropertyMetadata
+    {
+        $leafType = $typeMeta = $propertyMeta->getType();
+
+        while ($leafType && $leafType->isCollection()) {
+            $leafType = $leafType->getCollectionValueType();
+        }
+        $leafClass = $leafType ? $leafType->getClassName() : null;
+        $link = $leafClass && $this->isResource(PHPClass::get($leafClass));
+
+        $isReadable = ($propertyMeta->isReadable() || $propertyMeta->isReadableLink()) && $this->propertyInfoExtractor->isReadable($class->getFullName(), $propertyName);
+        $isWritable = ($propertyMeta->isWritable() || $propertyMeta->isWritableLink()) && $this->propertyInfoExtractor->isWritable($class->getFullName(), $propertyName);
+
+        return new PropertyMetadata(
+            $propertyName,
+            $this->propertyInfoExtractor->getShortDescription($class->getFullName(), $propertyName) ?: '',
+            $typeMeta,
+            $propertyMeta->isIdentifier(),
+            $isReadable,
+            $isWritable,
+            $propertyMeta->isInitializable() ?: false,
+            $link
+        );
+    }
 }
diff --git a/src/Metadata/OperationDef.php b/src/Metadata/OperationDef.php
new file mode 100644
index 0000000000000000000000000000000000000000..1657976910531ec6204816657b05eda1baef10c2
--- /dev/null
+++ b/src/Metadata/OperationDef.php
@@ -0,0 +1,178 @@
+<?php declare(strict_types=1);
+/*
+ * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata.
+ * Copyright (C) 2018 IRSTEA
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License and the GNU
+ * Lesser General Public License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+namespace Irstea\NgModelGeneratorBundle\Metadata;
+
+use Doctrine\Common\Inflector\Inflector;
+
+/**
+ * Class OperationDef.
+ */
+final class OperationDef
+{
+    /** @var string */
+    private $name;
+
+    /** @var string */
+    private $originalName;
+
+    /** @var string */
+    private $method;
+
+    /** @var bool */
+    private $isCollection;
+
+    /** @var string|null */
+    private $special;
+
+    /**
+     * OperationDef constructor.
+     *
+     * @param string $name
+     * @param string $method
+     * @param bool   $isCollection
+     */
+    public function __construct(string $name, string $method, bool $isCollection)
+    {
+        $this->originalName = $name;
+        $this->method = $method;
+
+        if (\strtolower($method) === \strtolower($name)) {
+            $this->special = \strtoupper($method);
+            if ($method === 'POST') {
+                $isCollection = false;
+            }
+        }
+        $this->isCollection = $isCollection;
+
+        $name = Inflector::camelize($name);
+
+        if ($method === 'GET' && strpos($name, 'get') === false) {
+            $name = 'get' . ucfirst($name);
+        }
+
+        if ($isCollection) {
+            if (\in_array($name, ['get', 'put', 'delete', 'patch'], true)) {
+                $name .= 'All';
+            } else {
+                $name = Inflector::pluralize($name);
+            }
+        } else {
+            $name = Inflector::singularize($name);
+        }
+
+        $this->name = $name;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasNormalization(): bool
+    {
+        return \in_array($this->method, ['GET', 'PUT', 'POST']);
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasDenormalization(): bool
+    {
+        return \in_array($this->method, ['PUT', 'POST']);
+    }
+
+    /**
+     * @return bool
+     */
+    public function isGetItem(): bool
+    {
+        return $this->special === 'GET' && !$this->isCollection;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isCreateItem(): bool
+    {
+        return $this->special === 'POST' && !$this->isCollection;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isUpdateItem(): bool
+    {
+        return $this->special === 'PUT' && !$this->isCollection;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isDeleteItem(): bool
+    {
+        return $this->special === 'DELETE' && !$this->isCollection;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isGetCollection(): bool
+    {
+        return $this->method === 'GET' && $this->isCollection;
+    }
+
+    /**
+     * Get name.
+     *
+     * @return string
+     */
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    /**
+     * Get originalName.
+     *
+     * @return string
+     */
+    public function getOriginalName(): string
+    {
+        return $this->originalName;
+    }
+
+    /**
+     * Get method.
+     *
+     * @return string
+     */
+    public function getMethod(): string
+    {
+        return $this->method;
+    }
+
+    /**
+     * Get isCollection.
+     *
+     * @return bool
+     */
+    public function isCollection(): bool
+    {
+        return $this->isCollection;
+    }
+}
diff --git a/src/Metadata/OperationMetadata.php b/src/Metadata/OperationMetadata.php
index 63a0ec8dacf0565e7a45df0dc45785c133783180..4924b70bf015a9d23fd1ef3a58f884451d141ee6 100644
--- a/src/Metadata/OperationMetadata.php
+++ b/src/Metadata/OperationMetadata.php
@@ -20,25 +20,18 @@
 namespace Irstea\NgModelGeneratorBundle\Metadata;
 
 use ApiPlatform\Core\Api\FilterInterface;
-use ApiPlatform\Core\Api\OperationType;
 
 /**
  * Class OperationMetadata.
  */
 class OperationMetadata implements \JsonSerializable
 {
-    /** @var string */
-    private $name;
+    /** @var OperationDef */
+    private $opDef;
 
     /** @var string */
     private $description;
 
-    /** @var string */
-    private $type;
-
-    /** @var string */
-    private $method;
-
     /** @var string */
     private $path;
 
@@ -63,10 +56,8 @@ class OperationMetadata implements \JsonSerializable
     /**
      * OperationMetadata constructor.
      *
-     * @param string                     $name
+     * @param OperationDef               $opDef
      * @param string                     $description
-     * @param string                     $type
-     * @param string                     $method
      * @param string                     $path
      * @param array                      $requirements
      * @param array                      $filters
@@ -75,10 +66,8 @@ class OperationMetadata implements \JsonSerializable
      * @param SerializationMetadata|null $denormalization
      */
     public function __construct(
-        string $name,
+        OperationDef $opDef,
         string $description,
-        string $type,
-        string $method,
         string $path,
         array $requirements,
         array $filters,
@@ -86,9 +75,6 @@ class OperationMetadata implements \JsonSerializable
         ?SerializationMetadata $normalization,
         ?SerializationMetadata $denormalization
     ) {
-        $this->name = $name;
-        $this->type = $type;
-        $this->method = $method;
         $this->path = $path;
         $this->filters = $filters;
         $this->description = $description;
@@ -96,6 +82,7 @@ class OperationMetadata implements \JsonSerializable
         $this->requirements = $requirements;
         $this->normalization = $normalization;
         $this->denormalization = $denormalization;
+        $this->opDef = $opDef;
     }
 
     /**
@@ -121,6 +108,16 @@ class OperationMetadata implements \JsonSerializable
         return $this->resource->getClassName();
     }
 
+    /**
+     * Get opDef.
+     *
+     * @return OperationDef
+     */
+    public function getOpDef(): OperationDef
+    {
+        return $this->opDef;
+    }
+
     /**
      * Get name.
      *
@@ -128,7 +125,7 @@ class OperationMetadata implements \JsonSerializable
      */
     public function getName(): string
     {
-        return $this->name;
+        return $this->opDef->getName();
     }
 
     /**
@@ -148,7 +145,7 @@ class OperationMetadata implements \JsonSerializable
      */
     public function getType(): string
     {
-        return $this->type;
+        return $this->opDef->isCollection() ? 'collection' : 'item';
     }
 
     /**
@@ -156,7 +153,7 @@ class OperationMetadata implements \JsonSerializable
      */
     public function isItemOperation(): bool
     {
-        return $this->type === OperationType::ITEM;
+        return !$this->opDef->isCollection();
     }
 
     /**
@@ -164,7 +161,7 @@ class OperationMetadata implements \JsonSerializable
      */
     public function isCollectionOperation(): bool
     {
-        return $this->type === OperationType::COLLECTION;
+        return $this->opDef->isCollection();
     }
 
     /**
@@ -174,7 +171,7 @@ class OperationMetadata implements \JsonSerializable
      */
     public function getMethod(): string
     {
-        return $this->method;
+        return $this->opDef->getMethod();
     }
 
     /**
diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php
index 60d7c361acc6a75d287f795846f2a95bb54073ec..eb205f4d5250aba2f6e0fffe666dfb2bb304ffc1 100644
--- a/src/Metadata/PropertyMetadata.php
+++ b/src/Metadata/PropertyMetadata.php
@@ -49,9 +49,6 @@ class PropertyMetadata implements \JsonSerializable
     /** @var bool */
     private $link;
 
-    /** @var bool */
-    private $embedded;
-
     /**
      * PropertyMetadata constructor.
      *
@@ -63,7 +60,6 @@ class PropertyMetadata implements \JsonSerializable
      * @param bool   $writable
      * @param bool   $initializable
      * @param bool   $link
-     * @param bool   $embedded
      */
     public function __construct(
         string $name,
@@ -73,8 +69,7 @@ class PropertyMetadata implements \JsonSerializable
         bool $readable,
         bool $writable,
         bool $initializable,
-        bool $link,
-        bool $embedded
+        bool $link
     ) {
         $this->name = $name;
         $this->description = $description;
@@ -84,7 +79,6 @@ class PropertyMetadata implements \JsonSerializable
         $this->writable = $writable;
         $this->initializable = $initializable;
         $this->link = $link;
-        $this->embedded = $embedded;
     }
 
     /**
@@ -188,16 +182,6 @@ class PropertyMetadata implements \JsonSerializable
         return $this->link;
     }
 
-    /**
-     * Get embedded.
-     *
-     * @return bool
-     */
-    public function isEmbedded(): bool
-    {
-        return $this->embedded;
-    }
-
     /**
      * {@inheritdoc}
      */
diff --git a/src/Metadata/RepresentationMetadata.php b/src/Metadata/RepresentationMetadata.php
index 58f61bf3ef8fe4f65f8d099d1c60e51d9e7fbebd..6145b37550eba34822b212dc07a452c0b67dd62c 100644
--- a/src/Metadata/RepresentationMetadata.php
+++ b/src/Metadata/RepresentationMetadata.php
@@ -26,6 +26,11 @@ use Irstea\NgModelGeneratorBundle\Models\PHPClass;
  */
 final class RepresentationMetadata implements \JsonSerializable
 {
+    /**
+     * @var string
+     */
+    private $name;
+
     /**
      * @var PHPClass
      */
@@ -44,11 +49,12 @@ final class RepresentationMetadata implements \JsonSerializable
     /**
      * RepresentationMetadata constructor.
      *
+     * @param string             $name
      * @param PHPClass           $class
      * @param PHPClass|null      $parent
      * @param PropertyMetadata[] $properties
      */
-    public function __construct(PHPClass $class, ?PHPClass $parent, array $properties)
+    public function __construct(string $name, PHPClass $class, ?PHPClass $parent, array $properties)
     {
         $this->class = $class;
         $this->parent = $parent;
@@ -57,6 +63,18 @@ final class RepresentationMetadata implements \JsonSerializable
             $this->properties[$property->getName()] = $property;
         }
         ksort($this->properties);
+
+        $this->name = $name;
+    }
+
+    /**
+     * Get name.
+     *
+     * @return string
+     */
+    public function getName(): string
+    {
+        return $this->name;
     }
 
     /**
diff --git a/src/Metadata/DefaultClassHierarchy.php b/src/Metadata/ResourceClassHierarchy.php
similarity index 80%
rename from src/Metadata/DefaultClassHierarchy.php
rename to src/Metadata/ResourceClassHierarchy.php
index c15e883f1a2f2945b97d30991cf6524625ac0486..e4bed6fefb279486fbc41fcefff1a251d6e0dae2 100644
--- a/src/Metadata/DefaultClassHierarchy.php
+++ b/src/Metadata/ResourceClassHierarchy.php
@@ -19,12 +19,13 @@
 
 namespace Irstea\NgModelGeneratorBundle\Metadata;
 
+use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection;
 use Irstea\NgModelGeneratorBundle\Models\PHPClass;
 
 /**
- * Class DefaultClassHierarchy.
+ * Class ResourceClassHierarchy.
  */
-final class DefaultClassHierarchy implements ClassHierarchy
+final class ResourceClassHierarchy implements ClassHierarchy
 {
     /** @var PHPClass[] */
     private $parents = [];
@@ -33,9 +34,19 @@ final class DefaultClassHierarchy implements ClassHierarchy
     private $children = [];
 
     /**
-     * @param PHPClass[] $classes
+     * ResourceClassHierarchy constructor.
      */
-    public function preload(PHPClass $class): void
+    public function __construct(ResourceNameCollection $nameCollection)
+    {
+        foreach ($nameCollection->getIterator() as $className) {
+            $this->preload(PHPClass::get($className));
+        }
+    }
+
+    /**
+     * @param PHPClass $class
+     */
+    private function preload(PHPClass $class): void
     {
         $className = $class->getFullName();
         if (\array_key_exists($className, $this->parents)) {
diff --git a/src/Metadata/ResourceMetadata.php b/src/Metadata/ResourceMetadata.php
index 95eb4b67a366edd4c92a507652cda1f500a0ea7e..43e030b51b38db659cc41997e0a202f3f695ad0f 100644
--- a/src/Metadata/ResourceMetadata.php
+++ b/src/Metadata/ResourceMetadata.php
@@ -38,26 +38,18 @@ class ResourceMetadata implements \JsonSerializable
     /** @var bool */
     private $abstract;
 
-    /** @var SerializationMetadata */
-    private $defaultNormalization;
-
-    /** @var SerializationMetadata */
-    private $defaultDenormalization;
-
     /** @var OperationMetadata[] */
     private $operations = [];
 
     /**
      * ResourceMetadata constructor.
      *
-     * @param PHPClass              $class
-     * @param PHPClass|null         $parentClass
-     * @param string                $shortName
-     * @param string                $description
-     * @param bool                  $abstract
-     * @param SerializationMetadata $defaultNormalization
-     * @param SerializationMetadata $defaultDenormalization
-     * @param OperationMetadata[]   $operations
+     * @param PHPClass            $class
+     * @param PHPClass|null       $parentClass
+     * @param string              $shortName
+     * @param string              $description
+     * @param bool                $abstract
+     * @param OperationMetadata[] $operations
      */
     public function __construct(
         PHPClass $class,
@@ -65,16 +57,12 @@ class ResourceMetadata implements \JsonSerializable
         string $shortName,
         string $description,
         bool $abstract,
-        SerializationMetadata $defaultNormalization,
-        SerializationMetadata $defaultDenormalization,
         array $operations
     ) {
         $this->class = $class;
         $this->parentClass = $parentClass;
         $this->abstract = $abstract;
         $this->description = $description;
-        $this->defaultNormalization = $defaultNormalization;
-        $this->defaultDenormalization = $defaultDenormalization;
 
         foreach ($operations as $operation) {
             $this->operations[$operation->getName() . $operation->getType()] = $operation->withResource($this);
@@ -152,26 +140,6 @@ class ResourceMetadata implements \JsonSerializable
         return $this->operations;
     }
 
-    /**
-     * Get defaultNormalization.
-     *
-     * @return SerializationMetadata
-     */
-    public function getDefaultNormalization(): SerializationMetadata
-    {
-        return $this->defaultNormalization;
-    }
-
-    /**
-     * Get defaultDenormalization.
-     *
-     * @return SerializationMetadata
-     */
-    public function getDefaultDenormalization(): SerializationMetadata
-    {
-        return $this->defaultDenormalization;
-    }
-
     /**
      * {@inheritdoc}
      */
diff --git a/src/ModelGenerator.php b/src/ModelGenerator.php
index 00cce57644ecb907dae1032a940572aa103d41cc..c6a9fcaa88244e19d5c7c7dc2f60e14b98e4a4b6 100644
--- a/src/ModelGenerator.php
+++ b/src/ModelGenerator.php
@@ -24,11 +24,8 @@ use Irstea\NgModelGeneratorBundle\Exceptions\DomainException;
 use Irstea\NgModelGeneratorBundle\Exceptions\InvalidArgumentException;
 use Irstea\NgModelGeneratorBundle\Iterators\RecursorIterator;
 use Irstea\NgModelGeneratorBundle\Iterators\UniqueFilter;
-use Irstea\NgModelGeneratorBundle\Metadata\ClassHierarchy;
-use Irstea\NgModelGeneratorBundle\Metadata\DefaultClassHierarchy;
 use Irstea\NgModelGeneratorBundle\Metadata\MetadataFactoryInterface;
 use Irstea\NgModelGeneratorBundle\Metadata\ResourceMetadata;
-use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata;
 use Irstea\NgModelGeneratorBundle\Models\Declaration;
 use Irstea\NgModelGeneratorBundle\Models\PHPClass;
 use Irstea\NgModelGeneratorBundle\Models\Types\Alias;
@@ -36,8 +33,7 @@ use Irstea\NgModelGeneratorBundle\Models\Types\BuiltinType;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\InterfaceType;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Repository;
-use Irstea\NgModelGeneratorBundle\Models\Types\Placeholder;
-use Irstea\NgModelGeneratorBundle\Models\Types\Resources\ConcreteRepresentation;
+use Irstea\NgModelGeneratorBundle\Models\Types\Resources\UUID;
 use Irstea\NgModelGeneratorBundle\Models\Types\Type;
 use Symfony\Component\PropertyInfo\Type as PHPType;
 use Twig\Environment;
@@ -59,15 +55,6 @@ final class ModelGenerator
     /** @var TypeFactory */
     private $typeFactory;
 
-    /** @var SerializationMetadata[][] */
-    private $defaultSerializations;
-
-    /** @var ConcreteRepresentation[][] */
-    private $resources;
-
-    /** @var ClassHierarchy */
-    private $classHierarchy;
-
     /**
      * Serializer constructor.
      *
@@ -102,9 +89,7 @@ final class ModelGenerator
         } finally {
             unset(
                 $this->documentation,
-                $this->typeFactory,
-                $this->defaultGroups,
-                $this->resources
+                $this->typeFactory
             );
         }
     }
@@ -116,12 +101,8 @@ final class ModelGenerator
      */
     private function doGenerate(): string
     {
-        $this->classHierarchy = $this->createClassHierachy();
-
         $this->typeFactory = $this->createTypeFactory();
 
-        $this->defaultSerializations = $this->extractDefaultSerializations();
-
         [$repositories, $iriPatterns] = $this->extractRepositories();
 
         $declarations = $this->extractDeclarations($repositories);
@@ -139,20 +120,6 @@ final class ModelGenerator
         );
     }
 
-    /**
-     * @return ClassHierarchy
-     */
-    private function createClassHierachy(): ClassHierarchy
-    {
-        $hierarchy = new DefaultClassHierarchy();
-
-        foreach ($this->getResourceMetadata() as $class => $meta) {
-            $hierarchy->preload($class);
-        }
-
-        return $hierarchy;
-    }
-
     /**
      * Crée une usine à types contenant un certain nombre de types par défaut.
      *
@@ -173,9 +140,7 @@ final class ModelGenerator
             }
         );
 
-        $factory->getOrCreate('UUID', [Placeholder::class, 'get'], 'UUID');
-        $factory->getOrCreate('IRI', [Placeholder::class, 'get'], 'IRI');
-        $factory->getOrCreate('Collection', [Placeholder::class, 'get'], 'Collection');
+        $factory->getOrCreate('UUID', [UUID::class, 'get']);
 
         $factory->getOrCreate(
             'CommonFilters',
@@ -221,34 +186,6 @@ final class ModelGenerator
         return new InterfaceType($name, null, $properties);
     }
 
-    /**
-     * Extrait les groupes de sérialization par défaut des ressources.
-     *
-     * @return SerializationMetadata[][]
-     */
-    private function extractDefaultSerializations(): array
-    {
-        $serializations = [
-            'normalization'   => [],
-            'denormalization' => [],
-        ];
-
-        /**
-         * @var PHPClass
-         * @var ResourceMetadata $resourceMeta
-         */
-        foreach ($this->getResourceMetadata() as $class => $resourceMeta) {
-            $className = $class->getFullName();
-
-            $serializations['normalization'][$className] =
-                $resourceMeta->getDefaultNormalization();
-            $serializations['denormalization'][$className] =
-                $resourceMeta->getDefaultDenormalization();
-        }
-
-        return $serializations;
-    }
-
     /**
      * Retourne un iterateur sur les métadonnées des ressources.
      *
@@ -278,6 +215,9 @@ final class ModelGenerator
          * @var ResourceMetadata $resourceMeta
          */
         foreach ($this->getResourceMetadata() as $class => $resourceMeta) {
+//            printf("\n\n===================================\n\n%s\n", \json_encode($resourceMeta, \JSON_PRETTY_PRINT));
+//            continue;
+
             $repoName = $resourceMeta->getShortName() . 'Repository';
 
             $repositories[$repoName] = $this->typeFactory->getOrCreate(
diff --git a/src/Models/PHPClass.php b/src/Models/PHPClass.php
index a20e719ed27354fbbca2905d31e6668f7dd8c3f6..40a3d96b2fad3d995e4f7107816402fc7af889bb 100644
--- a/src/Models/PHPClass.php
+++ b/src/Models/PHPClass.php
@@ -90,6 +90,16 @@ final class PHPClass implements \JsonSerializable
         return new \ReflectionClass($this->getFullName());
     }
 
+    /**
+     * @param string|PHPClass $class
+     *
+     * @return bool
+     */
+    public function is($class): bool
+    {
+        return $this === self::get($class);
+    }
+
     /**
      * @return string
      */
diff --git a/src/Models/Types/Objects/AbstractHierarchicalObject.php b/src/Models/Types/Objects/AbstractHierarchicalObject.php
index f64688a0f73d00a04f4326025c52f1bbb9a01554..89d7d4304cbb50edb131482e28c5344bb75b12da 100644
--- a/src/Models/Types/Objects/AbstractHierarchicalObject.php
+++ b/src/Models/Types/Objects/AbstractHierarchicalObject.php
@@ -34,8 +34,10 @@ abstract class AbstractHierarchicalObject extends AnonymousObject implements Dec
     /** @var Type|null */
     protected $parent;
 
-    /** @var Type[] */
-    protected $children;
+    /**
+     * @var array
+     */
+    private $children;
 
     /**
      * ObjectClass constructor.
@@ -44,7 +46,6 @@ abstract class AbstractHierarchicalObject extends AnonymousObject implements Dec
      * @param Type|null  $parent
      * @param Property[] $properties
      * @param string     $description
-     * @param Type[]     $children
      */
     public function __construct(string $name, ?Type $parent, array $properties = [], string $description = '', array $children = [])
     {
@@ -121,6 +122,7 @@ abstract class AbstractHierarchicalObject extends AnonymousObject implements Dec
         if ($this->parent) {
             yield $this->parent;
         }
+        yield from $this->children;
         yield from parent::getIterator();
     }
 
diff --git a/src/Models/Types/Resources/AbstractRepresentation.php b/src/Models/Types/Resources/AbstractRepresentation.php
deleted file mode 100644
index 44d30a194d9667136b428abb29dd98c9a580caba..0000000000000000000000000000000000000000
--- a/src/Models/Types/Resources/AbstractRepresentation.php
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata.
- * Copyright (C) 2018 IRSTEA
- *
- * This program is free software: you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation, either version 3 of the License, or (at your option) any
- * later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License and the GNU
- * Lesser General Public License along with this program. If not, see
- * <https://www.gnu.org/licenses/>.
- */
-
-namespace Irstea\NgModelGeneratorBundle\Models\Types\Resources;
-
-use Irstea\NgModelGeneratorBundle\Metadata\ResourceMetadata;
-use Irstea\NgModelGeneratorBundle\Models\DeclarationTrait;
-use Irstea\NgModelGeneratorBundle\Models\Types\AbstractType;
-use Irstea\NgModelGeneratorBundle\Models\Types\Type;
-
-/**
- * Class AbstractRepresentation.
- */
-final class AbstractRepresentation extends AbstractType implements Representation
-{
-    use DeclarationTrait;
-
-    /** @var Type|null */
-    private $parent;
-
-    /** @var Type[] */
-    private $children;
-
-    /**
-     * AbstractRepresentation constructor.
-     *
-     * @param ResourceMetadata $resource
-     * @param string           $name
-     * @param string           $description
-     * @param Type|null        $parent
-     * @param Type[]           $children
-     */
-    public function __construct(string $name, string $description, Type $parent = null, array $children = [])
-    {
-        $this->name = $name;
-        $this->parent = $parent;
-        $this->children = $children;
-        $this->description = $description;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getIterator()
-    {
-        if ($this->parent) {
-            yield $this->parent;
-        }
-        yield from $this->children;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getUsage(): string
-    {
-        switch (\count($this->children)) {
-            case 0:
-                return 'never';
-            case 1:
-                return $this->children[0]->getUsage();
-            default:
-                return $this->getName();
-        }
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getDeclaration(): string
-    {
-        if (\count($this->children) < 2) {
-            return '';
-        }
-
-        return \sprintf(
-            'export type %s = %s;',
-            $this->name,
-            \implode(
-                ' | ',
-                \array_map(
-                    function (Type $o) {
-                        return $o->getUsage();
-                    },
-                    $this->children
-                )
-            )
-        );
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function castToStringOrStringArray(string $expr): string
-    {
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function checkType(string $expr): string
-    {
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function jsonSerialize()
-    {
-        return [
-            'parent'   => $this->parent,
-            'children' => $this->children,
-        ];
-    }
-}
diff --git a/src/Models/Types/Resources/AtType.php b/src/Models/Types/Resources/AtType.php
new file mode 100644
index 0000000000000000000000000000000000000000..be97c7eca556885f979002ff8f9b7daca917724f
--- /dev/null
+++ b/src/Models/Types/Resources/AtType.php
@@ -0,0 +1,90 @@
+<?php declare(strict_types=1);
+/*
+ * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata.
+ * Copyright (C) 2018 IRSTEA
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License and the GNU
+ * Lesser General Public License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+namespace Irstea\NgModelGeneratorBundle\Models\Types\Resources;
+
+use Irstea\NgModelGeneratorBundle\Models\Types\AbstractType;
+use Irstea\NgModelGeneratorBundle\Models\Types\Type;
+use Irstea\NgModelGeneratorBundle\TypescriptHelper;
+
+/**
+ * Class AtType.
+ */
+final class AtType extends AbstractType
+{
+    /** @var Type */
+    private $types;
+
+    /**
+     * IRI constructor.
+     *
+     * @param Type[] $types
+     */
+    public function __construct(array $types)
+    {
+        $this->types = $types;
+    }
+
+    /**
+     * @return array
+     */
+    private function getNames(): array
+    {
+        return array_map(
+            function (Type $t): string {
+                return $t->getUsage();
+            },
+            $this->types
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUsage(): string
+    {
+        $names = $this->getNames();
+
+        return implode(' | ', array_map([TypescriptHelper::class, 'quoteString'], $names));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function castToStringOrStringArray(string $expr): string
+    {
+        return $expr;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function checkType(string $expr): string
+    {
+        return sprintf('(%s in [%s])', $expr, $this->getUsage());
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getIterator()
+    {
+        yield from $this->types;
+    }
+}
diff --git a/src/Models/Types/Resources/ConcreteRepresentation.php b/src/Models/Types/Resources/ConcreteRepresentation.php
deleted file mode 100644
index 952d3c5ab13cdb2eda58823130ccac3a5b4daeee..0000000000000000000000000000000000000000
--- a/src/Models/Types/Resources/ConcreteRepresentation.php
+++ /dev/null
@@ -1,101 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata.
- * Copyright (C) 2018 IRSTEA
- *
- * This program is free software: you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation, either version 3 of the License, or (at your option) any
- * later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License and the GNU
- * Lesser General Public License along with this program. If not, see
- * <https://www.gnu.org/licenses/>.
- */
-
-namespace Irstea\NgModelGeneratorBundle\Models\Types\Resources;
-
-use Irstea\NgModelGeneratorBundle\Models\PHPClass;
-use Irstea\NgModelGeneratorBundle\Models\Types\Objects\InterfaceType;
-use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
-use Irstea\NgModelGeneratorBundle\Models\Types\Type;
-use Irstea\NgModelGeneratorBundle\TypescriptHelper;
-
-/**
- * Class ConcreteRepresentation.
- */
-final class ConcreteRepresentation extends InterfaceType implements Representation
-{
-    /** @var bool */
-    private $normalization;
-
-    /**
-     * @var array|PHPClass[]
-     */
-    private $resources;
-
-    /**
-     * Representation constructor.
-     *
-     * @param string     $name
-     * @param Type|null  $parent
-     * @param Property[] $properties
-     * @param string     $description
-     * @param PHPClass[] $resources
-     * @param bool       $normalization
-     */
-    public function __construct(string $name, ?Type $parent, array $properties, string $description, array $resources, bool $normalization)
-    {
-        parent::__construct($name, $parent, $properties, $description, $resources);
-        $this->normalization = $normalization;
-        $this->resources = $resources;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    protected function getPropertyDeclarations(): array
-    {
-        $decl = parent::getPropertyDeclarations();
-
-        $names = $this->getAllResourceNames();
-        $optional = $this->normalization ? '' : '?';
-        array_unshift(
-            $decl,
-            sprintf("readonly '@id'%s: IRI<%s>;", $optional, implode(' | ', $names)),
-            sprintf("readonly '@type'%s: %s;", $optional, implode(' | ', array_map([TypescriptHelper::class, 'quoteString'], $names)))
-        );
-
-        return $decl;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function checkType(string $expr): string
-    {
-        $names = $this->getAllResourceNames();
-        if (\count($names) === 1) {
-            $test = sprintf("%s['@type'] === %s", $expr, TypescriptHelper::quoteString($names[0]));
-        } else {
-            $test = sprintf('in [%s]', implode(', ', array_map([TypescriptHelper::class, 'quoteString'], $names)));
-        }
-
-        return sprintf("(typeof %s === 'object' && '@type' in %s && %s)", $expr, $expr, $test);
-    }
-
-    /**
-     * @return string[]
-     */
-    private function getAllResourceNames(): array
-    {
-        return array_map(
-            function (PHPClass $c) { return $c->getBaseName(); },
-            $this->resources
-        );
-    }
-}
diff --git a/src/Models/Types/Resources/IRI.php b/src/Models/Types/Resources/IRI.php
index e251a5f937b6104dbf034414a926ec01aa2a2c62..9725e87bbd7f673342920b8834e663457f682f9e 100644
--- a/src/Models/Types/Resources/IRI.php
+++ b/src/Models/Types/Resources/IRI.php
@@ -19,29 +19,25 @@
 
 namespace Irstea\NgModelGeneratorBundle\Models\Types\Resources;
 
-use Irstea\NgModelGeneratorBundle\Models\MultitonTrait;
-use Irstea\NgModelGeneratorBundle\Models\PHPClass;
 use Irstea\NgModelGeneratorBundle\Models\Types\AbstractType;
-use Irstea\NgModelGeneratorBundle\TypescriptHelper;
+use Irstea\NgModelGeneratorBundle\Models\Types\Type;
 
 /**
  * Class IRI.
  */
 final class IRI extends AbstractType
 {
-    use MultitonTrait;
-
-    /** @var PHPClass */
-    private $resource;
+    /** @var Type */
+    private $type;
 
     /**
      * IRI constructor.
      *
-     * @param PHPClass $resource
+     * @param Type $type
      */
-    public function __construct(string $className)
+    private function __construct(Type $type)
     {
-        $this->resource = PHPClass::get($className);
+        $this->type = $type;
     }
 
     /**
@@ -49,7 +45,7 @@ final class IRI extends AbstractType
      */
     public function getUsage(): string
     {
-        return sprintf('IRI<%s>', $this->resource->getBaseName());
+        return sprintf('IRI<%s>', $this->type->getUsage());
     }
 
     /**
@@ -65,11 +61,42 @@ final class IRI extends AbstractType
      */
     public function checkType(string $expr): string
     {
-        return sprintf(
-            'isIRI<%s>(%s, %s)',
-            $this->resource->getBaseName(),
-            $expr,
-            TypescriptHelper::quoteString($this->resource->getBaseName())
-        );
+//        $name = $this->type->getUsage();
+//
+//        return sprintf(
+//            'isIRI<%s>(%s, %s)',
+//             $name,
+//             $expr,
+//             $name
+//        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getIterator()
+    {
+        yield $this->type;
+    }
+
+    /**
+     * @param Type $type
+     *
+     * @return IRI
+     */
+    public static function get(Type $type): self
+    {
+        static $instances = [];
+
+        if ($type instanceof self) {
+            return $type;
+        }
+
+        $key = $type->getUsage();
+        if (!isset($instances[$key])) {
+            $instances[$key] = new self($type);
+        }
+
+        return $instances[$key];
     }
 }
diff --git a/src/Models/Types/Resources/Representation.php b/src/Models/Types/Resources/UUID.php
similarity index 52%
rename from src/Models/Types/Resources/Representation.php
rename to src/Models/Types/Resources/UUID.php
index 375af4d58b293670ce5ed25d7d2d57370f93cae0..29559eaa3ca64fa4e05baad4784b8359f6f0656f 100644
--- a/src/Models/Types/Resources/Representation.php
+++ b/src/Models/Types/Resources/UUID.php
@@ -19,12 +19,54 @@
 
 namespace Irstea\NgModelGeneratorBundle\Models\Types\Resources;
 
-use Irstea\NgModelGeneratorBundle\Models\Declaration;
-use Irstea\NgModelGeneratorBundle\Models\Types\Type;
+use Irstea\NgModelGeneratorBundle\Models\Types\AbstractType;
 
 /**
- * Interface Representation.
+ * Class UUID.
  */
-interface Representation extends Type, Declaration
+final class UUID extends AbstractType
 {
+    /**
+     * UUID constructor.
+     */
+    private function __construct()
+    {
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUsage(): string
+    {
+        return 'UUID';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function castToStringOrStringArray(string $expr): string
+    {
+        return $expr;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function checkType(string $expr): string
+    {
+        return parent::checkType($expr); // TODO: Change the autogenerated stub
+    }
+
+    /**
+     * @return UUID
+     */
+    public static function get(): self
+    {
+        static $instance;
+        if (!$instance) {
+            $instance = new self();
+        }
+
+        return $instance;
+    }
 }
diff --git a/src/Models/Types/Union.php b/src/Models/Types/Union.php
index f27b8ee1c8472824f7eed3522073ae1a03d9f8b7..8a8198fe58039784a2182b2eacb2476b9bee3c51 100644
--- a/src/Models/Types/Union.php
+++ b/src/Models/Types/Union.php
@@ -110,7 +110,7 @@ final class Union extends AbstractType
             case 0:
                 return BuiltinType::get('never');
             case 1:
-                return $types[0];
+                return array_shift($types);
             default:
                 return new Union($types);
         }
diff --git a/src/OperationMapper.php b/src/OperationMapper.php
index 90639941f4c44d51c4d54538014c03c6d1e56fde..f1ab13958ae24f85703f2a6e33d15bf4383e63c0 100644
--- a/src/OperationMapper.php
+++ b/src/OperationMapper.php
@@ -20,6 +20,7 @@
 namespace Irstea\NgModelGeneratorBundle;
 
 use Doctrine\Common\Inflector\Inflector;
+use Irstea\NgModelGeneratorBundle\Metadata\ClassHierarchy;
 use Irstea\NgModelGeneratorBundle\Metadata\OperationMetadata;
 use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata;
 use Irstea\NgModelGeneratorBundle\Models\Types\ArrayType;
@@ -43,19 +44,6 @@ use Irstea\NgModelGeneratorBundle\Models\Types\Union;
  */
 final class OperationMapper
 {
-    /** @var string[][] */
-    private const CACHE_DECORATORS = [
-        'item'       => [
-            'GET'    => ['get(iri, () => %s)', 'iri'],
-            'PUT'    => ['put(iri, %s)', 'iri'],
-            'DELETE' => ['delete(iri, %s)', 'iri'],
-            'POST'   => ['post(%s)', null],
-        ],
-        'collection' => [
-            'GET' => ['getAll(%s)', null],
-        ],
-    ];
-
     /** @var TypeFactoryInterface */
     private $typeFactory;
 
@@ -66,6 +54,7 @@ final class OperationMapper
      * OperationMapper constructor.
      *
      * @param TypeFactoryInterface $typeFactory
+     * @param ClassHierarchy       $classHierarchy
      * @param OperationMetadata    $operation
      */
     public function __construct(TypeFactoryInterface $typeFactory, OperationMetadata $operation)
@@ -79,34 +68,28 @@ final class OperationMapper
      */
     public function __invoke(): Operation
     {
-        $httpMethod = $this->operation->getMethod();
-        $normalization = $this->operation->getNormalization();
-        $denormalization = $this->operation->getDenormalization();
-
-        $isCollection = $this->operation->isCollectionOperation();
-
-        if ($this->operation->getName() === 'post' && $httpMethod === 'POST' && $normalization && $denormalization) {
-            $isCollection = false;
-        }
-
         $requestBody = $responseBody = null;
 
+        $normalization = $this->operation->getNormalization();
         if ($normalization) {
             $responseBody = $this->mapSerialization($normalization);
 
-            if ($isCollection) {
+            if ($this->operation->isCollectionOperation()) {
                 $responseBody = new Collection($responseBody);
             }
         }
 
+        $denormalization = $this->operation->getDenormalization();
         if ($denormalization) {
             $responseBody = $this->mapSerialization($denormalization);
 
-            if ($isCollection) {
+            if ($this->operation->isCollectionOperation()) {
                 $requestBody = new ArrayType($requestBody);
             }
         }
 
+        $httpMethod = $this->operation->getMethod();
+
         $clientCall = $this->applyCache(
             new DirectClientCall(
                 $httpMethod,
@@ -114,16 +97,15 @@ final class OperationMapper
                 $this->getFilters(),
                 $responseBody,
                 $requestBody
-            ),
-            $isCollection
+            )
         );
 
         return new Operation(
-            $this->getFancyName($isCollection),
+            $this->operation->getName(),
             $clientCall,
             sprintf(
                 "Operation: %s\nType: %s\nMethod: %s\nPath: %s",
-                $this->operation->getName(),
+                $this->operation->getOpDef()->getOriginalName(),
                 $this->operation->getType(),
                 $httpMethod,
                 $this->operation->getPath()
@@ -138,7 +120,11 @@ final class OperationMapper
      */
     private function mapSerialization(SerializationMetadata $serializationMetadata): Type
     {
-        $mapper = new SerializationMapper($this->typeFactory, $serializationMetadata);
+        $mapper = new SerializationMapper(
+            $this->typeFactory,
+            $serializationMetadata,
+            !$this->operation->getOpDef()->isCreateItem()
+        );
 
         return $mapper->get($serializationMetadata->getRoot()->getFullName());
     }
@@ -198,57 +184,32 @@ final class OperationMapper
         );
     }
 
-    /**
-     * @param bool $isCollection
-     *
-     * @return string
-     */
-    private function getFancyName(bool $isCollection): string
-    {
-        $name = Inflector::camelize($this->operation->getName());
-
-        if ($this->operation->getMethod() === 'GET' && strpos($name, 'get') === false) {
-            $name = 'get' . ucfirst($name);
-        }
-
-        if ($isCollection) {
-            if (\in_array($name, ['get', 'put', 'delete', 'patch'], true)) {
-                return $name . 'All';
-            }
-
-            return Inflector::pluralize($name);
-        }
-
-        return Inflector::singularize($name);
-    }
-
     /**
      * @param ClientCall $clientCall
      *
      * @return ClientCall
      */
-    private function applyCache(ClientCall $clientCall, bool $isCollection): ClientCall
+    private function applyCache(ClientCall $clientCall): ClientCall
     {
-        $decorators = self::CACHE_DECORATORS[$isCollection ? 'collection' : 'item'];
-        $method = $this->operation->getMethod();
+        $opDef = $this->operation->getOpDef();
 
-        if (!isset($decorators[$method])) {
-            return $clientCall;
+        if ($opDef->isGetCollection()) {
+            return new CachedClientCall($clientCall, 'this.cache.getAll(%s)');
         }
-
-        $normalization = $this->operation->getNormalization();
-        if (!$normalization) {
-            if ($method !== 'DELETE') {
-                return $clientCall;
-            }
+        if ($opDef->isCreateItem()) {
+            return new CachedClientCall($clientCall, 'this.cache.post(%s)');
         }
-
-        [$template, $requiredParam] = $decorators[$method];
-        if ($requiredParam !== null && !$clientCall->hasParameter($requiredParam)) {
-            return $clientCall;
+        if ($opDef->isGetItem()) {
+            return new CachedClientCall($clientCall, 'this.cache.get(iri, () => %s)');
+        }
+        if ($opDef->isUpdateItem()) {
+            return new CachedClientCall($clientCall, 'this.cache.put(iri, %s)');
+        }
+        if ($opDef->isDeleteItem()) {
+            return new CachedClientCall($clientCall, 'this.cache.delete(iri, %s)');
         }
 
-        return new CachedClientCall($clientCall, 'this.cache.' . $template);
+        return $clientCall;
     }
 
     /**
diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml
index f693d0aae3c671f0ca74e8380c5e8e7dad80b96a..0552d76b28160f4a669bc09ef3d7cf3a7fe585ae 100644
--- a/src/Resources/config/config.xml
+++ b/src/Resources/config/config.xml
@@ -17,6 +17,7 @@
             <argument type="service" id="api_platform.operation_path_resolver"/>
             <argument type="service" id="api_platform.filter_locator"/>
             <argument type="service" id="ng_model_generator.metadata.pagination"/>
+            <argument type="service" id="irstea_ng_model_generator.metadata.resource_class_hierarchy"/>
         </service>
 
         <service id="ng_model_generator.metadata.caching_factory" lazy="true" public="false"
@@ -26,6 +27,11 @@
             <argument type="service" id="doctrine_cache.providers.ng_model_generator_metadata_cache" on-invalid="null"/>
         </service>
 
+        <service id="irstea_ng_model_generator.metadata.resource_class_hierarchy"
+                class="Irstea\NgModelGeneratorBundle\Metadata\ResourceClassHierarchy" >
+            <argument id="irstea_ng_model_generator.resource_name_collection" type="service"/>
+        </service>
+
         <service id="ng_model_generator.metadata.pagination" lazy="true"
                  class="Irstea\NgModelGeneratorBundle\Metadata\PaginationMetadata">
             <argument>%api_platform.collection.pagination.enabled%</argument>
diff --git a/src/SerializationMapper.php b/src/SerializationMapper.php
index 8aaaf48d9242c150f7ca5e0fd2c640c77146a0e4..0e8c5a9536c247c458899fdf4bd6d51a591f5a2d 100644
--- a/src/SerializationMapper.php
+++ b/src/SerializationMapper.php
@@ -21,15 +21,21 @@ namespace Irstea\NgModelGeneratorBundle;
 
 use Irstea\NgModelGeneratorBundle\Exceptions\DomainException;
 use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata;
+use Irstea\NgModelGeneratorBundle\Metadata\RepresentationMetadata;
 use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata;
 use Irstea\NgModelGeneratorBundle\Models\PHPClass;
+use Irstea\NgModelGeneratorBundle\Models\Types\Alias;
 use Irstea\NgModelGeneratorBundle\Models\Types\ArrayType;
 use Irstea\NgModelGeneratorBundle\Models\Types\BuiltinType;
+use Irstea\NgModelGeneratorBundle\Models\Types\Objects\AnonymousObject;
+use Irstea\NgModelGeneratorBundle\Models\Types\Objects\InterfaceType;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
-use Irstea\NgModelGeneratorBundle\Models\Types\Resources\ConcreteRepresentation;
+use Irstea\NgModelGeneratorBundle\Models\Types\Placeholder;
+use Irstea\NgModelGeneratorBundle\Models\Types\Resources\AtType;
 use Irstea\NgModelGeneratorBundle\Models\Types\Resources\IRI;
 use Irstea\NgModelGeneratorBundle\Models\Types\Type;
 use Irstea\NgModelGeneratorBundle\Models\Types\Union;
+use Symfony\Component\PropertyInfo\Type as APIType;
 
 /**
  * Class SerializationMapper.
@@ -42,24 +48,27 @@ final class SerializationMapper implements TypeFactoryInterface
     /** @var SerializationMetadata */
     private $serialization;
 
-    /** @var int[] */
-    private $refCounts;
+    /** @var array */
+    private $classInfo;
 
-    /** @var PHPClass[] */
-    private $children;
+    /** @var bool */
+    private $withAtFields;
 
     /**
      * SerializationMapper constructor.
      *
      * @param TypeFactoryInterface  $typeFactory
      * @param SerializationMetadata $serialization
+     * @param bool                  $withAtFields
      */
     public function __construct(
         TypeFactoryInterface $typeFactory,
-        SerializationMetadata $serialization
+        SerializationMetadata $serialization,
+        bool $withAtFields
     ) {
         $this->typeFactory = $typeFactory;
         $this->serialization = $serialization;
+        $this->withAtFields = $withAtFields;
     }
 
     /**
@@ -87,13 +96,20 @@ final class SerializationMapper implements TypeFactoryInterface
             return $this->typeFactory->get($name);
         }
 
-        $class = PHPClass::get($name);
-        if ($this->serialization->hasRepresentationOf($class)) {
-            $reprName = $this->getRepresentationName($class);
+        if ($this->classInfo === null) {
+            $this->init();
+        }
+
+        if (isset($this->classInfo[$name])) {
+            $class = PHPClass::get($name);
+            $repr = $this->serialization->getRepresentationOf($class);
 
-            return $this->typeFactory->getOrCreate($reprName, function () use ($class, $reprName) {
-                return $this->mapRepresentation($class, $reprName);
-            });
+            return $this->typeFactory->getOrCreate(
+                $repr->getName(),
+                function () use ($repr) {
+                    return $this->mapRepresentation($repr);
+                }
+            );
         }
 
         return $this->getOrCreate($name, [$this, 'get'], 'any');
@@ -116,74 +132,89 @@ final class SerializationMapper implements TypeFactoryInterface
     }
 
     /**
-     * @param PHPClass $class
-     *
-     * @return string
-     */
-    private function getRepresentationName(PHPClass $class): string
-    {
-        $prefix = ($this->serialization->isNormalization() ? 'Get' : 'Put') . $this->serialization->getRoot()->getBaseName();
-
-        return (implode('', $this->serialization->getGroups()) ?: $prefix) . $class->getBaseName();
-    }
-
-    /**
-     * @param string   $name
-     * @param PHPClass $resourceClass
+     * @param RepresentationMetadata $repr
      *
      * @return Type
      */
-    private function mapRepresentation(PHPClass $resourceClass, string $reprName): Type
+    private function mapRepresentation(RepresentationMetadata $repr): Type
     {
+        $resourceClass = $repr->getClass();
+
         /**
-         * @var PHPClass
+         * @var bool
+         * @var PHPClass           $parentClass
          * @var PropertyMetadata[] $propertiesMeta
+         * @var PHPClass[]         $descendantClasses
          */
-        [, $parentClass, $propertiesMeta] = $this->doMapRepresentation($resourceClass);
-
-        $properties = $this->mapProperties($propertiesMeta);
-        $parent = $parentClass ? $this->get($parentClass->getFullName()) : null;
+        [$keep, $parentClass, $propertiesMeta, $descendantClasses] = $this->classInfo[$resourceClass->getFullName()];
 
-//        $repr = $this->serialization->getRepresentationOf($resourceClass);
-//
-//        $parent = $repr->getParent() ? $this->get($repr->getParent()->getFullName()) : null;
-//        $properties = $this->mapProperties($repr->getProperties());
+        if (!$keep) {
+            if ($propertiesMeta) {
+                return new AnonymousObject($this->mapProperties($propertiesMeta));
+            }
+            if ($descendantClasses) {
+                $descendants = array_map(
+                    function (PHPClass $c) {
+                        return $this->get($c->getFullName());
+                    },
+                    $descendantClasses
+                );
+
+                return new Alias($repr->getName(), Union::create($descendants));
+            }
 
-        $desc = [];
-        $desc[] = 'Resource: ' . $resourceClass->getFullName();
-//        $desc[] = 'Direction: ' . ($normalization ? 'response' : 'request');
-//        $desc[] = sprintf('Serialization groups: %s', $groups ? implode(', ', $groups) : '-');
-        $desc = trim(implode("\n", $desc));
+            return IRI::get(Placeholder::get($resourceClass->getBaseName()));
+        }
 
-        return new ConcreteRepresentation($reprName, $parent, $properties, $desc, $this->getCoveredResources($resourceClass), $this->serialization->isNormalization());
-    }
+        $parent = $parentClass ? $this->get($parentClass->getFullName()) : null;
 
-    /**
-     * @param PHPClass $class
-     *
-     * @return array
-     */
-    private function doMapRepresentation(PHPClass $class): array
-    {
-        $repr = $this->serialization->getRepresentationOf($class);
-        $properties = $repr->getProperties();
+        $properties = $this->mapProperties($propertiesMeta);
 
-        $parentClass = $repr->getParent();
-        if ($parentClass) {
-            [$keep, , $parentProperties] = $this->doMapRepresentation($parentClass);
-            if (!$keep) {
-                $parentClass = null;
-                $properties = array_replace($parentProperties, $properties);
+        $children = [];
+        /** @var PHPClass $descendant */
+        foreach ($descendantClasses as $descendant) {
+            if (!$descendant->is($resourceClass)) {
+                $children[] = $this->get($descendant->getFullName());
             }
         }
 
-        if (!$this->serialization->isRoot($class)) {
-            if (!$parentClass && $this->getRefCount($class) < 2) {
-                return [false, null, $properties];
+        if ($children && !$parent && !$properties) {
+            return new Alias($repr->getName(), Union::create($children));
+        }
+
+        if ($this->withAtFields) {
+            $names = [];
+            foreach ($descendantClasses as $descendant) {
+                $resName = $descendant->getBaseName();
+                $names[$resName] = Placeholder::get($resName);
             }
+
+            $properties['@id'] = new Property(
+                '@id',
+                '',
+                IRI::get(Union::create($names)),
+                true,
+                !$this->serialization->isNormalization(),
+                true
+            );
+
+            $properties['@type'] = new Property(
+                '@type',
+                '',
+                new AtType($names),
+                true,
+                !$this->serialization->isNormalization(),
+                true
+            );
         }
 
-        return [true, $parentClass, $properties];
+        $desc = [];
+        $desc[] = 'Resource: ' . $resourceClass->getFullName();
+        $desc[] = 'Direction: ' . ($this->serialization->isNormalization() ? 'response' : 'request');
+        $desc[] = sprintf('Serialization groups: %s', implode(', ', $this->serialization->getGroups()) ?: '-');
+        $desc = trim(implode("\n", $desc));
+
+        return new InterfaceType($repr->getName(), $parent, $properties, $desc, $children);
     }
 
     /**
@@ -197,10 +228,6 @@ final class SerializationMapper implements TypeFactoryInterface
         $identifierCount = 0;
 
         foreach ($propertiesMeta as $propertyMeta) {
-            if (!$this->acceptProperty($propertyMeta)) {
-                continue;
-            }
-
             if ($propertyMeta->isIdentifier()) {
                 ++$identifierCount;
             }
@@ -216,23 +243,6 @@ final class SerializationMapper implements TypeFactoryInterface
         return $properties;
     }
 
-    /**
-     * @param PropertyMetadata $propertyMeta
-     *
-     * @return bool
-     */
-    public function acceptProperty(PropertyMetadata $propertyMeta): bool
-    {
-        if (!$propertyMeta->getType()) {
-            return false;
-        }
-        if ($this->serialization->isNormalization()) {
-            return $propertyMeta->isReadable();
-        }
-
-        return $propertyMeta->isWritable() || $propertyMeta->isInitializable();
-    }
-
     /**
      * @param PropertyMetadata $propertyMeta
      *
@@ -251,25 +261,19 @@ final class SerializationMapper implements TypeFactoryInterface
         }
 
         if ($propertyMeta->isLink()) {
-            $linkedClassName = $leafType->getClassName();
-            $type = IRI::get($linkedClassName);
-
-            if ($propertyMeta->isEmbedded()) {
-                $linkedType = $this->get($linkedClassName);
-                if ($this->serialization->isNormalization()) {
-                    $type = $linkedType;
-                } else {
-                    $type = Union::create([$type, $linkedType]);
-                }
-            }
+            $type = $this->get($leafType->getClassName());
         } else {
             $type = $this->get($leafType->getClassName() ?: $leafType->getBuiltinType());
         }
 
         $number = BuiltinType::get('number');
-        foreach (\array_reverse($collections) as $indexType) {
+        foreach (\array_reverse($collections) as $indexTypeMeta) {
+            /** @var APIType $indexTypeMeta */
+            $indexType = $this->get($indexTypeMeta->getClassName() ?: $indexTypeMeta->getBuiltinType());
             if ($indexType === $number) {
                 $type = new ArrayType($type);
+            } else {
+                throw new DomainException("Cannot create collection with key $indexType");
             }
         }
 
@@ -283,77 +287,116 @@ final class SerializationMapper implements TypeFactoryInterface
         );
     }
 
-    /**
-     * @param PHPClass|string $class
-     *
-     * @return int
-     */
-    private function getRefCount($class): int
-    {
-        if (!$this->refCounts) {
-            $this->initRefCounts();
-        }
-
-        return $this->refCounts[PHPClass::get($class)->getFullName()] ?? 0;
-    }
-
-    private function initRefCounts(): void
+    private function init(): void
     {
         $reprs = $this->serialization->getRepresentations();
-        $this->refCounts = \array_fill_keys(array_keys($reprs), 0);
+        $refCounts = \array_fill_keys(array_keys($reprs), 0);
+        $children = \array_fill_keys(array_keys($reprs), []);
 
         foreach ($reprs as $repr) {
-            if ($repr->getParent()) {
-                ++$this->refCounts[$repr->getParent()->getFullName()];
+            $parent = $repr->getParent();
+            if ($parent) {
+                ++$refCounts[$parent->getFullName()];
+                $children[$parent->getFullName()][] = $repr->getClass();
             }
+
             foreach ($repr->getProperties() as $property) {
                 $className = $property->getLeafType()->getClassName();
                 if ($className && isset($this->refCounts[$className])) {
-                    ++$this->refCounts[$className];
+                    ++$refCounts[$className];
                 }
             }
         }
+
+        foreach ($reprs as $repr) {
+            $this->initInfo($repr, $refCounts);
+        }
+
+        foreach ($this->classInfo as $className => &$ci) {
+            $ci[3] = $this->initConcreteDescendants(PHPClass::get($className), $children);
+        }
+        unset($ci);
+
+//        if ($this->serialization->getRoot()->getBaseName() === 'Location') {
+//            printf("%s\n", json_encode($this->classInfo, \JSON_PRETTY_PRINT));
+//            die;
+//        }
     }
 
     /**
-     * @param PHPClass|string $class
+     * @param RepresentationMetadata $repr
+     * @param array                  $refCount
      *
-     * @return int
+     * @return array
      */
-    private function getCoveredResources($class): array
+    private function initInfo(RepresentationMetadata $repr, array $refCount): array
     {
-        $resources = [$class];
-
-        for ($i = 0; $i < \count($resources); ++$i) {
-            $resources = array_merge($resources, $this->getChildren($resources[$i]));
+        $className = $repr->getClass()->getFullName();
+        if (!isset($this->classInfo[$className])) {
+            $this->classInfo[$className] = $this->doInitInfo($repr, $refCount);
         }
 
-        return $resources;
+        return $this->classInfo[$className];
     }
 
     /**
-     * @param PHPClass|string $class
+     * @param RepresentationMetadata $repr
+     * @param array                  $refCount
      *
-     * @return int
+     * @return array
      */
-    private function getChildren($class): array
+    private function doInitInfo(RepresentationMetadata $repr, array $refCount): array
     {
-        if (!$this->children) {
-            $this->initChildren();
+        $class = $repr->getClass();
+        $className = $class->getFullName();
+        $properties = $repr->getProperties();
+
+        $parentClass = $repr->getParent();
+        if ($parentClass) {
+            [$keep, , $parentProperties, ] = $this->initInfo(
+                $this->serialization->getRepresentationOf($parentClass),
+                $refCount
+            );
+            if (!$keep) {
+                $parentClass = null;
+                $properties = array_replace($parentProperties, $properties);
+            }
         }
 
-        return $this->children[PHPClass::get($class)->getFullName()] ?? [];
+        if (!$this->serialization->isRoot($class)) {
+            if (!$parentClass && $refCount[$className] < 2) {
+                return [false, null, $properties, []];
+            }
+            if (!$properties) {
+                return [false, null, [], []];
+            }
+        }
+
+        return [true, $parentClass, $properties, []];
     }
 
-    private function initChildren(): void
+    /**
+     * @param PHPClass     $class
+     * @param PHPClass[][] $children
+     *
+     * @return PHPClass[]
+     */
+    private function initConcreteDescendants(PHPClass $class, array $children): array
     {
-        $reprs = $this->serialization->getRepresentations();
-        $this->children = \array_fill_keys(array_keys($reprs), []);
-
-        foreach ($reprs as $repr) {
-            if ($repr->getParent()) {
-                $this->children[$repr->getParent()->getFullName()][] = $repr->getClass();
+        /** @var PHPClass[] $queue */
+        $queue = [$class];
+        /** @var PHPClass[] $descendantsqueue */
+        $descendants = [];
+
+        while ($queue) {
+            $current = array_shift($queue);
+            if ($this->classInfo[$current->getFullName()][0]) {
+                $descendants[] = $current;
             }
+            /** @noinspection SlowArrayOperationsInLoopInspection */
+            $queue = array_merge($queue, $children[$current->getFullName()]);
         }
+
+        return $descendants;
     }
 }