diff --git a/src/Command/NgModelGenerateCommand.php b/src/Command/NgModelGenerateCommand.php
index fe4d1048674d4a5bbdc839914bb2fc016fd7a7b8..5e7507d863e74c4ea9f99105f99b6b6162dfb8a4 100644
--- a/src/Command/NgModelGenerateCommand.php
+++ b/src/Command/NgModelGenerateCommand.php
@@ -63,7 +63,7 @@ final class NgModelGenerateCommand extends Command
     {
         $this
             ->setName('ng-model:generate')
-            ->setDescription('Dump Typescript models');
+            ->setDescription('Dump Typescript representations');
     }
 
     /**
diff --git a/src/Context.php b/src/Context.php
deleted file mode 100644
index ac9cb04c3d4d5571b639f1c65d977d6dc017bf25..0000000000000000000000000000000000000000
--- a/src/Context.php
+++ /dev/null
@@ -1,130 +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;
-
-use Irstea\NgModelGeneratorBundle\Models\PHPClass;
-
-/**
- * Class Context.
- */
-class Context implements ContextInterface
-{
-    /** @var PHPClass */
-    private $rootClass;
-
-    /** @var bool */
-    private $normalization;
-
-    /** @var string[] */
-    private $groups;
-
-    /** @var string[][] */
-    private $defaultGroups;
-
-    /**
-     * Context constructor.
-     *
-     * @param string      $rootClass
-     * @param bool        $normalization
-     * @param string[]    $groups
-     * @param \string[][] $defaultGroups
-     */
-    public function __construct(PHPClass $rootClass, array $defaultGroups, bool $normalization = false, array $groups = [])
-    {
-        $this->rootClass = $rootClass;
-        $this->defaultGroups = $defaultGroups;
-        $this->normalization = $normalization;
-        $this->groups = $groups;
-        sort($this->groups);
-    }
-
-    /**
-     * @param bool $normalization
-     *
-     * @return Context
-     */
-    public function withNormalization(bool $normalization): ContextInterface
-    {
-        if ($normalization === $this->normalization) {
-            return $this;
-        }
-
-        $clone = clone $this;
-        $clone->normalization = $normalization;
-
-        return $clone;
-    }
-
-    /**
-     * @param string[] $groups
-     *
-     * @return Context
-     */
-    public function withGroups(array $groups): ContextInterface
-    {
-        sort($groups);
-
-        /* @noinspection TypeUnsafeComparisonInspection */
-        if ($groups == $this->groups) {
-            return $this;
-        }
-
-        $clone = clone $this;
-        $clone->groups = $groups;
-
-        return $clone;
-    }
-
-    /**
-     * Get rootClass.
-     *
-     * @return PHPClass
-     */
-    public function getRootClass(): PHPClass
-    {
-        return $this->rootClass;
-    }
-
-    /**
-     * @return bool
-     */
-    public function isNormalization(): bool
-    {
-        return $this->normalization;
-    }
-
-    /**
-     * @return array
-     */
-    public function getGroups(): array
-    {
-        return $this->groups;
-    }
-
-    /**
-     * @param PHPClass $class
-     *
-     * @return string[]
-     */
-    public function getDefaultGroups(PHPClass $class): array
-    {
-        return $this->defaultGroups[$this->normalization ? 'normalization' : 'denormalization'][$class->getFullName()] ?? [];
-    }
-}
diff --git a/src/ContextInterface.php b/src/ContextInterface.php
deleted file mode 100644
index 0b02806664be49c2a382d3da70c971a96b29ee0f..0000000000000000000000000000000000000000
--- a/src/ContextInterface.php
+++ /dev/null
@@ -1,66 +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;
-
-use Irstea\NgModelGeneratorBundle\Models\PHPClass;
-
-/**
- * Interface ContextInterface.
- */
-interface ContextInterface
-{
-    /**
-     * @param bool $normalization
-     *
-     * @return Context
-     */
-    public function withNormalization(bool $normalization): self;
-
-    /**
-     * @param string[] $groups
-     *
-     * @return Context
-     */
-    public function withGroups(array $groups): self;
-
-    /**
-     * Get rootClass.
-     *
-     * @return PHPClass
-     */
-    public function getRootClass(): PHPClass;
-
-    /**
-     * @return bool
-     */
-    public function isNormalization(): bool;
-
-    /**
-     * @return array
-     */
-    public function getGroups(): array;
-
-    /**
-     * @param PHPClass $class
-     *
-     * @return string[]
-     */
-    public function getDefaultGroups(PHPClass $class): array;
-}
diff --git a/src/DefaultNamingStrategy.php b/src/DefaultNamingStrategy.php
deleted file mode 100644
index 656f8b0f48b94f6e0f530f688d98a0a8268ce473..0000000000000000000000000000000000000000
--- a/src/DefaultNamingStrategy.php
+++ /dev/null
@@ -1,64 +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;
-
-use Irstea\NgModelGeneratorBundle\Models\PHPClass;
-
-/**
- * Class DefaultNamingStrategy.
- */
-final class DefaultNamingStrategy implements NamingStrategy
-{
-    /**
-     * {@inheritdoc}
-     */
-    public function getName(ContextInterface $ctx, PHPClass $resourceClass): string
-    {
-        $parts = array_diff(
-            $ctx->getGroups(),
-            $ctx->getDefaultGroups($resourceClass),
-            $ctx->getDefaultGroups($ctx->getRootClass())
-        );
-
-        $baseName = $resourceClass->getBaseName();
-        $shortPrefix = $ctx->isNormalization() ? '' : 'Put';
-        $fullPrefix = $ctx->isNormalization() ? 'Get' : 'Put';
-
-        if ($resourceClass === $ctx->getRootClass()) {
-            if (\in_array($shortPrefix . $baseName, $parts, true)) {
-                array_unshift($parts, $baseName);
-            } else {
-                array_unshift($parts, $shortPrefix . $baseName);
-            }
-        } else {
-            $rootBaseName = $ctx->getRootClass()->getBaseName();
-            $rootGroup = $fullPrefix . $rootBaseName;
-            if (\in_array($rootGroup, $parts, true)) {
-                array_unshift($parts, $rootBaseName);
-            } else {
-                array_unshift($parts, $rootGroup);
-            }
-
-            $parts[] = $baseName;
-        }
-
-        return implode('', $parts);
-    }
-}
diff --git a/src/Metadata/CachingMetadataFactory.php b/src/Metadata/CachingMetadataFactory.php
index 018e6568ded4183d388649c09a2487284285f1eb..35f553f2eec8ba76c06abd80f66dc5a468ef404d 100644
--- a/src/Metadata/CachingMetadataFactory.php
+++ b/src/Metadata/CachingMetadataFactory.php
@@ -60,30 +60,7 @@ final class CachingMetadataFactory implements MetadataFactoryInterface
     public function getResourceMetadata(PHPClass $class): ResourceMetadata
     {
         return $this->memoize(__METHOD__, $class->getFullName(), function () use ($class) {
-            return $this->inner->getResourceMetadata($class)->setFactory($this);
-        });
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getOperations(PHPClass $class): array
-    {
-        return $this->memoize(__METHOD__, $class->getFullName(), function () use ($class) {
-            return $this->inner->getOperations($class);
-        });
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getProperties(PHPClass $class, array $groups = []): array
-    {
-        sort($groups);
-        $key = $class->getFullName() . '::' . implode('-', $groups);
-
-        return $this->memoize(__METHOD__, $key, function () use ($class, $groups) {
-            return $this->inner->getProperties($class, $groups);
+            return $this->inner->getResourceMetadata($class);
         });
     }
 
diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php
index 95ebdd784c694a9cf9440eda50d20929d3cdbe6c..40f7ef7bfa3b8ccd0fa1ee46e4e12350359d5092 100644
--- a/src/Metadata/MetadataFactory.php
+++ b/src/Metadata/MetadataFactory.php
@@ -65,6 +65,9 @@ final class MetadataFactory implements MetadataFactoryInterface
     /** @var PaginationMetadata */
     private $paginationMetadata;
 
+    /** @var SerializationMetadata[] */
+    private $serializations = [];
+
     /**
      * MetadataFactory constructor.
      *
@@ -125,13 +128,17 @@ final class MetadataFactory implements MetadataFactoryInterface
             $metadata->getShortName(),
             $metadata->getDescription(),
             $classMeta->isAbstract(),
-            $this->buildPagination(
-                $metadata->getAttribute('pagination_enabled', true),
-                $metadata->getAttribute('pagination_client_items_per_page', true)
+            $this->getSerialization(
+                $class,
+                true,
+                $metadata->getAttribute('normalization_context', [])['groups'] ?? []
+            ),
+            $this->getSerialization(
+                $class,
+                false,
+                $metadata->getAttribute('normalization_context', [])['groups'] ?? []
             ),
-            $metadata->getAttribute('normalization_context', [])['groups'] ?? [],
-            $metadata->getAttribute('denormalization_context', [])['groups'] ?? [],
-            $this
+            $this->getOperations($class)
         );
     }
 
@@ -151,6 +158,137 @@ 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.
      *
@@ -166,7 +304,7 @@ final class MetadataFactory implements MetadataFactoryInterface
      *
      * @return OperationMetadata[]
      */
-    public function getOperations(PHPClass $class): array
+    private function getOperations(PHPClass $class): array
     {
         $resourceMetadata = $this->resourceMetadataFactory->create($class->getFullName());
 
@@ -216,20 +354,48 @@ final class MetadataFactory implements MetadataFactoryInterface
             return $resourceMetadata->getTypedOperationAttribute($type, $name, $attrName, $default, true);
         };
 
+        if ($type === OperationType::COLLECTION && $method === 'GET') {
+            $filters = $this->getFilters($class, $getAttribute('filters', []));
+            $pagination = $this->buildPagination(
+                $getAttribute('pagination_enabled', true),
+                $getAttribute('pagination_client_items_per_page', true)
+            );
+        } else {
+            $filters = [];
+            $pagination = null;
+        }
+
+        if (\in_array($method, ['GET', 'PUT', 'POST'], true)) {
+            $normalization = $this->getSerialization(
+                $class,
+                true,
+                $getAttribute('normalization_context', [])['groups'] ?? []
+            );
+        } else {
+            $normalization = null;
+        }
+
+        if (\in_array($method, ['POST', 'PUT'], true)) {
+            $denormalization = $this->getSerialization(
+                $class,
+                false,
+                $getAttribute('denormalization_context', [])['groups'] ?? []
+            );
+        } else {
+            $denormalization = null;
+        }
+
         return new OperationMetadata(
             $name,
             $operation['description'] ?? '',
             $type,
             $method,
             $path,
-            $getAttribute('normalization_context', [])['groups'] ?? [],
-            $getAttribute('denormalization_context', [])['groups'] ?? [],
             $getAttribute('requirements', []),
-            $this->getFilters($class, $getAttribute('filters', [])),
-            $this->buildPagination(
-                $getAttribute('pagination_enabled', true),
-                $getAttribute('pagination_client_items_per_page', true)
-            )
+            $filters,
+            $pagination,
+            $normalization,
+            $denormalization
         );
     }
 
@@ -257,67 +423,4 @@ final class MetadataFactory implements MetadataFactoryInterface
 
         return $filters;
     }
-
-    /**
-     * @param string $className
-     * @param array  $groups
-     *
-     * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException
-     * @throws \ApiPlatform\Core\Exception\ResourceClassNotFoundException
-     *
-     * @return PropertyMetadata[]
-     */
-    public function getProperties(PHPClass $class, 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;
-            }
-
-            $identifier = (bool) $propertyMeta->isIdentifier();
-            $readable = $propertyMeta->isReadable() && $this->propertyInfoExtractor->isReadable($className, $propertyName);
-            $writable = ($propertyMeta->isWritable() && $this->propertyInfoExtractor->isWritable($className, $propertyName));
-            $initializable = (bool) $propertyMeta->isInitializable();
-            $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;
-    }
 }
diff --git a/src/Metadata/MetadataFactoryInterface.php b/src/Metadata/MetadataFactoryInterface.php
index 2d6d1964c55ba07bf22fcb8e7bbcbfb9944bb257..42dfa79f199d2b7122876dbd0f67ef977a28431f 100644
--- a/src/Metadata/MetadataFactoryInterface.php
+++ b/src/Metadata/MetadataFactoryInterface.php
@@ -40,21 +40,6 @@ interface MetadataFactoryInterface
      */
     public function getResourceMetadata(PHPClass $class): ResourceMetadata;
 
-    /**
-     * @param PHPClass $class
-     *
-     * @return OperationMetadata[]
-     */
-    public function getOperations(PHPClass $class): array;
-
-    /**
-     * @param PHPClass $class
-     * @param array    $groups
-     *
-     * @return PropertyMetadata[]
-     */
-    public function getProperties(PHPClass $class, array $groups = []): array;
-
     /**
      * @return PaginationMetadata
      */
diff --git a/src/Metadata/Normalizer.php b/src/Metadata/Normalizer.php
deleted file mode 100644
index bdc59e4999cc637b2edd81c6fc5c8582934fb043..0000000000000000000000000000000000000000
--- a/src/Metadata/Normalizer.php
+++ /dev/null
@@ -1,356 +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\Metadata;
-
-use ApiPlatform\Core\Documentation\Documentation;
-use Irstea\NgModelGeneratorBundle\Context;
-use Irstea\NgModelGeneratorBundle\NamingStrategy;
-use Symfony\Component\PropertyInfo\Type;
-use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
-
-/**
- * Class Normalizer.
- */
-final class Normalizer implements NormalizerInterface
-{
-    /** @var string */
-    public const FORMAT_MIME_TYPE = 'application/x-api-platform-metadata+json';
-
-    /** @var string */
-    public const FORMAT_NAME = 'metadata.json';
-
-    /**
-     * @var MetadataFactoryInterface
-     */
-    private $metadataFactory;
-
-    /**
-     * @var NamingStrategy
-     */
-    private $namingStrategy;
-
-    /**
-     * Normalizer constructor.
-     *
-     * @param MetadataFactoryInterface $metadataFactory
-     * @param NamingStrategy           $namingStrategy
-     */
-    public function __construct(
-        MetadataFactoryInterface $metadataFactory,
-        NamingStrategy $namingStrategy
-    ) {
-        $this->metadataFactory = $metadataFactory;
-        $this->namingStrategy = $namingStrategy;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function normalize($data, $format = null, array $context = [])
-    {
-        if ($format === self::FORMAT_NAME && $data instanceof Documentation) {
-            return $this->normalizeDocumentation($data, $context);
-        }
-
-        return null;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function supportsNormalization($data, $format = null)
-    {
-        return $format === self::FORMAT_NAME && $data instanceof Documentation;
-    }
-
-    /**
-     * @param Documentation $doc
-     * @param array         $context
-     *
-     * @return array
-     */
-    private function normalizeDocumentation(Documentation $doc, array $context): array
-    {
-        $context['documentation'] = $doc;
-        $context['models'] = $models = new \ArrayObject();
-        $context['references'] = new \ArrayObject();
-
-        $defaultGroups = [];
-        /** @var string $className */
-        foreach ($doc->getResourceNameCollection() as $className) {
-            $meta = $this->metadataFactory->getResourceMetadata($className);
-            $defaultGroups['normalization'][$className] = $meta->getSerializationGroups(true);
-            $defaultGroups['denormalization'][$className] = $meta->getSerializationGroups(false);
-        }
-
-        $context['defaultGroups'] = $defaultGroups;
-
-        $resources = [];
-        /** @var string $className */
-        foreach ($doc->getResourceNameCollection() as $className) {
-            $meta = $this->metadataFactory->getResourceMetadata($className);
-            $context['resourceClassName'] = $className;
-
-            $resources[$meta->getShortName()] = $this->normalizeResource($meta, $context);
-        }
-
-        return [
-            'title'       => $doc->getTitle(),
-            'version'     => $doc->getVersion(),
-            'description' => $doc->getDescription(),
-            'mime_types'  => $doc->getMimeTypes(),
-            'resources'   => $resources,
-            'models'      => $models,
-        ];
-    }
-
-    /**
-     * @param ResourceMetadata $resource
-     * @param array            $context
-     *
-     * @return array
-     */
-    private function normalizeResource(ResourceMetadata $resource, array $context): array
-    {
-        $context['resource'] = $resource;
-
-        $operations = [];
-
-        foreach ($resource->getOperations() as $operation) {
-            $context['operation'] = $operation;
-            $type = $operation->getType();
-
-            if (!isset($operations[$type])) {
-                $operations[$type] = [];
-            }
-            $operations[$type][$operation->getName()] = $this->normalizeOperation($operation, $context);
-        }
-
-        return [
-            'className'              => $resource->getClassName(),
-            'shortName'              => $resource->getShortName(),
-            'parentClassName'        => $resource->getParentClassName(),
-            'description'            => $resource->getDescription(),
-            'pagination'             => $this->normalizePagination($resource->getPagination(), $context),
-            'normalization_groups'   => $resource->getSerializationGroups(true),
-            'denormalization_groups' => $resource->getSerializationGroups(false),
-            'operations'             => $operations,
-        ];
-    }
-
-    /**
-     * @param OperationMetadata $operation
-     * @param array             $context
-     *
-     * @return array
-     */
-    private function normalizeOperation(OperationMetadata $operation, array $context): array
-    {
-        $response = $requestBody = null;
-        $context['type'] = $operation->getType();
-
-        if (\in_array($operation->getMethod(), ['GET', 'PUT', 'POST'], true)) {
-            $context['normalization'] = true;
-            $context['groups'] = $operation->getSerializationGroups(true);
-            $response = $this->normalizeType($operation->getClassName(), $context);
-        }
-        if (\in_array($operation->getMethod(), ['PUT', 'POST'], true)) {
-            $context['normalization'] = false;
-            $context['groups'] = $operation->getSerializationGroups(false);
-            $requestBody = $this->normalizeType($operation->getClassName(), $context);
-        }
-
-        return [
-            'name'                   => $operation->getName(),
-            'type'                   => $operation->getType(),
-            'description'            => $operation->getDescription(),
-            'method'                 => $operation->getMethod(),
-            'path'                   => $operation->getPath(),
-            'requirements'           => $operation->getRequirements(),
-            'pagination'             => $operation->getPagination(),
-            'filters'                => $operation->getFilters(),
-            'normalization_groups'   => $operation->getSerializationGroups(true),
-            'denormalization_groups' => $operation->getSerializationGroups(false),
-            'requestBody'            => $requestBody,
-            'response'               => $response,
-        ];
-    }
-
-    /**
-     * @param PropertyMetadata $property
-     * @param array            $context
-     *
-     * @return array
-     */
-    private function normalizeProperty(PropertyMetadata $property, array $context): array
-    {
-        return [
-            'name'        => $property->getName(),
-            'description' => $property->getDescription(),
-            'type'        => $this->normalizeTypeInfo($property->getType(), $context),
-            'identifier'  => $property->isIdentifier(),
-            'nullable'    => $property->isNullable(),
-            'readable'    => $property->isReadable(),
-            'writable'    => $property->isWritable(),
-        ];
-    }
-
-    /**
-     * @param string $type
-     * @param array  $context
-     *
-     * @return mixed
-     */
-    private function normalizeTypeInfo(?Type $info, array $context)
-    {
-        if (!$info) {
-            return 'any';
-        }
-        if ($info->isCollection()) {
-            return [
-                'type'     => 'Array',
-                'itemType' => $this->normalizeTypeInfo($info->getCollectionValueType(), $context),
-            ];
-        }
-
-        return $this->normalizeType($info->getClassName() ?: $info->getBuiltinType(), $context);
-    }
-
-    /**
-     * @param string $name
-     * @param array  $context
-     *
-     * @return mixed
-     */
-    private function normalizeType(string $name, array $context)
-    {
-        switch ($name) {
-            case Type::BUILTIN_TYPE_BOOL:
-                return 'boolean';
-            case Type::BUILTIN_TYPE_NULL:
-                return 'null';
-            case Type::BUILTIN_TYPE_FLOAT:
-            case Type::BUILTIN_TYPE_INT:
-                return 'number';
-            case Type::BUILTIN_TYPE_STRING:
-                return 'string';
-            case Type::BUILTIN_TYPE_ARRAY:
-                return 'Array';
-            case \DateTime::class:
-            case \DateTimeImmutable::class:
-            case \DateTimeInterface::class:
-                return ['type' => 'string', 'subtype' => 'DateTime'];
-            case 'Ramsey\\Uuid\\UuidInterface':
-                return ['type' => 'string', 'subtype' => 'UUID'];
-        }
-
-        if (\class_exists($name)) {
-            if ($this->metadataFactory->isResource($name)) {
-                return $this->normalizeResourceRepresentation($name, $context);
-            }
-            if (\method_exists($name, '__toString')) {
-                return ['type' => 'string', 'subtype' => $name];
-            }
-
-            return ['type' => 'object', 'subtype' => $name];
-        }
-
-        return $name;
-    }
-
-    /**
-     * @param PaginationMetadata $data
-     * @param array              $context
-     *
-     * @return array
-     */
-    private function normalizePagination(PaginationMetadata $data, array $context): array
-    {
-        return [
-            'enabled'                              => $data->isEnabled(),
-            'page_parameter_name'                  => $data->getPageParameterName(),
-            'client_items_per_page_enabled'        => $data->isClientItemsPerPage(),
-            'client_items_per_page_parameter_name' => $data->getItemsPerPageParameterName(),
-        ];
-    }
-
-    /**
-     * @param string $name
-     * @param array  $context
-     */
-    private function normalizeResourceRepresentation(string $className, array $context)
-    {
-        $reprName = $this->namingStrategy->getName(Context::fromArray($context), $className);
-
-        if (isset($context['references'][$reprName])) {
-            return $context['references'][$reprName];
-        }
-
-        $resource = $this->metadataFactory->getResourceMetadata($className);
-        $properties = $resource->getProperties($context['groups']);
-
-        $hasNonIdentifier = false;
-        foreach ($properties as $property) {
-            if (!$property->isIdentifier()) {
-                $hasNonIdentifier = true;
-                break;
-            }
-        }
-
-        if (!$hasNonIdentifier) {
-            return $context['references'][$reprName] = [
-                'type'     => 'string',
-                'subtype'  => 'IRI',
-                'resource' => $resource->getShortName(),
-            ];
-        }
-
-        $context['references'][$reprName] = '#/models/' . $reprName;
-        $context['models'][$reprName] = array_merge(
-            ['@type' => $resource->getShortName()],
-            $this->normalizeModel($properties, $context)
-        );
-
-        return $context['references'][$reprName];
-    }
-
-    /**
-     * @param array $meta
-     * @param array $context
-     *
-     * @return array
-     */
-    private function normalizeModel(array $meta, array $context): array
-    {
-        $properties = [];
-
-        /** @var PropertyMetadata $property */
-        foreach ($meta as $property) {
-            $properties[$property->getName()] = $this->normalizeProperty($property, $context);
-        }
-
-        ksort($properties);
-
-        return [
-            'groups'     => $context['groups'],
-            'properties' => $properties,
-        ];
-    }
-}
diff --git a/src/Metadata/OperationMetadata.php b/src/Metadata/OperationMetadata.php
index 4c7404299e753459469c60b1114d8efa71d949cf..63a0ec8dacf0565e7a45df0dc45785c133783180 100644
--- a/src/Metadata/OperationMetadata.php
+++ b/src/Metadata/OperationMetadata.php
@@ -27,9 +27,6 @@ use ApiPlatform\Core\Api\OperationType;
  */
 class OperationMetadata implements \JsonSerializable
 {
-    use PaginedTrait;
-    use SerializationGroupsTrait;
-
     /** @var string */
     private $name;
 
@@ -54,19 +51,28 @@ class OperationMetadata implements \JsonSerializable
     /** @var string[] */
     private $requirements;
 
+    /** @var PaginationMetadata|null */
+    private $pagination;
+
+    /** @var SerializationMetadata|null */
+    private $normalization;
+
+    /** @var SerializationMetadata|null */
+    private $denormalization;
+
     /**
      * OperationMetadata constructor.
      *
-     * @param string             $name
-     * @param string             $description
-     * @param string             $type
-     * @param string             $method
-     * @param string             $path
-     * @param array              $normalizationGroups
-     * @param array              $denormalizationGroups
-     * @param array              $requirements
-     * @param array              $filters
-     * @param PaginationMetadata $pagination
+     * @param string                     $name
+     * @param string                     $description
+     * @param string                     $type
+     * @param string                     $method
+     * @param string                     $path
+     * @param array                      $requirements
+     * @param array                      $filters
+     * @param PaginationMetadata|null    $pagination
+     * @param SerializationMetadata|null $normalization
+     * @param SerializationMetadata|null $denormalization
      */
     public function __construct(
         string $name,
@@ -74,11 +80,11 @@ class OperationMetadata implements \JsonSerializable
         string $type,
         string $method,
         string $path,
-        array $normalizationGroups,
-        array $denormalizationGroups,
         array $requirements,
         array $filters,
-        PaginationMetadata $pagination
+        ?PaginationMetadata $pagination,
+        ?SerializationMetadata $normalization,
+        ?SerializationMetadata $denormalization
     ) {
         $this->name = $name;
         $this->type = $type;
@@ -87,9 +93,9 @@ class OperationMetadata implements \JsonSerializable
         $this->filters = $filters;
         $this->description = $description;
         $this->pagination = $pagination;
-        $this->normalizationGroups = $normalizationGroups;
-        $this->denormalizationGroups = $denormalizationGroups;
         $this->requirements = $requirements;
+        $this->normalization = $normalization;
+        $this->denormalization = $denormalization;
     }
 
     /**
@@ -191,6 +197,16 @@ class OperationMetadata implements \JsonSerializable
         return $this->requirements;
     }
 
+    /**
+     * Get pagination.
+     *
+     * @return PaginationMetadata|null
+     */
+    public function getPagination(): ?PaginationMetadata
+    {
+        return $this->pagination;
+    }
+
     /**
      * Get filters.
      *
@@ -201,6 +217,26 @@ class OperationMetadata implements \JsonSerializable
         return $this->filters;
     }
 
+    /**
+     * Get normalization.
+     *
+     * @return SerializationMetadata|null
+     */
+    public function getNormalization(): ?SerializationMetadata
+    {
+        return $this->normalization;
+    }
+
+    /**
+     * Get denormalization.
+     *
+     * @return SerializationMetadata|null
+     */
+    public function getDenormalization(): ?SerializationMetadata
+    {
+        return $this->denormalization;
+    }
+
     /**
      * Get resource.
      *
@@ -216,23 +252,9 @@ class OperationMetadata implements \JsonSerializable
      */
     public function jsonSerialize()
     {
-        return [
-            'name'                   => $this->name,
-            'description'            => $this->description,
-            'type'                   => $this->type,
-            'method'                 => $this->method,
-            'path'                   => $this->path,
-            'filters'                => $this->filters,
-            'requirements'           => $this->requirements,
-            'pagination'             => $this->pagination,
-            'normalization'          => [
-                'groups'     => $this->normalizationGroups,
-                'properties' => $this->getResource()->getProperties($this->normalizationGroups),
-            ],
-            'denormalization' => [
-                'groups'     => $this->denormalizationGroups,
-                'properties' => $this->getResource()->getProperties($this->denormalizationGroups),
-            ],
-        ];
+        $vars = \get_object_vars($this);
+        unset($vars['resource']);
+
+        return $vars;
     }
 }
diff --git a/src/Metadata/PaginationMetadata.php b/src/Metadata/PaginationMetadata.php
index 79394e23fc6f74d28d338c507284f36b0c551a02..08447aefd8e4614fb6990e4589c5e27257f0514f 100644
--- a/src/Metadata/PaginationMetadata.php
+++ b/src/Metadata/PaginationMetadata.php
@@ -99,11 +99,6 @@ final class PaginationMetadata implements \JsonSerializable
      */
     public function jsonSerialize()
     {
-        return [
-            'enabled'                       => $this->enabled,
-            'page_parameter_name'           => $this->pageParameterName,
-            'client_items_per_page_enabled' => $this->clientItemsPerPage,
-            'items_per_page_parameter_name' => $this->itemsPerPageParameterName,
-        ];
+        return \get_object_vars($this);
     }
 }
diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php
index f0b8db1223f504b38cedbe5252848a1faefc4159..60d7c361acc6a75d287f795846f2a95bb54073ec 100644
--- a/src/Metadata/PropertyMetadata.php
+++ b/src/Metadata/PropertyMetadata.php
@@ -35,8 +35,7 @@ class PropertyMetadata implements \JsonSerializable
     /** @var Type */
     private $type;
 
-    /** @var bool */
-    private $identifier;
+    /** @var bool */private $identifier;
 
     /** @var bool */
     private $readable;
@@ -44,9 +43,6 @@ class PropertyMetadata implements \JsonSerializable
     /** @var bool */
     private $writable;
 
-    /** @var ResourceMetadata */
-    private $resource;
-
     /** @var bool */
     private $initializable;
 
@@ -91,29 +87,6 @@ class PropertyMetadata implements \JsonSerializable
         $this->embedded = $embedded;
     }
 
-    /**
-     * @param ResourceMetadata $resource
-     *
-     * @return PropertyMetadata
-     */
-    public function withResource(ResourceMetadata $resource): self
-    {
-        $new = clone $this;
-        $new->resource = $resource;
-
-        return $new;
-    }
-
-    /**
-     * Get className.
-     *
-     * @return string
-     */
-    public function getClassName(): string
-    {
-        return $this->resource->getClassName();
-    }
-
     /**
      * Get name.
      *
@@ -150,7 +123,7 @@ class PropertyMetadata implements \JsonSerializable
     public function getLeafType(): Type
     {
         $type = $this->type;
-        while ($type->isCollection()) {
+        while ($type && $type->isCollection() && $type->getCollectionValueType()) {
             $type = $type->getCollectionValueType();
         }
 
@@ -225,33 +198,16 @@ class PropertyMetadata implements \JsonSerializable
         return $this->embedded;
     }
 
-    /**
-     * Get resource.
-     *
-     * @return ResourceMetadata
-     */
-    public function getResource(): ResourceMetadata
-    {
-        return $this->resource;
-    }
-
     /**
      * {@inheritdoc}
      */
     public function jsonSerialize()
     {
-        return [
-            'name'          => $this->name,
-            'description'   => $this->description,
-            'type'          => $this->serializeType($this->type),
-            'identifier'    => $this->identifier,
-            'readable'      => $this->readable,
-            'writable'      => $this->writable,
-            'nullable'      => $this->isNullable(),
-            'initializable' => $this->initializable,
-            'link'          => $this->link,
-            'embedded'      => $this->embedded,
-        ];
+        $vars = \get_object_vars($this);
+        $vars['type'] = $this->serializeType($this->type);
+        unset($vars['resource']);
+
+        return $vars;
     }
 
     /**
diff --git a/src/Metadata/RepresentationMetadata.php b/src/Metadata/RepresentationMetadata.php
new file mode 100644
index 0000000000000000000000000000000000000000..58f61bf3ef8fe4f65f8d099d1c60e51d9e7fbebd
--- /dev/null
+++ b/src/Metadata/RepresentationMetadata.php
@@ -0,0 +1,99 @@
+<?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 Irstea\NgModelGeneratorBundle\Models\PHPClass;
+
+/**
+ * Class RepresentationMetadata.
+ */
+final class RepresentationMetadata implements \JsonSerializable
+{
+    /**
+     * @var PHPClass
+     */
+    private $class;
+
+    /**
+     * @var null|PHPClass
+     */
+    private $parent;
+
+    /**
+     * @var array
+     */
+    private $properties = [];
+
+    /**
+     * RepresentationMetadata constructor.
+     *
+     * @param PHPClass           $class
+     * @param PHPClass|null      $parent
+     * @param PropertyMetadata[] $properties
+     */
+    public function __construct(PHPClass $class, ?PHPClass $parent, array $properties)
+    {
+        $this->class = $class;
+        $this->parent = $parent;
+
+        foreach ($properties as $property) {
+            $this->properties[$property->getName()] = $property;
+        }
+        ksort($this->properties);
+    }
+
+    /**
+     * Get class.
+     *
+     * @return PHPClass
+     */
+    public function getClass(): PHPClass
+    {
+        return $this->class;
+    }
+
+    /**
+     * Get parent.
+     *
+     * @return null|PHPClass
+     */
+    public function getParent(): ?PHPClass
+    {
+        return $this->parent;
+    }
+
+    /**
+     * Get properties.
+     *
+     * @return PropertyMetadata[]
+     */
+    public function getProperties(): array
+    {
+        return $this->properties;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function jsonSerialize()
+    {
+        return \get_object_vars($this);
+    }
+}
diff --git a/src/Metadata/ResourceMetadata.php b/src/Metadata/ResourceMetadata.php
index cda350562ba0aeca44a32f69ae48abc0ce73c95c..95eb4b67a366edd4c92a507652cda1f500a0ea7e 100644
--- a/src/Metadata/ResourceMetadata.php
+++ b/src/Metadata/ResourceMetadata.php
@@ -26,39 +26,38 @@ use Irstea\NgModelGeneratorBundle\Models\PHPClass;
  */
 class ResourceMetadata implements \JsonSerializable
 {
-    use PaginedTrait;
-    use SerializationGroupsTrait;
-
     /** @var PHPClass */
     private $class;
 
     /** @var PHPClass|null */
     private $parentClass;
 
-    /** @var string */
-    private $shortName;
-
     /** @var string */
     private $description;
 
     /** @var bool */
     private $abstract;
 
-    /** @var MetadataFactoryInterface */
-    private $factory;
+    /** @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 PaginationMetadata       $pagination
-     * @param string[]                 $normalizationGroups
-     * @param string[]                 $denormalizationGroups
-     * @param MetadataFactoryInterface $factory
+     * @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
      */
     public function __construct(
         PHPClass $class,
@@ -66,34 +65,21 @@ class ResourceMetadata implements \JsonSerializable
         string $shortName,
         string $description,
         bool $abstract,
-        PaginationMetadata $pagination,
-        array $normalizationGroups,
-        array $denormalizationGroups,
-        MetadataFactoryInterface $factory
+        SerializationMetadata $defaultNormalization,
+        SerializationMetadata $defaultDenormalization,
+        array $operations
     ) {
         $this->class = $class;
         $this->parentClass = $parentClass;
-        $this->shortName = $shortName;
-        $this->description = $description;
-        $this->factory = $factory;
         $this->abstract = $abstract;
-        $this->pagination = $pagination;
-        $this->normalizationGroups = $normalizationGroups;
-        $this->denormalizationGroups = $denormalizationGroups;
-    }
-
-    /**
-     * Set factory.
-     *
-     * @param MetadataFactoryInterface $factory
-     *
-     * @return ResourceMetadata
-     */
-    public function setFactory(MetadataFactoryInterface $factory): self
-    {
-        $this->factory = $factory;
+        $this->description = $description;
+        $this->defaultNormalization = $defaultNormalization;
+        $this->defaultDenormalization = $defaultDenormalization;
 
-        return $this;
+        foreach ($operations as $operation) {
+            $this->operations[$operation->getName() . $operation->getType()] = $operation->withResource($this);
+        }
+        ksort($this->operations);
     }
 
     /**
@@ -163,29 +149,27 @@ class ResourceMetadata implements \JsonSerializable
      */
     public function getOperations(): array
     {
-        return array_map(
-            function (OperationMetadata $op): OperationMetadata {
-                return $op->withResource($this);
-            },
-            $this->factory->getOperations($this->class)
-        );
+        return $this->operations;
     }
 
     /**
-     * Get properties.
+     * Get defaultNormalization.
      *
-     * @param array $groups
+     * @return SerializationMetadata
+     */
+    public function getDefaultNormalization(): SerializationMetadata
+    {
+        return $this->defaultNormalization;
+    }
+
+    /**
+     * Get defaultDenormalization.
      *
-     * @return PropertyMetadata[]
+     * @return SerializationMetadata
      */
-    public function getProperties(array $groups = []): array
+    public function getDefaultDenormalization(): SerializationMetadata
     {
-        return array_map(
-            function (PropertyMetadata $p): PropertyMetadata {
-                return $p->withResource($this);
-            },
-            $this->factory->getProperties($this->class, $groups)
-        );
+        return $this->defaultDenormalization;
     }
 
     /**
@@ -193,22 +177,6 @@ class ResourceMetadata implements \JsonSerializable
      */
     public function jsonSerialize()
     {
-        return [
-            'class_name'             => $this->class->getFullName(),
-            'parent_class_name'      => $this->getParentClassName(),
-            'short_name'             => $this->shortName,
-            'description'            => $this->description,
-            'abstract'               => $this->abstract,
-            'pagination'             => $this->pagination,
-            'normalization'          => [
-                'groups'     => $this->normalizationGroups,
-                'properties' => $this->getProperties($this->normalizationGroups),
-            ],
-            'denormalization' => [
-                'groups'     => $this->denormalizationGroups,
-                'properties' => $this->getProperties($this->denormalizationGroups),
-            ],
-            'operations'             => $this->getOperations(),
-        ];
+        return \get_object_vars($this);
     }
 }
diff --git a/src/Metadata/SerializationGroupsTrait.php b/src/Metadata/SerializationGroupsTrait.php
deleted file mode 100644
index 309cb11d46a6f47eacfeaf2a1c2019756a36510e..0000000000000000000000000000000000000000
--- a/src/Metadata/SerializationGroupsTrait.php
+++ /dev/null
@@ -1,42 +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\Metadata;
-
-/**
- * Trait SerializationGroupsTrait.
- */
-trait SerializationGroupsTrait
-{
-    /** @var string[] */
-    private $normalizationGroups;
-
-    /** @var string[] */
-    private $denormalizationGroups;
-
-    /**
-     * @param bool $normalization
-     *
-     * @return string[]
-     */
-    public function getSerializationGroups(bool $normalization): array
-    {
-        return $normalization ? $this->normalizationGroups : $this->denormalizationGroups;
-    }
-}
diff --git a/src/Metadata/SerializationMetadata.php b/src/Metadata/SerializationMetadata.php
new file mode 100644
index 0000000000000000000000000000000000000000..999bd5d45ec33af2dd5eb8bf9c6c01aa299e0a92
--- /dev/null
+++ b/src/Metadata/SerializationMetadata.php
@@ -0,0 +1,148 @@
+<?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 Irstea\NgModelGeneratorBundle\Models\PHPClass;
+
+/**
+ * Class SerializationMetadata.
+ */
+final class SerializationMetadata implements \JsonSerializable
+{
+    /**
+     * @var PHPClass
+     */
+    private $root;
+
+    /**
+     * @var string[]
+     */
+    private $groups;
+
+    /**
+     * @var bool
+     */
+    private $normalization;
+
+    /**
+     * @var RepresentationMetadata[]
+     */
+    private $representations = [];
+
+    /**
+     * SerializationMetadata constructor.
+     *
+     * @param PHPClass                 $root
+     * @param string[]                 $groups
+     * @param bool                     $normalization
+     * @param RepresentationMetadata[] $representations
+     */
+    public function __construct(PHPClass $root, array $groups, bool $normalization, array $representations)
+    {
+        $this->groups = $groups;
+        sort($this->groups);
+
+        $this->root = $root;
+        $this->normalization = $normalization;
+
+        foreach ($representations as $representation) {
+            $this->representations[$representation->getClass()->getFullName()] = $representation;
+        }
+        ksort($this->representations);
+    }
+
+    /**
+     * Get groups.
+     *
+     * @return string[]
+     */
+    public function getGroups(): array
+    {
+        return $this->groups;
+    }
+
+    /**
+     * Get root.
+     *
+     * @return PHPClass
+     */
+    public function getRoot(): PHPClass
+    {
+        return $this->root;
+    }
+
+    /**
+     * @param PHPClass|string $class
+     *
+     * @return bool
+     */
+    public function isRoot($class): bool
+    {
+        return $this->root === PHPClass::get($class);
+    }
+
+    /**
+     * Get representations.
+     *
+     * @return RepresentationMetadata[]
+     */
+    public function getRepresentations(): array
+    {
+        return $this->representations;
+    }
+
+    /**
+     * @param PHPClass $class
+     *
+     * @return bool
+     */
+    public function hasRepresentationOf(PHPClass $class): bool
+    {
+        return isset($this->representations[$class->getFullName()]);
+    }
+
+    /**
+     * @param PHPClass $class
+     *
+     * @return RepresentationMetadata
+     */
+    public function getRepresentationOf(PHPClass $class): RepresentationMetadata
+    {
+        return $this->representations[$class->getFullName()];
+    }
+
+    /**
+     * Get normalization.
+     *
+     * @return bool
+     */
+    public function isNormalization(): bool
+    {
+        return $this->normalization;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function jsonSerialize()
+    {
+        return \get_object_vars($this);
+    }
+}
diff --git a/src/ModelGenerator.php b/src/ModelGenerator.php
index 72a3aa831289882736925f700913f7d00fe46d29..00cce57644ecb907dae1032a940572aa103d41cc 100644
--- a/src/ModelGenerator.php
+++ b/src/ModelGenerator.php
@@ -28,6 +28,7 @@ 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;
@@ -58,8 +59,8 @@ final class ModelGenerator
     /** @var TypeFactory */
     private $typeFactory;
 
-    /** @var string[][][] */
-    private $defaultGroups;
+    /** @var SerializationMetadata[][] */
+    private $defaultSerializations;
 
     /** @var ConcreteRepresentation[][] */
     private $resources;
@@ -67,24 +68,18 @@ final class ModelGenerator
     /** @var ClassHierarchy */
     private $classHierarchy;
 
-    /** @var NamingStrategy */
-    private $namingStrategy;
-
     /**
      * Serializer constructor.
      *
      * @param MetadataFactoryInterface $metadataFactory
      * @param Environment              $twigEnv
-     * @param NamingStrategy           $namingStrategy
      */
     public function __construct(
         MetadataFactoryInterface $metadataFactory,
-        Environment $twigEnv,
-        NamingStrategy $namingStrategy
+        Environment $twigEnv
     ) {
         $this->metadataFactory = $metadataFactory;
         $this->twigEnv = $twigEnv;
-        $this->namingStrategy = $namingStrategy;
     }
 
     /**
@@ -125,9 +120,7 @@ final class ModelGenerator
 
         $this->typeFactory = $this->createTypeFactory();
 
-        $this->defaultGroups = $this->extractDefaultGroups();
-
-        $this->resources = $this->extractBaseRepresentations();
+        $this->defaultSerializations = $this->extractDefaultSerializations();
 
         [$repositories, $iriPatterns] = $this->extractRepositories();
 
@@ -139,7 +132,6 @@ final class ModelGenerator
                 'title'        => $this->documentation->getTitle(),
                 'version'      => $this->documentation->getVersion(),
                 'description'  => $this->documentation->getDescription() ?: '',
-                'resources'    => $this->resources,
                 'repositories' => $repositories,
                 'declarations' => $declarations,
                 'iriPatterns'  => $iriPatterns,
@@ -206,12 +198,7 @@ final class ModelGenerator
             $factory->addAlias($alias, $target);
         }
 
-        return new ResourceTypeFactory(
-            $this->metadataFactory,
-            $factory,
-            $this->namingStrategy,
-            $this->classHierarchy
-        );
+        return $factory;
     }
 
     /**
@@ -237,11 +224,11 @@ final class ModelGenerator
     /**
      * Extrait les groupes de sérialization par défaut des ressources.
      *
-     * @return string[][][]
+     * @return SerializationMetadata[][]
      */
-    private function extractDefaultGroups(): array
+    private function extractDefaultSerializations(): array
     {
-        $defaultGroups = [
+        $serializations = [
             'normalization'   => [],
             'denormalization' => [],
         ];
@@ -253,14 +240,13 @@ final class ModelGenerator
         foreach ($this->getResourceMetadata() as $class => $resourceMeta) {
             $className = $class->getFullName();
 
-            foreach (['normalization', 'denormalization'] as $normalization) {
-                $groups = $resourceMeta->getSerializationGroups($normalization === 'normalization');
-                sort($groups);
-                $defaultGroups[$normalization][$className] = $groups;
-            }
+            $serializations['normalization'][$className] =
+                $resourceMeta->getDefaultNormalization();
+            $serializations['denormalization'][$className] =
+                $resourceMeta->getDefaultDenormalization();
         }
 
-        return $defaultGroups;
+        return $serializations;
     }
 
     /**
@@ -279,38 +265,6 @@ final class ModelGenerator
         }
     }
 
-    /**
-     * Extrait les représentations par défaut des ressources.
-     *
-     * @return ConcreteRepresentation[][]
-     */
-    private function extractBaseRepresentations(): array
-    {
-        $representations = [];
-
-        /** @var PHPClass $class */
-        foreach ($this->getResourceMetadata() as $class => $resourceMeta) {
-            $className = $class->getFullName();
-            $representations[$className] = [];
-
-            foreach (['normalization', 'denormalization'] as $normalization) {
-                $ctx = new Context(
-                    $class,
-                    $this->defaultGroups,
-                    $normalization === 'normalization',
-                    $this->defaultGroups[$normalization][$className]
-                );
-
-                $representations[$className][$normalization] =
-                    $this->typeFactory
-                        ->withContext($ctx)
-                        ->get($className);
-            }
-        }
-
-        return $representations;
-    }
-
     /**
      * @return array
      */
@@ -326,20 +280,16 @@ final class ModelGenerator
         foreach ($this->getResourceMetadata() as $class => $resourceMeta) {
             $repoName = $resourceMeta->getShortName() . 'Repository';
 
-            $ctx = new Context($class, $this->defaultGroups);
-
             $repositories[$repoName] = $this->typeFactory->getOrCreate(
                 $repoName,
 
-                function () use ($resourceMeta, $class, &$iriPatterns, $ctx): Type {
+                function () use ($resourceMeta, &$iriPatterns): Type {
                     $operations = [];
 
                     foreach ($resourceMeta->getOperations() as $operation) {
                         $mapper = new OperationMapper(
-                            $this->typeFactory->withContext($ctx),
-                            $operation,
-                            $this->resources[$class->getFullName()]['normalization'],
-                            $ctx
+                            $this->typeFactory,
+                            $operation
                         );
                         $operations[] = $operation = $mapper();
 
diff --git a/src/Models/PHPClass.php b/src/Models/PHPClass.php
index 60954b99d608c178c570cb8c667808891cff887a..a20e719ed27354fbbca2905d31e6668f7dd8c3f6 100644
--- a/src/Models/PHPClass.php
+++ b/src/Models/PHPClass.php
@@ -26,8 +26,6 @@ use Irstea\NgModelGeneratorBundle\Exceptions\InvalidArgumentException;
  */
 final class PHPClass implements \JsonSerializable
 {
-    use MultitonTrait;
-
     /** @var string */
     private $namespace;
 
@@ -37,15 +35,13 @@ final class PHPClass implements \JsonSerializable
     /**
      * ClassName constructor.
      *
-     * @param string $name
+     * @param string $namespace
+     * @param string $baseName
      */
-    private function __construct(string $name)
+    private function __construct(string $namespace, string $baseName)
     {
-        $groups = [];
-        if (!preg_match('/^\\\\?((?:\w+\\\\)*)(\w+)$/i', $name, $groups)) {
-            throw new InvalidArgumentException("Invalid PHP class name: $name");
-        }
-        [, $this->namespace, $this->baseName] = $groups;
+        $this->namespace = $namespace;
+        $this->baseName = $baseName;
     }
 
     /**
@@ -120,4 +116,30 @@ final class PHPClass implements \JsonSerializable
     {
         return $a->getBaseName() > $b->getBaseName();
     }
+
+    /**
+     * @param self|string $name
+     *
+     * @return self
+     */
+    public static function get($name): self
+    {
+        if ($name instanceof self) {
+            return $name;
+        }
+
+        static $instances = [];
+
+        if (!isset($instances[$name])) {
+            $groups = [];
+            if (!preg_match('/^\\\\?((?:\w+\\\\)*)(\w+)$/i', $name, $groups)) {
+                throw new InvalidArgumentException("Invalid PHP class name: $name");
+            }
+            [, $namespace, $baseName] = $groups;
+
+            $instances[$name] = new self($namespace, $baseName);
+        }
+
+        return $instances[$name];
+    }
 }
diff --git a/src/Models/Types/Generic.php b/src/Models/Types/AbstractCollection.php
similarity index 51%
rename from src/Models/Types/Generic.php
rename to src/Models/Types/AbstractCollection.php
index 93f0e69082e6d01f1153475fec045d92555df65c..8817b9630934909f2a67bcbb16c49593f7c8e6a1 100644
--- a/src/Models/Types/Generic.php
+++ b/src/Models/Types/AbstractCollection.php
@@ -20,82 +20,57 @@
 namespace Irstea\NgModelGeneratorBundle\Models\Types;
 
 /**
- * Class Generic.
+ * Class AbstractCollection.
  */
-final class Generic extends AbstractType
+abstract class AbstractCollection extends AbstractType
 {
     /**
      * @var Type
      */
-    private $baseType;
+    protected $valueType;
 
     /**
-     * @var Type[]
-     */
-    private $parameters;
-
-    /**
-     * Generic constructor.
+     * Collection constructor.
      *
-     * @param Type   $baseType
-     * @param Type[] $parameters
+     * @param Type $itemType
      */
-    public function __construct(Type $baseType, array $parameters)
+    public function __construct(Type $itemType)
     {
-        $this->baseType = $baseType;
-        $this->parameters = $parameters;
+        $this->valueType = $itemType;
     }
 
     /**
      * {@inheritdoc}
      */
-    public function getUsage(): string
+    public function getIterator()
     {
-        return sprintf(
-            '%s<%s>',
-            $this->baseType->getUsage(),
-            \implode(', ', \array_map(
-                function (Type $t): string {
-                    return $t->getUsage();
-                },
-                $this->parameters
-            ))
-        );
+        yield $this->getKeyType();
+        yield $this->valueType;
     }
 
     /**
      * {@inheritdoc}
      */
-    public function getIterator()
+    public function getUsage(): string
     {
-        yield $this->baseType;
-        yield from $this->parameters;
+        return sprintf('%s<%s>', $this->getGenericUsage(), $this->valueType->getUsage());
     }
 
     /**
-     * {@inheritdoc}
+     * @return array
      */
-    public function castToStringOrStringArray(string $expr): string
+    public function jsonSerialize()
     {
-        return $this->baseType->castToStringOrStringArray($expr);
+        return ['type' => $this->getGenericUsage(), 'keyType' => $this->getKeyType(), 'valueType' => $this->valueType];
     }
 
     /**
-     * {@inheritdoc}
+     * @return Type
      */
-    public function checkType(string $expr): string
-    {
-        return $this->baseType->checkType($expr);
-    }
+    abstract protected function getKeyType(): Type;
 
     /**
-     * {@inheritdoc}
+     * @return string
      */
-    public function jsonSerialize()
-    {
-        return [
-            'baseType'       => $this->baseType,
-            'parameters'     => $this->parameters,
-        ];
-    }
+    abstract protected function getGenericUsage(): string;
 }
diff --git a/src/Models/Types/Collection.php b/src/Models/Types/ArrayType.php
similarity index 67%
rename from src/Models/Types/Collection.php
rename to src/Models/Types/ArrayType.php
index 1174593df28e025da1c342d12c8856a5897e4970..3527d4c1b3ce73373d85301d168146173ab1db58 100644
--- a/src/Models/Types/Collection.php
+++ b/src/Models/Types/ArrayType.php
@@ -20,31 +20,24 @@
 namespace Irstea\NgModelGeneratorBundle\Models\Types;
 
 /**
- * Class Collection.
+ * Class ArrayType.
  */
-final class Collection extends AbstractType
+class ArrayType extends AbstractCollection
 {
     /**
-     * @var Type
-     */
-    private $itemType;
-
-    /**
-     * Collection constructor.
-     *
-     * @param Type $itemType
+     * {@inheritdoc}
      */
-    public function __construct(Type $itemType)
+    protected function getKeyType(): Type
     {
-        $this->itemType = $itemType;
+        return BuiltinType::get('number');
     }
 
     /**
      * {@inheritdoc}
      */
-    public function getUsage(): string
+    protected function getGenericUsage(): string
     {
-        return sprintf('Array<%s>', $this->itemType->getUsage());
+        return 'Array';
     }
 
     /**
@@ -52,7 +45,7 @@ final class Collection extends AbstractType
      */
     public function castToStringOrStringArray(string $expr): string
     {
-        $cast = $this->itemType->castToStringOrStringArray('x');
+        $cast = $this->valueType->castToStringOrStringArray('x');
         if ($cast !== 'x') {
             return sprintf('%s.map(x => %s)', $expr, $cast);
         }
@@ -67,20 +60,4 @@ final class Collection extends AbstractType
     {
         return sprintf('Array.isArray(%s)', $expr);
     }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getIterator()
-    {
-        yield $this->itemType;
-    }
-
-    /**
-     * @return array
-     */
-    public function jsonSerialize()
-    {
-        return ['type' => 'Array', 'itemType' => $this->itemType];
-    }
 }
diff --git a/src/Models/Types/BuiltinType.php b/src/Models/Types/BuiltinType.php
index 7a9c60eed96a82817a34bc445878c1220ad614ca..37aeaafa71ca1e442bc93c98f93bfa4d5f94c9f6 100644
--- a/src/Models/Types/BuiltinType.php
+++ b/src/Models/Types/BuiltinType.php
@@ -26,7 +26,7 @@ use Irstea\NgModelGeneratorBundle\TypescriptHelper;
 /**
  * Class BuiltinType.
  */
-final class BuiltinType extends AbstractType
+class BuiltinType extends AbstractType
 {
     use NamedTrait;
     use MultitonTrait;
diff --git a/src/Models/Types/Resources/AbstractRepresentation.php b/src/Models/Types/Resources/AbstractRepresentation.php
index ec423fb4a1c93409a4226e9e8e8825b627ce407f..44d30a194d9667136b428abb29dd98c9a580caba 100644
--- a/src/Models/Types/Resources/AbstractRepresentation.php
+++ b/src/Models/Types/Resources/AbstractRepresentation.php
@@ -37,9 +37,6 @@ final class AbstractRepresentation extends AbstractType implements Representatio
     /** @var Type[] */
     private $children;
 
-    /** @var ResourceMetadata */
-    private $resource;
-
     /**
      * AbstractRepresentation constructor.
      *
@@ -49,13 +46,12 @@ final class AbstractRepresentation extends AbstractType implements Representatio
      * @param Type|null        $parent
      * @param Type[]           $children
      */
-    public function __construct(ResourceMetadata $resource, string $name, string $description, Type $parent = null, array $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;
-        $this->resource = $resource;
     }
 
     /**
@@ -132,28 +128,4 @@ final class AbstractRepresentation extends AbstractType implements Representatio
             'children' => $this->children,
         ];
     }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getResourceClassName(): string
-    {
-        return $this->resource->getClassName();
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getResourceShortName(): string
-    {
-        return $this->resource->getShortName();
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function isAbstractResource(): bool
-    {
-        return true;
-    }
 }
diff --git a/src/Metadata/PaginedTrait.php b/src/Models/Types/Resources/Collection.php
similarity index 74%
rename from src/Metadata/PaginedTrait.php
rename to src/Models/Types/Resources/Collection.php
index 78b5133ac92c1f55dd02efbe68efd19b95c7a234..8754e96596e7360fd1453b04259b89557279d62e 100644
--- a/src/Metadata/PaginedTrait.php
+++ b/src/Models/Types/Resources/Collection.php
@@ -17,23 +17,20 @@
  * <https://www.gnu.org/licenses/>.
  */
 
-namespace Irstea\NgModelGeneratorBundle\Metadata;
+namespace Irstea\NgModelGeneratorBundle\Models\Types\Resources;
+
+use Irstea\NgModelGeneratorBundle\Models\Types\ArrayType;
 
 /**
- * Trait PaginedTrait.
+ * Class Collection.
  */
-trait PaginedTrait
+final class Collection extends ArrayType
 {
-    /** @var PaginationMetadata */
-    private $pagination;
-
     /**
-     * Get pagination.
-     *
-     * @return PaginationMetadata
+     * {@inheritdoc}
      */
-    public function getPagination(): PaginationMetadata
+    protected function getGenericUsage(): string
     {
-        return $this->pagination;
+        return 'Collection';
     }
 }
diff --git a/src/Models/Types/Resources/ConcreteRepresentation.php b/src/Models/Types/Resources/ConcreteRepresentation.php
index 6f87ea44b5a4e153ca45d871da3d9f7ea58b4c0f..952d3c5ab13cdb2eda58823130ccac3a5b4daeee 100644
--- a/src/Models/Types/Resources/ConcreteRepresentation.php
+++ b/src/Models/Types/Resources/ConcreteRepresentation.php
@@ -19,8 +19,9 @@
 
 namespace Irstea\NgModelGeneratorBundle\Models\Types\Resources;
 
-use Irstea\NgModelGeneratorBundle\Metadata\ResourceMetadata;
+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;
 
@@ -29,54 +30,29 @@ use Irstea\NgModelGeneratorBundle\TypescriptHelper;
  */
 final class ConcreteRepresentation extends InterfaceType implements Representation
 {
-    /** @var ResourceMetadata */
-    private $resource;
-
     /** @var bool */
     private $normalization;
 
     /**
-     * Representation constructor.
-     *
-     * @param ResourceMetadata $resource
-     * @param string           $name
-     * @param Type|null        $parent
-     * @param array            $properties
-     * @param string           $description
-     * @param array            $children
-     * @param bool             $normalization
+     * @var array|PHPClass[]
      */
-    public function __construct(ResourceMetadata $resource, string $name, ?Type $parent, array $properties, string $description, array $children, bool $normalization)
-    {
-        parent::__construct($name, $parent, $properties, $description, $children);
-        $this->normalization = $normalization;
-        $this->resource = $resource;
-    }
+    private $resources;
 
     /**
-     * Get resourceClassName.
+     * Representation constructor.
      *
-     * @return string
-     */
-    public function getResourceClassName(): string
-    {
-        return $this->resource->getClassName();
-    }
-
-    /**
-     * @return string
-     */
-    public function getResourceShortName(): string
-    {
-        return $this->resource->getShortName();
-    }
-
-    /**
-     * {@inheritdoc}
+     * @param string     $name
+     * @param Type|null  $parent
+     * @param Property[] $properties
+     * @param string     $description
+     * @param PHPClass[] $resources
+     * @param bool       $normalization
      */
-    public function isAbstractResource(): bool
+    public function __construct(string $name, ?Type $parent, array $properties, string $description, array $resources, bool $normalization)
     {
-        return false;
+        parent::__construct($name, $parent, $properties, $description, $resources);
+        $this->normalization = $normalization;
+        $this->resources = $resources;
     }
 
     /**
@@ -117,19 +93,9 @@ final class ConcreteRepresentation extends InterfaceType implements Representati
      */
     private function getAllResourceNames(): array
     {
-        ksort($names);
-
-        return array_keys($names);
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function jsonSerialize()
-    {
-        $json = parent::jsonSerialize();
-        $json['resource'] = $this->getResourceShortName();
-
-        return $json;
+        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
new file mode 100644
index 0000000000000000000000000000000000000000..e251a5f937b6104dbf034414a926ec01aa2a2c62
--- /dev/null
+++ b/src/Models/Types/Resources/IRI.php
@@ -0,0 +1,75 @@
+<?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\MultitonTrait;
+use Irstea\NgModelGeneratorBundle\Models\PHPClass;
+use Irstea\NgModelGeneratorBundle\Models\Types\AbstractType;
+use Irstea\NgModelGeneratorBundle\TypescriptHelper;
+
+/**
+ * Class IRI.
+ */
+final class IRI extends AbstractType
+{
+    use MultitonTrait;
+
+    /** @var PHPClass */
+    private $resource;
+
+    /**
+     * IRI constructor.
+     *
+     * @param PHPClass $resource
+     */
+    public function __construct(string $className)
+    {
+        $this->resource = PHPClass::get($className);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUsage(): string
+    {
+        return sprintf('IRI<%s>', $this->resource->getBaseName());
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function castToStringOrStringArray(string $expr): string
+    {
+        return $expr;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function checkType(string $expr): string
+    {
+        return sprintf(
+            'isIRI<%s>(%s, %s)',
+            $this->resource->getBaseName(),
+            $expr,
+            TypescriptHelper::quoteString($this->resource->getBaseName())
+        );
+    }
+}
diff --git a/src/Models/Types/Resources/Representation.php b/src/Models/Types/Resources/Representation.php
index 4e269de6684e25430a96e5ef47c4438f7c5def02..375af4d58b293670ce5ed25d7d2d57370f93cae0 100644
--- a/src/Models/Types/Resources/Representation.php
+++ b/src/Models/Types/Resources/Representation.php
@@ -27,18 +27,4 @@ use Irstea\NgModelGeneratorBundle\Models\Types\Type;
  */
 interface Representation extends Type, Declaration
 {
-    /**
-     * @return string
-     */
-    public function getResourceClassName(): string;
-
-    /**
-     * @return string
-     */
-    public function getResourceShortName(): string;
-
-    /**
-     * @return bool
-     */
-    public function isAbstractResource(): bool;
 }
diff --git a/src/NamingStrategy.php b/src/NamingStrategy.php
deleted file mode 100644
index 0f8efaec9525a3ff0b7c9a651e402e7e1835a92d..0000000000000000000000000000000000000000
--- a/src/NamingStrategy.php
+++ /dev/null
@@ -1,36 +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;
-
-use Irstea\NgModelGeneratorBundle\Models\PHPClass;
-
-/**
- * Interface NamingStrategy.
- */
-interface NamingStrategy
-{
-    /**
-     * @param ContextInterface $context
-     * @param PHPClass         $resourceClass
-     *
-     * @return mixed
-     */
-    public function getName(ContextInterface $context, PHPClass $resourceClass);
-}
diff --git a/src/OperationMapper.php b/src/OperationMapper.php
index b95f99d9fd5df29610c278081f0c30e53f0cb2a3..90639941f4c44d51c4d54538014c03c6d1e56fde 100644
--- a/src/OperationMapper.php
+++ b/src/OperationMapper.php
@@ -21,37 +21,31 @@ namespace Irstea\NgModelGeneratorBundle;
 
 use Doctrine\Common\Inflector\Inflector;
 use Irstea\NgModelGeneratorBundle\Metadata\OperationMetadata;
+use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata;
+use Irstea\NgModelGeneratorBundle\Models\Types\ArrayType;
 use Irstea\NgModelGeneratorBundle\Models\Types\BuiltinType;
-use Irstea\NgModelGeneratorBundle\Models\Types\Collection;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\InterfaceType;
-use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Object;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
 use Irstea\NgModelGeneratorBundle\Models\Types\Operations\CachedClientCall;
+use Irstea\NgModelGeneratorBundle\Models\Types\Operations\ClientCall;
 use Irstea\NgModelGeneratorBundle\Models\Types\Operations\DirectClientCall;
 use Irstea\NgModelGeneratorBundle\Models\Types\Operations\Operation;
 use Irstea\NgModelGeneratorBundle\Models\Types\Operations\Parameter;
 use Irstea\NgModelGeneratorBundle\Models\Types\Operations\Path;
 use Irstea\NgModelGeneratorBundle\Models\Types\Operations\PathTemplate;
 use Irstea\NgModelGeneratorBundle\Models\Types\Operations\PlainPath;
-use Irstea\NgModelGeneratorBundle\Models\Types\Resources\ConcreteRepresentation;
+use Irstea\NgModelGeneratorBundle\Models\Types\Resources\Collection;
 use Irstea\NgModelGeneratorBundle\Models\Types\Type;
 use Irstea\NgModelGeneratorBundle\Models\Types\Union;
-use Symfony\Component\PropertyInfo\Type as APIType;
 
 /**
  * Class OperationMapper.
  */
 final class OperationMapper
 {
-    /** @var string[] */
-    private const NORMALIZING_METHODS = ['POST', 'PUT', 'PATCH', 'GET'];
-
-    /** @var string[] */
-    private const DENORMALIZING_METHODS = ['POST', 'PUT', 'PATCH'];
-
     /** @var string[][] */
     private const CACHE_DECORATORS = [
-        'item' => [
+        'item'       => [
             'GET'    => ['get(iri, () => %s)', 'iri'],
             'PUT'    => ['put(iri, %s)', 'iri'],
             'DELETE' => ['delete(iri, %s)', 'iri'],
@@ -68,26 +62,16 @@ final class OperationMapper
     /** @var OperationMetadata */
     private $operation;
 
-    /** @var ConcreteRepresentation */
-    private $baseRepr;
-
-    /** @var ContextInterface */
-    private $context;
-
     /**
      * OperationMapper constructor.
      *
-     * @param TypeFactoryInterface   $typeFactory
-     * @param OperationMetadata      $operation
-     * @param ConcreteRepresentation $baseRepr
-     * @param ContextInterface       $context
+     * @param TypeFactoryInterface $typeFactory
+     * @param OperationMetadata    $operation
      */
-    public function __construct(TypeFactoryInterface $typeFactory, OperationMetadata $operation, ConcreteRepresentation $baseRepr, ContextInterface $context)
+    public function __construct(TypeFactoryInterface $typeFactory, OperationMetadata $operation)
     {
         $this->typeFactory = $typeFactory;
         $this->operation = $operation;
-        $this->baseRepr = $baseRepr;
-        $this->context = $context;
     }
 
     /**
@@ -96,101 +80,46 @@ final class OperationMapper
     public function __invoke(): Operation
     {
         $httpMethod = $this->operation->getMethod();
-        $normalize = \in_array($httpMethod, self::NORMALIZING_METHODS, true);
-        $denormalize = \in_array($httpMethod, self::DENORMALIZING_METHODS, true);
+        $normalization = $this->operation->getNormalization();
+        $denormalization = $this->operation->getDenormalization();
 
-        $className = $this->operation->getClassName();
-        $name = Inflector::camelize($this->operation->getName());
-        $collection = $this->operation->isCollectionOperation();
+        $isCollection = $this->operation->isCollectionOperation();
 
-        if ($denormalize && $name === 'post' && $httpMethod === 'POST') {
-            $collection = false;
+        if ($this->operation->getName() === 'post' && $httpMethod === 'POST' && $normalization && $denormalization) {
+            $isCollection = false;
         }
 
-        $pathProperties = $this->baseRepr->getAllProperties();
-
         $requestBody = $responseBody = null;
 
-        $canUseCache = ($httpMethod === 'DELETE');
-
-        if ($normalize) {
-            $factory = $this->typeFactory
-                ->withContext(
-                    $this->context
-                        ->withNormalization(true)
-                        ->withGroups($this->operation->getSerializationGroups(true))
-                );
-
-            $responseBody = $factory->get($className);
+        if ($normalization) {
+            $responseBody = $this->mapSerialization($normalization);
 
-            if ($collection) {
-                $responseBody = $factory->getGenericRef('Collection<T>', [$responseBody]);
+            if ($isCollection) {
+                $responseBody = new Collection($responseBody);
             }
         }
 
-        if ($denormalize) {
-            $requestBody = $this->typeFactory
-                ->withContext(
-                    $this->context
-                        ->withNormalization(false)
-                        ->withGroups($this->operation->getSerializationGroups(false))
-                )
-                ->get($className);
-
-            if ($collection) {
-                $requestBody = new Collection($requestBody);
-            }
-        }
+        if ($denormalization) {
+            $responseBody = $this->mapSerialization($denormalization);
 
-        if ($httpMethod === 'GET' && strpos($name, 'get') === false) {
-            $name = 'get' . ucfirst($name);
-        }
-        if ($collection) {
-            if (\in_array($name, ['get', 'put', 'delete', 'patch'], true)) {
-                $name .= 'All';
-            } else {
-                $name = Inflector::pluralize($name);
+            if ($isCollection) {
+                $requestBody = new ArrayType($requestBody);
             }
-        } else {
-            $name = Inflector::singularize($name);
-        }
-
-        $filters = null;
-        if ($collection && $httpMethod === 'GET') {
-            $filtersName = $this->operation->getResource()->getShortName() . 'Filters';
-
-            $filters = $this->typeFactory->getOrCreate(
-                $filtersName,
-                function () use ($filtersName): Type {
-                    return new InterfaceType(
-                        $filtersName,
-                        $this->typeFactory->get('CommonFilters'),
-                        $this->getFilters()
-                    );
-                }
-            );
         }
 
-        $path = $this->parsePath($pathProperties);
-
-        $clientCall = new DirectClientCall(
-            $httpMethod,
-            $path,
-            $filters,
-            $responseBody,
-            $requestBody
+        $clientCall = $this->applyCache(
+            new DirectClientCall(
+                $httpMethod,
+                $this->parsePath(),
+                $this->getFilters(),
+                $responseBody,
+                $requestBody
+            ),
+            $isCollection
         );
 
-        $opType = $collection ? 'collection' : 'item';
-        if ($canUseCache && isset(self::CACHE_DECORATORS[$opType][$httpMethod])) {
-            [$template, $requiredParam] = self::CACHE_DECORATORS[$opType][$httpMethod];
-            if ($requiredParam === null || $clientCall->hasParameter($requiredParam)) {
-                $clientCall = new CachedClientCall($clientCall, 'this.cache.' . $template);
-            }
-        }
-
         return new Operation(
-            $name,
+            $this->getFancyName($isCollection),
             $clientCall,
             sprintf(
                 "Operation: %s\nType: %s\nMethod: %s\nPath: %s",
@@ -202,12 +131,22 @@ final class OperationMapper
         );
     }
 
-    /**
-     * @param Property[] $properties
+    /***
+     * @param SerializationMetadata $serializationMetadata
      *
+     * @return Type
+     */
+    private function mapSerialization(SerializationMetadata $serializationMetadata): Type
+    {
+        $mapper = new SerializationMapper($this->typeFactory, $serializationMetadata);
+
+        return $mapper->get($serializationMetadata->getRoot()->getFullName());
+    }
+
+    /**
      * @return Path
      */
-    private function parsePath(array $properties): Path
+    private function parsePath(): Path
     {
         $path = $this->operation->getPath();
 
@@ -217,19 +156,18 @@ final class OperationMapper
             return new PlainPath($parts[0]);
         }
 
-        return $this->parsePathTemplate($parts, $properties);
+        return $this->parsePathTemplate($parts);
     }
 
     /**
-     * @param string[]   $parts
-     * @param Property[] $properties
+     * @param string[] $parts
      *
      * @return PathTemplate
      */
-    private function parsePathTemplate(array $parts, array $properties): PathTemplate
+    private function parsePathTemplate(array $parts): PathTemplate
     {
         $requirements = $this->operation->getRequirements();
-        $defaultType = $this->typeFactory->get(APIType::BUILTIN_TYPE_STRING);
+        $type = BuiltinType::get('string');
 
         $template = $pattern = $parameters = [];
 
@@ -250,11 +188,6 @@ final class OperationMapper
                 $pattern[] = '([^\\/]*)';
             }
 
-            $type = $defaultType;
-            if (isset($properties[$name])) {
-                $type = $properties[$name]->getType();
-            }
-
             $parameters[$name] = new Parameter($name, $type);
         }
 
@@ -265,15 +198,90 @@ 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
+    {
+        $decorators = self::CACHE_DECORATORS[$isCollection ? 'collection' : 'item'];
+        $method = $this->operation->getMethod();
+
+        if (!isset($decorators[$method])) {
+            return $clientCall;
+        }
+
+        $normalization = $this->operation->getNormalization();
+        if (!$normalization) {
+            if ($method !== 'DELETE') {
+                return $clientCall;
+            }
+        }
+
+        [$template, $requiredParam] = $decorators[$method];
+        if ($requiredParam !== null && !$clientCall->hasParameter($requiredParam)) {
+            return $clientCall;
+        }
+
+        return new CachedClientCall($clientCall, 'this.cache.' . $template);
+    }
+
+    /**
+     * @return Type | null
+     */
+    private function getFilters(): ?Type
+    {
+        if (!$this->operation->getFilters() && !$this->operation->getPagination()) {
+            return null;
+        }
+        $filtersName = $this->operation->getResource()->getShortName() . 'Filters';
+
+        return $this->typeFactory->getOrCreate(
+            $filtersName,
+            function () use ($filtersName): Type {
+                return new InterfaceType(
+                    $filtersName,
+                    $this->typeFactory->get('CommonFilters'),
+                    $this->getFilterParameters()
+                );
+            }
+        );
+    }
+
     /**
      * @return Property[]
      */
-    private function getFilters(): array
+    private function getFilterParameters(): array
     {
         $parameters = [];
 
         $pagination = $this->operation->getPagination();
-        if ($pagination->isEnabled()) {
+        if ($pagination && $pagination->isEnabled()) {
             $intType = BuiltinType::get('number');
 
             $parameters[$pagination->getPageParameterName()] = $intType;
@@ -294,7 +302,7 @@ final class OperationMapper
 
             if (substr($name, $l - 2) === '[]') {
                 $name = substr($name, 0, $l - 2);
-                $type = new Collection($type);
+                $type = new ArrayType($type);
             }
             $simplerName = Inflector::camelize(preg_replace('/\W+/', '_', $name));
 
diff --git a/src/ResourceTypeFactory.php b/src/ResourceTypeFactory.php
deleted file mode 100644
index 0e569730d2349cf18be2f53c402c51bc6b04fefc..0000000000000000000000000000000000000000
--- a/src/ResourceTypeFactory.php
+++ /dev/null
@@ -1,283 +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;
-
-use Irstea\NgModelGeneratorBundle\Exceptions\DomainException;
-use Irstea\NgModelGeneratorBundle\Metadata\ClassHierarchy;
-use Irstea\NgModelGeneratorBundle\Metadata\MetadataFactoryInterface;
-use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata;
-use Irstea\NgModelGeneratorBundle\Models\PHPClass;
-use Irstea\NgModelGeneratorBundle\Models\Types\Collection;
-use Irstea\NgModelGeneratorBundle\Models\Types\Objects\HierarchicalObject;
-use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
-use Irstea\NgModelGeneratorBundle\Models\Types\Placeholder;
-use Irstea\NgModelGeneratorBundle\Models\Types\Resources\ConcreteRepresentation;
-use Irstea\NgModelGeneratorBundle\Models\Types\Type;
-use Irstea\NgModelGeneratorBundle\Models\Types\Union;
-
-/**
- * Class TypeMapper.
- */
-final class ResourceTypeFactory implements TypeFactoryInterface
-{
-    /** @var MetadataFactoryInterface */
-    private $metadataFactory;
-
-    /** @var TypeFactoryInterface */
-    private $typeFactory;
-
-    /** @var NamingStrategy */
-    private $namingStrategy;
-
-    /** @var ClassHierarchy */
-    private $classHierarchy;
-
-    /** @var ContextInterface|null */
-    private $context;
-
-    /**
-     * TypeMapper constructor.
-     *
-     * @param MetadataFactoryInterface $metadataFactory
-     * @param TypeFactoryInterface     $typeFactory
-     * @param NamingStrategy           $namingStrategy
-     * @param ContextInterface         $context
-     */
-    public function __construct(
-        MetadataFactoryInterface $metadataFactory,
-        TypeFactoryInterface $typeFactory,
-        NamingStrategy $namingStrategy,
-        ClassHierarchy $classHierarchy,
-        ContextInterface $context = null
-    ) {
-        $this->metadataFactory = $metadataFactory;
-        $this->typeFactory = $typeFactory;
-        $this->namingStrategy = $namingStrategy;
-        $this->context = $context;
-        $this->classHierarchy = $classHierarchy;
-    }
-
-    /**
-     * @param ContextInterface|null $context
-     *
-     * @return TypeFactoryInterface
-     */
-    public function withContext(?ContextInterface $context): TypeFactoryInterface
-    {
-        if ($context === $this->context) {
-            return $this;
-        }
-
-        $clone = clone $this;
-        $clone->context = $context;
-        $clone->typeFactory = $clone->typeFactory->withContext($context);
-
-        return $clone;
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function has(string $name): bool
-    {
-        return $this->typeFactory->has($name);
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getOrCreate(string $name, callable $builder, ...$args): Type
-    {
-        return $this->typeFactory->getOrCreate($name, $builder, ...$args);
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function get(string $name): Type
-    {
-        if (!$this->context || !class_exists($name) || $this->typeFactory->has($name)) {
-            return $this->typeFactory->get($name);
-        }
-
-        $class = PHPClass::get($name);
-
-        if ($this->metadataFactory->isResource($class)) {
-            $reprName = $this->namingStrategy->getName($this->context, $class);
-
-            return $this->typeFactory->getOrCreate(
-                $reprName,
-                function () use ($class, $reprName): Type {
-                    return $this->mapRepresentation($class, $reprName);
-                }
-            );
-        }
-
-        return $this->getOrCreate($name, [$this, 'get'], 'any');
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function addBuiltin(string $name): void
-    {
-        $this->typeFactory->addBuiltin($name);
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function addAlias(string $alias, string $target): void
-    {
-        $this->typeFactory->addAlias($alias, $target);
-    }
-
-    /**
-     * @param string   $name
-     * @param PHPClass $resourceClass
-     *
-     * @return Type
-     */
-    private function mapRepresentation(PHPClass $resourceClass, string $name): Type
-    {
-        $resourceMeta = $this->metadataFactory->getResourceMetadata($resourceClass);
-
-        $normalization = $this->context->isNormalization();
-        $groups = $this->context->getGroups();
-
-        $properties = $this->mapProperties($resourceMeta->getProperties());
-
-        $parent = null;
-        $parentClassName = $resourceMeta->getParentClassName();
-        if ($parentClassName) {
-            $parent = $this->get($parentClassName);
-
-            if ($parent instanceof HierarchicalObject) {
-                $properties = \array_diff_key($properties, $parent->getAllProperties());
-            } else {
-                $parent = null;
-            }
-        }
-
-        $desc = explode("\n", $resourceMeta->getDescription());
-        $desc[] = 'Resource: ' . $resourceClass;
-        $desc[] = 'Direction: ' . ($normalization ? 'response' : 'request');
-        $desc[] = sprintf('Serialization groups: %s', $groups ? implode(', ', $groups) : '-');
-        $desc = trim(implode("\n", $desc));
-
-        return new ConcreteRepresentation($resourceMeta, $name, $this->context->isNormalization(), $parent, $properties, $desc);
-    }
-
-    /**
-     * @param PropertyMetadata[] $propertiesMeta
-     *
-     * @return array
-     */
-    private function mapProperties(array $propertiesMeta): array
-    {
-        $properties = [];
-        $identifierCount = 0;
-
-        foreach ($propertiesMeta as $propertyMeta) {
-            if (!$this->acceptProperty($propertyMeta)) {
-                continue;
-            }
-
-            if ($propertyMeta->isIdentifier()) {
-                ++$identifierCount;
-            }
-
-            $property = $this->mapProperty($propertyMeta);
-            $properties[$property->getName()] = $property;
-        }
-
-        if ($identifierCount > 1) {
-            throw new DomainException('Cannot handle resource with composite identifier');
-        }
-
-        return $properties;
-    }
-
-    /**
-     * @param PropertyMetadata $propertyMeta
-     *
-     * @return bool
-     */
-    public function acceptProperty(PropertyMetadata $propertyMeta): bool
-    {
-        if (!$propertyMeta->getType()) {
-            return false;
-        }
-        if ($this->context->isNormalization()) {
-            return $propertyMeta->isReadable();
-        }
-
-        return ($propertyMeta->getClassName() !== $this->context->getRootClass() && $propertyMeta->isIdentifier())
-            || $propertyMeta->isWritable()
-            || $propertyMeta->isInitializable();
-    }
-
-    /**
-     * @param PropertyMetadata $propertyMeta
-     *
-     * @return Property
-     */
-    private function mapProperty(PropertyMetadata $propertyMeta): Property
-    {
-        $typeMeta = $propertyMeta->getType();
-        \assert($typeMeta !== null);
-
-        $collections = [];
-        $leafType = $typeMeta;
-        while ($leafType->getCollectionValueType()) {
-            $collections[] = $leafType->getCollectionKeyType();
-            $leafType = $leafType->getCollectionValueType();
-        }
-
-        if ($propertyMeta->isLink()) {
-            $linkedClassName = $leafType->getClassName();
-            $type = $this->typeFactory->getGenericRef('IRI<T>', [Placeholder::get(FQCN::baseName($linkedClassName))]);
-
-            if ($propertyMeta->isEmbedded()) {
-                $linkedType = $this->get($linkedClassName);
-                if ($this->context->isNormalization()) {
-                    $type = $linkedType;
-                } else {
-                    $type = Union::create([$type, $linkedType]);
-                }
-            }
-        } else {
-            $type = $this->get($leafType->getClassName() ?: $leafType->getBuiltinType());
-        }
-
-        foreach (\array_reverse($collections) as $indexType) {
-            $type = new Collection($type);
-        }
-
-        return new Property(
-            $propertyMeta->getName(),
-            $propertyMeta->getDescription() ?: '',
-            $type,
-            $propertyMeta->isIdentifier(),
-            $propertyMeta->isNullable() || ($propertyMeta->isIdentifier() && !$this->context->isNormalization()),
-            !$propertyMeta->isWritable()
-        );
-    }
-}
diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml
index 1d59f88f98c3bc929ffafbbbdeb7118dea1beb7f..f693d0aae3c671f0ca74e8380c5e8e7dad80b96a 100644
--- a/src/Resources/config/config.xml
+++ b/src/Resources/config/config.xml
@@ -45,7 +45,6 @@
         <service id="irstea_ng_model_generator.model_generator" lazy="true" class="Irstea\NgModelGeneratorBundle\ModelGenerator">
             <argument id="ng_model_generator.metadata.factory" type="service"/>
             <argument id="twig" type="service"/>
-            <argument id="irstea_ng_model_generator.naming_strategy" type="service"/>
         </service>
 
         <service id="irstea_ng_model_generator.documentation" lazy="true" class="ApiPlatform\Core\Documentation\Documentation">
@@ -61,9 +60,6 @@
             <factory service="api_platform.metadata.resource.name_collection_factory" method="create"/>
         </service>
 
-        <service id="irstea_ng_model_generator.naming_strategy" lazy="true" class="Irstea\NgModelGeneratorBundle\DefaultNamingStrategy">
-        </service>
-
     </services>
 
 </container>
diff --git a/src/Resources/views/typescript_models.ts.twig b/src/Resources/views/typescript_models.ts.twig
index 08fdbc276369ecb721b569a99a18e60d5038d721..2623c6a697da31b07b5c96a8b0b6bdf0f5448b37 100644
--- a/src/Resources/views/typescript_models.ts.twig
+++ b/src/Resources/views/typescript_models.ts.twig
@@ -13,10 +13,10 @@ import { Observable } from 'rxjs/Observable';
 /**
  * Union de tous les types de ressources.
  */
-export type Resource = {% for res in resources -%}
-  {%- if not loop.first %} | {% endif -%}
-  {{- res.normalization.usage -}}
-{%- endfor -%}
+{#export type Resource = {% for res in resources -%}#}
+  {#{%- if not loop.first %} | {% endif -%}#}
+  {#{{- res.normalization.usage -}}#}
+{#{%- endfor -%}#}
 ;
 
 /**
diff --git a/src/SerializationMapper.php b/src/SerializationMapper.php
new file mode 100644
index 0000000000000000000000000000000000000000..8aaaf48d9242c150f7ca5e0fd2c640c77146a0e4
--- /dev/null
+++ b/src/SerializationMapper.php
@@ -0,0 +1,359 @@
+<?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;
+
+use Irstea\NgModelGeneratorBundle\Exceptions\DomainException;
+use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata;
+use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata;
+use Irstea\NgModelGeneratorBundle\Models\PHPClass;
+use Irstea\NgModelGeneratorBundle\Models\Types\ArrayType;
+use Irstea\NgModelGeneratorBundle\Models\Types\BuiltinType;
+use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
+use Irstea\NgModelGeneratorBundle\Models\Types\Resources\ConcreteRepresentation;
+use Irstea\NgModelGeneratorBundle\Models\Types\Resources\IRI;
+use Irstea\NgModelGeneratorBundle\Models\Types\Type;
+use Irstea\NgModelGeneratorBundle\Models\Types\Union;
+
+/**
+ * Class SerializationMapper.
+ */
+final class SerializationMapper implements TypeFactoryInterface
+{
+    /** @var TypeFactoryInterface */
+    private $typeFactory;
+
+    /** @var SerializationMetadata */
+    private $serialization;
+
+    /** @var int[] */
+    private $refCounts;
+
+    /** @var PHPClass[] */
+    private $children;
+
+    /**
+     * SerializationMapper constructor.
+     *
+     * @param TypeFactoryInterface  $typeFactory
+     * @param SerializationMetadata $serialization
+     */
+    public function __construct(
+        TypeFactoryInterface $typeFactory,
+        SerializationMetadata $serialization
+    ) {
+        $this->typeFactory = $typeFactory;
+        $this->serialization = $serialization;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has(string $name): bool
+    {
+        return $this->typeFactory->has($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getOrCreate(string $name, callable $builder, ...$args): Type
+    {
+        return $this->typeFactory->getOrCreate($name, $builder, ...$args);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get(string $name): Type
+    {
+        if (!class_exists($name) || $this->typeFactory->has($name)) {
+            return $this->typeFactory->get($name);
+        }
+
+        $class = PHPClass::get($name);
+        if ($this->serialization->hasRepresentationOf($class)) {
+            $reprName = $this->getRepresentationName($class);
+
+            return $this->typeFactory->getOrCreate($reprName, function () use ($class, $reprName) {
+                return $this->mapRepresentation($class, $reprName);
+            });
+        }
+
+        return $this->getOrCreate($name, [$this, 'get'], 'any');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addBuiltin(string $name): void
+    {
+        $this->typeFactory->addBuiltin($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function addAlias(string $alias, string $target): void
+    {
+        $this->typeFactory->addAlias($alias, $target);
+    }
+
+    /**
+     * @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
+     *
+     * @return Type
+     */
+    private function mapRepresentation(PHPClass $resourceClass, string $reprName): Type
+    {
+        /**
+         * @var PHPClass
+         * @var PropertyMetadata[] $propertiesMeta
+         */
+        [, $parentClass, $propertiesMeta] = $this->doMapRepresentation($resourceClass);
+
+        $properties = $this->mapProperties($propertiesMeta);
+        $parent = $parentClass ? $this->get($parentClass->getFullName()) : null;
+
+//        $repr = $this->serialization->getRepresentationOf($resourceClass);
+//
+//        $parent = $repr->getParent() ? $this->get($repr->getParent()->getFullName()) : null;
+//        $properties = $this->mapProperties($repr->getProperties());
+
+        $desc = [];
+        $desc[] = 'Resource: ' . $resourceClass->getFullName();
+//        $desc[] = 'Direction: ' . ($normalization ? 'response' : 'request');
+//        $desc[] = sprintf('Serialization groups: %s', $groups ? implode(', ', $groups) : '-');
+        $desc = trim(implode("\n", $desc));
+
+        return new ConcreteRepresentation($reprName, $parent, $properties, $desc, $this->getCoveredResources($resourceClass), $this->serialization->isNormalization());
+    }
+
+    /**
+     * @param PHPClass $class
+     *
+     * @return array
+     */
+    private function doMapRepresentation(PHPClass $class): array
+    {
+        $repr = $this->serialization->getRepresentationOf($class);
+        $properties = $repr->getProperties();
+
+        $parentClass = $repr->getParent();
+        if ($parentClass) {
+            [$keep, , $parentProperties] = $this->doMapRepresentation($parentClass);
+            if (!$keep) {
+                $parentClass = null;
+                $properties = array_replace($parentProperties, $properties);
+            }
+        }
+
+        if (!$this->serialization->isRoot($class)) {
+            if (!$parentClass && $this->getRefCount($class) < 2) {
+                return [false, null, $properties];
+            }
+        }
+
+        return [true, $parentClass, $properties];
+    }
+
+    /**
+     * @param PropertyMetadata[] $propertiesMeta
+     *
+     * @return array
+     */
+    private function mapProperties(array $propertiesMeta): array
+    {
+        $properties = [];
+        $identifierCount = 0;
+
+        foreach ($propertiesMeta as $propertyMeta) {
+            if (!$this->acceptProperty($propertyMeta)) {
+                continue;
+            }
+
+            if ($propertyMeta->isIdentifier()) {
+                ++$identifierCount;
+            }
+
+            $property = $this->mapProperty($propertyMeta);
+            $properties[$property->getName()] = $property;
+        }
+
+        if ($identifierCount > 1) {
+            throw new DomainException('Cannot handle resource with composite identifier');
+        }
+
+        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
+     *
+     * @return Property
+     */
+    private function mapProperty(PropertyMetadata $propertyMeta): Property
+    {
+        $typeMeta = $propertyMeta->getType();
+        \assert($typeMeta !== null);
+
+        $collections = [];
+        $leafType = $typeMeta;
+        while ($leafType->getCollectionValueType()) {
+            $collections[] = $leafType->getCollectionKeyType();
+            $leafType = $leafType->getCollectionValueType();
+        }
+
+        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]);
+                }
+            }
+        } else {
+            $type = $this->get($leafType->getClassName() ?: $leafType->getBuiltinType());
+        }
+
+        $number = BuiltinType::get('number');
+        foreach (\array_reverse($collections) as $indexType) {
+            if ($indexType === $number) {
+                $type = new ArrayType($type);
+            }
+        }
+
+        return new Property(
+            $propertyMeta->getName(),
+            $propertyMeta->getDescription() ?: '',
+            $type,
+            $propertyMeta->isIdentifier(),
+            $propertyMeta->isNullable() || ($propertyMeta->isIdentifier() && !$this->serialization->isNormalization()),
+            !$propertyMeta->isWritable()
+        );
+    }
+
+    /**
+     * @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
+    {
+        $reprs = $this->serialization->getRepresentations();
+        $this->refCounts = \array_fill_keys(array_keys($reprs), 0);
+
+        foreach ($reprs as $repr) {
+            if ($repr->getParent()) {
+                ++$this->refCounts[$repr->getParent()->getFullName()];
+            }
+            foreach ($repr->getProperties() as $property) {
+                $className = $property->getLeafType()->getClassName();
+                if ($className && isset($this->refCounts[$className])) {
+                    ++$this->refCounts[$className];
+                }
+            }
+        }
+    }
+
+    /**
+     * @param PHPClass|string $class
+     *
+     * @return int
+     */
+    private function getCoveredResources($class): array
+    {
+        $resources = [$class];
+
+        for ($i = 0; $i < \count($resources); ++$i) {
+            $resources = array_merge($resources, $this->getChildren($resources[$i]));
+        }
+
+        return $resources;
+    }
+
+    /**
+     * @param PHPClass|string $class
+     *
+     * @return int
+     */
+    private function getChildren($class): array
+    {
+        if (!$this->children) {
+            $this->initChildren();
+        }
+
+        return $this->children[PHPClass::get($class)->getFullName()] ?? [];
+    }
+
+    private function initChildren(): void
+    {
+        $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();
+            }
+        }
+    }
+}
diff --git a/src/TypeFactoryInterface.php b/src/TypeFactoryInterface.php
index 37a052d1e04df1834b6fbb18c7b0d425740e17ec..760c4f5e237fcad6782200b2ebd7d171bfe310a2 100644
--- a/src/TypeFactoryInterface.php
+++ b/src/TypeFactoryInterface.php
@@ -26,13 +26,6 @@ use Irstea\NgModelGeneratorBundle\Models\Types\Type;
  */
 interface TypeFactoryInterface
 {
-    /**
-     * @param ContextInterface|null $context
-     *
-     * @return TypeFactoryInterface
-     */
-    public function withContext(?ContextInterface $context): self;
-
     /**
      * @param string $name
      *