diff --git a/src/php/ContextCache.php b/src/php/ContextCache.php
new file mode 100644
index 0000000000000000000000000000000000000000..8d84c0f05b17cf73c3f5e6a0fceba44fcd935652
--- /dev/null
+++ b/src/php/ContextCache.php
@@ -0,0 +1,71 @@
+<?php declare(strict_types=1);
+/**
+ * Copyright (C) 2019 IRSTEA
+ * All rights reserved.
+ *
+ * @copyright 2019 IRSTEA
+ * @author guillaume.perreal
+ */
+
+
+namespace Irstea\NgModelGeneratorBundle;
+
+
+use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\ContextInterface;
+
+/**
+ * Class ContextCache
+ */
+final class ContextCache implements ContextFactoryInterface
+{
+    /**
+     * @var ContextFactoryInterface
+     */
+    private $contextFactory;
+
+
+    /** @var ContextInterface[] */
+    private $cache = [];
+
+
+    /**
+     * ContextCache constructor.
+     * @param ContextFactoryInterface $contextFactory
+     */
+    public function __construct(ContextFactoryInterface $contextFactory)
+    {
+
+        $this->contextFactory = $contextFactory;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function create(SerializationMetadata $serialization, bool $withAtFields): ContextInterface
+    {
+        $key = $this->getCacheKey($serialization, $withAtFields);
+
+        if (!isset($this->cache[$key])) {
+            $this->cache[$key] = $this->contextFactory->create($serialization, $withAtFields);
+        }
+
+        return $this->cache[$key];
+    }
+
+    /**
+     * @param SerializationMetadata $serialization
+     * @param bool $withAtFields
+     * @return string
+     */
+    private function getCacheKey(SerializationMetadata $serialization, bool $withAtFields): string
+    {
+        return sprintf(
+            '%s:%s:%s:%s',
+            $serialization->getRoot(),
+            $serialization->isNormalization() ? 'norm' : 'denorm',
+            $withAtFields ? 'with' : 'without',
+            implode(',', $serialization->getGroups())
+        );
+    }
+}
diff --git a/src/php/ContextFactory.php b/src/php/ContextFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..bbefa823f21280c1f934d1456f7ff76ee2a19a45
--- /dev/null
+++ b/src/php/ContextFactory.php
@@ -0,0 +1,89 @@
+<?php declare(strict_types=1);
+/*
+ * This file is part of "irstea/ng-model-generator-bundle".
+ *
+ * "irstea/ng-model-generator-bundle" generates Typescript interfaces for Angular using api-platform metadata.
+ * Copyright (C) 2018-2019 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\Metadata\SerializationMetadata;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\BuiltinTypeFactory;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\CollectionTypeFactory;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\CompositeTypeFactory;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\Context;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\ContextInterface;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\DeferrableTypeFactory;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\NullableTypeFactory;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\ReflectionTypeFactory;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeCache;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeFactoryInterface;
+use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
+
+/**
+ * Class ContextFactory.
+ */
+final class ContextFactory implements ContextFactoryInterface
+{
+    /** @var TypeFactoryInterface */
+    private $nullableTypeFactory;
+
+    /** @var TypeFactoryInterface */
+    private $collectionTypeFactory;
+
+    /** @var TypeFactoryInterface */
+    private $reflectionTypeFactory;
+
+    /** @var TypeFactoryInterface */
+    private $builtinTypeFactory;
+
+    /**
+     * ContextFactory constructor.
+     * @param PropertyInfoExtractor $propertyInfoExtractor
+     */
+    public function __construct(PropertyInfoExtractor $propertyInfoExtractor)
+    {
+        $this->nullableTypeFactory = new NullableTypeFactory();
+        $this->collectionTypeFactory = new CollectionTypeFactory();
+        $this->reflectionTypeFactory = TypeCache::decorate(new ReflectionTypeFactory($propertyInfoExtractor));
+        $this->builtinTypeFactory = BuiltinTypeFactory::createDefault();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function create(SerializationMetadata $serialization, bool $withAtFields): ContextInterface
+    {
+        $serializationMapper = new SerializationMapper($serialization, $withAtFields);
+
+        $typeFactory = TypeCache::decorate(
+            new CompositeTypeFactory([
+                $this->nullableTypeFactory,
+                $this->collectionTypeFactory,
+                DeferrableTypeFactory::decorate(
+                    new CompositeTypeFactory([
+                        $serializationMapper,
+                        $this->reflectionTypeFactory
+                    ])
+                ),
+                $this->builtinTypeFactory
+            ])
+        );
+
+        return new Context($typeFactory);
+    }
+}
diff --git a/src/php/SerializationMapperFactoryInterface.php b/src/php/ContextFactoryInterface.php
similarity index 83%
rename from src/php/SerializationMapperFactoryInterface.php
rename to src/php/ContextFactoryInterface.php
index 3b8362c36143f0c92d6bd4a9990173315a485e4b..6fa9f1959fd747cadc46473a8b588a33733e0560 100644
--- a/src/php/SerializationMapperFactoryInterface.php
+++ b/src/php/ContextFactoryInterface.php
@@ -22,17 +22,18 @@
 namespace Irstea\NgModelGeneratorBundle;
 
 use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\ContextInterface;
 
 /**
- * Class SerializationMapperFactoryInterface.
+ * Class ContextFactoryInterface.
  */
-interface SerializationMapperFactoryInterface
+interface ContextFactoryInterface
 {
     /**
      * @param SerializationMetadata $serialization
-     * @param bool                  $withAtFields
+     * @param bool $withAtFields
      *
-     * @return SerializationMapper
+     * @return ContextInterface
      */
-    public function create(SerializationMetadata $serialization, bool $withAtFields): SerializationMapper;
+    public function create(SerializationMetadata $serialization, bool $withAtFields): ContextInterface;
 }
diff --git a/src/php/ModelGenerator.php b/src/php/ModelGenerator.php
index bc0f02ebfc645dd28d221062f0908bb3861e5a3a..63ac7cee60ce071499d89b68aed6b541c3677b88 100644
--- a/src/php/ModelGenerator.php
+++ b/src/php/ModelGenerator.php
@@ -31,6 +31,7 @@ use Irstea\NgModelGeneratorBundle\Models\ClassName;
 use Irstea\NgModelGeneratorBundle\Models\Declaration;
 use Irstea\NgModelGeneratorBundle\Models\PHPClass;
 use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeFactoryInterface;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeHelper;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Repository;
 use Irstea\NgModelGeneratorBundle\Models\Types\Type;
@@ -51,25 +52,25 @@ final class ModelGenerator
     /** @var Documentation */
     private $documentation;
 
-    /** @var SerializationMapperFactoryInterface */
-    private $serializationMapperFactory;
+    /** @var ContextFactoryInterface */
+    private $contextFactory;
 
     /**
      * Serializer constructor.
      *
      * @param MetadataFactoryInterface            $metadataFactory
-     * @param SerializationMapperFactoryInterface $serializationMapperFactory
+     * @param ContextFactoryInterface $contextFactory
      * @param TypeFactoryInterface                $typeFactory
      * @param Environment                         $twigEnv
      */
     public function __construct(
         MetadataFactoryInterface $metadataFactory,
-        SerializationMapperFactoryInterface $serializationMapperFactory,
+        ContextFactoryInterface $contextFactory,
         Environment $twigEnv
     ) {
         $this->metadataFactory = $metadataFactory;
         $this->twigEnv = $twigEnv;
-        $this->serializationMapperFactory = $serializationMapperFactory;
+        $this->contextFactory = $contextFactory;
     }
 
     /**
@@ -223,14 +224,14 @@ final class ModelGenerator
     {
         $defaultNormalization = $resourceMeta->getDefaultNormalization();
 
-        $defaultNormalizationMapper = $this->serializationMapperFactory->create($defaultNormalization, true);
+        $defaultContext = $this->contextFactory->create($defaultNormalization, true);
 
         /**
          * @var Type
          * @var Property   $identifier
          * @var Property[] $properties
          */
-        [$defaultRepr, $identifier, $properties] = $defaultNormalizationMapper->getResourceData();
+        [$defaultRepr, $identifier, $properties] = $defaultContext->createType(TypeHelper::fromClassName($resourceMeta->getFullName()));
 
         if (!$properties) {
             printf(
@@ -268,7 +269,7 @@ final class ModelGenerator
         }
 
         foreach ($opsMeta as $operation) {
-            $mapper = new OperationMapper($this->typeFactory, $this->serializationMapperFactory, $pathParser, $operation, $iri);
+            $mapper = new OperationMapper($this->contextFactory, $pathParser, $operation, $iri);
             $operations[] = $mapper();
         }
 
diff --git a/src/php/Models/Types/Factory/AbstractTypeFactoryDecorator.php b/src/php/Models/Types/Factory/AbstractTypeFactoryDecorator.php
index 6bd6c84db710fa675774ac395630730c3f570755..98ca281e6bf9955155e42e95bb38deeb9fef3eb7 100644
--- a/src/php/Models/Types/Factory/AbstractTypeFactoryDecorator.php
+++ b/src/php/Models/Types/Factory/AbstractTypeFactoryDecorator.php
@@ -39,7 +39,7 @@ abstract class AbstractTypeFactoryDecorator implements TypeFactoryInterface
      *
      * @param TypeFactoryInterface $decorated
      */
-    public function __construct(TypeFactoryInterface $decorated)
+    protected function __construct(TypeFactoryInterface $decorated)
     {
         $this->decorated = $decorated;
     }
@@ -59,4 +59,13 @@ abstract class AbstractTypeFactoryDecorator implements TypeFactoryInterface
     {
         return $this->decorated->createType($type, $context);
     }
+
+    /**
+     * @param TypeFactoryInterface $decorated
+     * @return TypeFactoryInterface
+     */
+    public static function decorate(TypeFactoryInterface $decorated): TypeFactoryInterface
+    {
+        return new static($decorated);
+    }
 }
diff --git a/src/php/Models/Types/Factory/CollectionTypeFactory.php b/src/php/Models/Types/Factory/CollectionTypeFactory.php
index 7e3ef073c7e8d6b514bc846ba504994c87b2fc39..efac62781466b8a22bff16f5c8b031ba58ffe439 100644
--- a/src/php/Models/Types/Factory/CollectionTypeFactory.php
+++ b/src/php/Models/Types/Factory/CollectionTypeFactory.php
@@ -30,13 +30,20 @@ use Symfony\Component\PropertyInfo\Type as PropertyType;
 /**
  * Class CollectionTypeFactory.
  */
-final class CollectionTypeFactory
+final class CollectionTypeFactory implements TypeFactoryInterface
 {
+
+    /**
+     * {@inheritDoc}
+     */
     public function supportsType(PropertyType $type, ContextInterface $context): bool
     {
         return $type->isCollection();
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public function createType(PropertyType $type, ContextInterface $context): Type
     {
         Assertion::true($this->supportsType($type, $context));
diff --git a/src/php/OperationMapper.php b/src/php/OperationMapper.php
index 30c35ff095b3801ad50027fdf26c1a52f137f2d9..4bef97f987f9cc30b328a96f60e299036f709279 100644
--- a/src/php/OperationMapper.php
+++ b/src/php/OperationMapper.php
@@ -31,6 +31,7 @@ use Irstea\NgModelGeneratorBundle\Models\Types\AbstractCollection;
 use Irstea\NgModelGeneratorBundle\Models\Types\ArrayType;
 use Irstea\NgModelGeneratorBundle\Models\Types\BuiltinType;
 use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeFactoryInterface;
+use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeHelper;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\AnonymousObject;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\InterfaceType;
 use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
@@ -48,17 +49,14 @@ use Irstea\NgModelGeneratorBundle\Models\Types\Union;
  */
 final class OperationMapper
 {
-    /** @var TypeFactoryInterface */
-    private $typeFactory;
-
     /** @var OperationMetadata */
     private $operation;
 
     /** @var PathParserInterface */
     private $pathParser;
 
-    /** @var SerializationMapperFactoryInterface */
-    private $serializationMapperFactory;
+    /** @var ContextFactoryInterface */
+    private $contextFactory;
 
     /** @var Path|null */
     private $iri;
@@ -67,20 +65,18 @@ final class OperationMapper
      * OperationMapper constructor.
      *
      * @param TypeFactoryInterface                $typeFactory
-     * @param SerializationMapperFactoryInterface $serializationMapperFactory
+     * @param ContextFactoryInterface $contextFactory
      * @param PathParserInterface                 $pathParser
      * @param OperationMetadata                   $operation
      * @param Path|null                           $iri
      */
     public function __construct(
-        TypeFactoryInterface $typeFactory,
-        SerializationMapperFactoryInterface $serializationMapperFactory,
+        ContextFactoryInterface $contextFactory,
         PathParserInterface $pathParser,
         OperationMetadata $operation,
         ?Path $iri
     ) {
-        $this->typeFactory = $typeFactory;
-        $this->serializationMapperFactory = $serializationMapperFactory;
+        $this->contextFactory = $contextFactory;
         $this->operation = $operation;
         $this->pathParser = $pathParser;
         $this->iri = $iri;
@@ -139,13 +135,13 @@ final class OperationMapper
             $opParameters[] = new Parameter('body', $requestBody);
             $body[] = 'options.body = body;';
         }
-
-        $filterProperties = $this->getFilterProperties();
-        if ($filterProperties) {
-            [$filterBody, $filterParam] = $this->buildFilterBody($filterProperties, 'options.params');
-            $body = array_merge($body, $filterBody);
-            $opParameters[] = $filterParam;
-        }
+//
+//        $filterProperties = $this->getFilterProperties();
+//        if ($filterProperties) {
+//            [$filterBody, $filterParam] = $this->buildFilterBody($filterProperties, 'options.params');
+//            $body = array_merge($body, $filterBody);
+//            $opParameters[] = $filterParam;
+//        }
 
         $opParameters[] = new Parameter('options', Placeholder::get('RequestOptions'), false, '{}');
         $callParameters[] = 'options';
@@ -177,12 +173,12 @@ final class OperationMapper
      */
     private function mapSerialization(SerializationMetadata $serializationMetadata): Type
     {
-        $mapper = $this->serializationMapperFactory->create(
+        $context = $this->contextFactory->create(
             $serializationMetadata,
             !$this->operation->getOpDef()->isCreateItem()
         );
 
-        return $mapper->create($serializationMetadata->getRoot()->getFullName());
+        return $context->createType(TypeHelper::fromClassName($serializationMetadata->getRoot()->getFullName()));
     }
 
     /**
@@ -222,187 +218,187 @@ final class OperationMapper
 
         return $clientCall;
     }
-
-    /**
-     * @return Property[]
-     */
-    private function getFilterProperties(): array
-    {
-        /** @var Property[] $properties */
-        $properties = [];
-
-        $pagination = $this->operation->getPagination();
-        if ($pagination && $pagination->isEnabled()) {
-            $intType = BuiltinType::get('number');
-
-            $pageName = $pagination->getPageParameterName();
-            $properties[] = new Property($pageName, 'Requested page', $intType, false, true, false, $pageName);
-
-            if ($pagination->isClientItemsPerPage()) {
-                $pageSizeName = $pagination->getItemsPerPageParameterName();
-                $properties[] = new Property($pageSizeName, 'Page size', $intType, false, true, false, $pageSizeName);
-            }
-        }
-
-        $rootClass = $this->operation->getClassName();
-
-        foreach ($this->operation->getFilters() as $filter) {
-            $filterType = PHPClass::get(get_class($filter))->getBaseName();
-
-            foreach ($filter->getDescription($this->operation->getResource()->getFullName()) as $name => $filterDesc) {
-                $type = $this->typeFactory->create($filterDesc['type']);
-
-                if (isset($filterDesc['property']) && is_string($filterDesc['property'])) {
-                    $propType = $this->resolvePropertyType($rootClass, $filterDesc['property']);
-                    if ($propType) {
-                        $type = $propType;
-                    }
-                }
-
-                /** @var string $alphanumName */
-                $alphanumName = preg_replace('/\W+/', '_', $name);
-                $propName = Inflector::camelize($alphanumName);
-
-                $type = $this->resolveFilterType($filterType, $type);
-
-                $type = $this->getSingularType($type);
-                $propName = Inflector::singularize($propName);
-                if (!empty($filterDesc['is_collection']) || substr($name, strlen($name) - 2) === '[]') {
-                    $type = new ArrayType($type);
-                    $propName .= 'In';
-                }
-
-                $properties[] = new Property($propName, '', $type, false, true, false, $name);
-            }
-        }
-
-        /** @var Property[] $propDict */
-        $propDict = [];
-        foreach ($properties as $property) {
-            $name = $property->getName();
-            if (isset($propDict[$name])) {
-                if ($propDict[$name]->getOriginalName() !== $property->getOriginalName()) {
-                    throw new InvalidArgumentException(sprintf("filter property conflict: $name"));
-                }
-                $mergedeType = Union::create([$propDict[$name]->getType(), $property->getType()]);
-                $mergedNullable = $propDict[$name]->isNullable() || $property->isNullable();
-                $propDict[$name] = new Property($name, '', $mergedeType, false, $mergedNullable, false, $property->getOriginalName());
-            } else {
-                $propDict[$name] = $property;
-            }
-        }
-
-        return $propDict;
-    }
-
-    /**
-     * @param string $class
-     * @param string $property
-     *
-     * @return Type|null
-     */
-    private function resolvePropertyType(string $class, string $property): ?Type
-    {
-        /** @var AnonymousObject|null $type */
-        $type = $this->typeFactory->create($class)->findType(AnonymousObject::class);
-        if (!$type || !$type->hasProperty($property)) {
-            return null;
-        }
-
-        return $type->getProperty($property)->getType();
-    }
-
-    /**
-     * @param string $filterType
-     * @param Type   $baseType
-     *
-     * @return Type
-     */
-    private function resolveFilterType(string $filterType, Type $baseType): Type
-    {
-        switch ($filterType) {
-            case 'DateFilter':
-                return BuiltinType::get('Date');
-
-            case 'NumericFilter':
-                return BuiltinType::get('number');
-                break;
-
-            case 'OrderFilter':
-               return $this->typeFactory->create('Ordering');
-
-            case 'BooleanFilter':
-            case 'ExistFilter':
-                return BuiltinType::get('boolean');
-        }
-
-        return $baseType;
-    }
-
-    /**
-     * @param Type $type
-     *
-     * @return Type
-     */
-    private function getSingularType(Type $type): Type
-    {
-        if ($type instanceof AbstractCollection) {
-            return $type->getValueType();
-        }
-        if ($type instanceof Reference) {
-            return $this->getSingularType($type->getTarget());
-        }
-
-        return $type;
-    }
-
-    /**
-     * @param Property[] $filterProperties
-     * @param string     $varName
-     *
-     * @return array
-     */
-    private function buildFilterBody(array $filterProperties, string $varName): array
-    {
-        $filterName = Inflector::classify(
-            $this->operation->getOpDef()->getName()
-            . $this->operation->getResource()->getBaseName()
-            . 'Filters'
-        );
-        $filterType = new InterfaceType(
-            $filterName,
-            null,
-            $filterProperties,
-            sprintf(
-                'Search filter(s) of %sRepository.%s',
-                $this->operation->getResource()->getBaseName(),
-                $this->operation->getName()
-            )
-        );
-        $this->typeFactory->add($filterName, $filterType);
-
-        $body = ["if (filters && typeof filters === 'object') {"];
-        $body[] = "  if (!$varName) {";
-        $body[] = "    $varName = {};";
-        $body[] = '  }';
-        foreach ($filterProperties as $property) {
-            $assignation = sprintf(
-                '  %s = %s;',
-                $property->getUsage($varName, true),
-                $property->getType()->castToStringOrStringArray($property->getUsage('filters'))
-            );
-            if ($property->isNullable()) {
-                $body[] = sprintf(
-                    "  if (%s) {\n  %s\n  }",
-                    TypescriptHelper::propertyTestor('filters', $property->getName()),
-                    $assignation
-                );
-            } else {
-                $body[] = $assignation;
-            }
-        }
-        $body[] = '}';
-
-        return [$body, new Parameter('filters', $filterType, true)];
-    }
+////
+////    /**
+////     * @return Property[]
+////     */
+////    private function getFilterProperties(): array
+////    {
+////        /** @var Property[] $properties */
+////        $properties = [];
+////
+////        $pagination = $this->operation->getPagination();
+////        if ($pagination && $pagination->isEnabled()) {
+////            $intType = BuiltinType::get('number');
+////
+////            $pageName = $pagination->getPageParameterName();
+////            $properties[] = new Property($pageName, 'Requested page', $intType, false, true, false, $pageName);
+////
+////            if ($pagination->isClientItemsPerPage()) {
+////                $pageSizeName = $pagination->getItemsPerPageParameterName();
+////                $properties[] = new Property($pageSizeName, 'Page size', $intType, false, true, false, $pageSizeName);
+////            }
+////        }
+////
+////        $rootClass = $this->operation->getClassName();
+////
+////        foreach ($this->operation->getFilters() as $filter) {
+////            $filterType = PHPClass::get(get_class($filter))->getBaseName();
+////
+////            foreach ($filter->getDescription($this->operation->getResource()->getFullName()) as $name => $filterDesc) {
+////                $type = $this->typeFactory->create($filterDesc['type']);
+////
+////                if (isset($filterDesc['property']) && is_string($filterDesc['property'])) {
+////                    $propType = $this->resolvePropertyType($rootClass, $filterDesc['property']);
+////                    if ($propType) {
+////                        $type = $propType;
+////                    }
+////                }
+////
+////                /** @var string $alphanumName */
+////                $alphanumName = preg_replace('/\W+/', '_', $name);
+////                $propName = Inflector::camelize($alphanumName);
+////
+////                $type = $this->resolveFilterType($filterType, $type);
+////
+////                $type = $this->getSingularType($type);
+////                $propName = Inflector::singularize($propName);
+////                if (!empty($filterDesc['is_collection']) || substr($name, strlen($name) - 2) === '[]') {
+////                    $type = new ArrayType($type);
+////                    $propName .= 'In';
+////                }
+////
+////                $properties[] = new Property($propName, '', $type, false, true, false, $name);
+////            }
+////        }
+////
+////        /** @var Property[] $propDict */
+////        $propDict = [];
+////        foreach ($properties as $property) {
+////            $name = $property->getName();
+////            if (isset($propDict[$name])) {
+////                if ($propDict[$name]->getOriginalName() !== $property->getOriginalName()) {
+////                    throw new InvalidArgumentException(sprintf("filter property conflict: $name"));
+////                }
+////                $mergedeType = Union::create([$propDict[$name]->getType(), $property->getType()]);
+////                $mergedNullable = $propDict[$name]->isNullable() || $property->isNullable();
+////                $propDict[$name] = new Property($name, '', $mergedeType, false, $mergedNullable, false, $property->getOriginalName());
+////            } else {
+////                $propDict[$name] = $property;
+////            }
+////        }
+////
+////        return $propDict;
+////    }
+//
+//    /**
+//     * @param string $class
+//     * @param string $property
+//     *
+//     * @return Type|null
+//     */
+//    private function resolvePropertyType(string $class, string $property): ?Type
+//    {
+//        /** @var AnonymousObject|null $type */
+//        $type = $this->typeFactory->create($class)->findType(AnonymousObject::class);
+//        if (!$type || !$type->hasProperty($property)) {
+//            return null;
+//        }
+//
+//        return $type->getProperty($property)->getType();
+//    }
+//
+//    /**
+//     * @param string $filterType
+//     * @param Type   $baseType
+//     *
+//     * @return Type
+//     */
+//    private function resolveFilterType(string $filterType, Type $baseType): Type
+//    {
+//        switch ($filterType) {
+//            case 'DateFilter':
+//                return BuiltinType::get('Date');
+//
+//            case 'NumericFilter':
+//                return BuiltinType::get('number');
+//                break;
+//
+//            case 'OrderFilter':
+//               return $this->typeFactory->create('Ordering');
+//
+//            case 'BooleanFilter':
+//            case 'ExistFilter':
+//                return BuiltinType::get('boolean');
+//        }
+//
+//        return $baseType;
+//    }
+//
+//    /**
+//     * @param Type $type
+//     *
+//     * @return Type
+//     */
+//    private function getSingularType(Type $type): Type
+//    {
+//        if ($type instanceof AbstractCollection) {
+//            return $type->getValueType();
+//        }
+//        if ($type instanceof Reference) {
+//            return $this->getSingularType($type->getTarget());
+//        }
+//
+//        return $type;
+//    }
+//
+//    /**
+//     * @param Property[] $filterProperties
+//     * @param string     $varName
+//     *
+//     * @return array
+//     */
+//    private function buildFilterBody(array $filterProperties, string $varName): array
+//    {
+//        $filterName = Inflector::classify(
+//            $this->operation->getOpDef()->getName()
+//            . $this->operation->getResource()->getBaseName()
+//            . 'Filters'
+//        );
+//        $filterType = new InterfaceType(
+//            $filterName,
+//            null,
+//            $filterProperties,
+//            sprintf(
+//                'Search filter(s) of %sRepository.%s',
+//                $this->operation->getResource()->getBaseName(),
+//                $this->operation->getName()
+//            )
+//        );
+//        $this->typeFactory->add($filterName, $filterType);
+//
+//        $body = ["if (filters && typeof filters === 'object') {"];
+//        $body[] = "  if (!$varName) {";
+//        $body[] = "    $varName = {};";
+//        $body[] = '  }';
+//        foreach ($filterProperties as $property) {
+//            $assignation = sprintf(
+//                '  %s = %s;',
+//                $property->getUsage($varName, true),
+//                $property->getType()->castToStringOrStringArray($property->getUsage('filters'))
+//            );
+//            if ($property->isNullable()) {
+//                $body[] = sprintf(
+//                    "  if (%s) {\n  %s\n  }",
+//                    TypescriptHelper::propertyTestor('filters', $property->getName()),
+//                    $assignation
+//                );
+//            } else {
+//                $body[] = $assignation;
+//            }
+//        }
+//        $body[] = '}';
+//
+//        return [$body, new Parameter('filters', $filterType, true)];
+//    }
 }
diff --git a/src/php/Resources/config/config.xml b/src/php/Resources/config/config.xml
index ee6e7442d8f1769b2ccec066999e9f626d2d8334..a45b0d974c1ee862e982046f16d5671d7604a36a 100644
--- a/src/php/Resources/config/config.xml
+++ b/src/php/Resources/config/config.xml
@@ -57,13 +57,21 @@
                  lazy="true"
         >
             <argument id="Irstea\NgModelGeneratorBundle\Metadata\MetadataFactory" type="service"/>
-            <argument id="Irstea\NgModelGeneratorBundle\SerializationMapperFactory" type="service"/>
+            <argument id="Irstea\NgModelGeneratorBundle\ContextFactory" type="service"/>
             <argument id="twig" type="service"/>
         </service>
 
-        <service id="Irstea\NgModelGeneratorBundle\SerializationMapperFactory"
+        <service id="Irstea\NgModelGeneratorBundle\ContextFactory"
                  lazy="true"
         >
+            <argument id="property_info" type="service"/>
+        </service>
+
+        <service id="Irstea\NgModelGeneratorBundle\ContextCache"
+                 lazy="true"
+                 decorates="Irstea\NgModelGeneratorBundle\ContextFactory"
+        >
+            <argument id="Irstea\NgModelGeneratorBundle\ContextCache.inner" type="service"/>
         </service>
 
         <service id="ApiPlatform\Core\Documentation\Documentation" lazy="true">
@@ -74,10 +82,6 @@
             <argument>%api_platform.formats%</argument>
         </service>
 
-        <service id="Irstea\NgModelGeneratorBundle\Models\Types\Factory\BuiltinTypeProvider" lazy="true">
-            <argument id="Irstea\NgModelGeneratorBundle\Metadata\MetadataFactory" type="service"/>
-        </service>
-
         <service id="ApiPlatform\Core\Metadata\Resource\ResourceNameCollection" lazy="true" public="false">
             <factory service="api_platform.metadata.resource.name_collection_factory" method="create"/>
         </service>
diff --git a/src/php/SerializationMapperFactory.php b/src/php/SerializationMapperFactory.php
deleted file mode 100644
index 1e78889e84c844630c5d8401ad3a87ac866899a2..0000000000000000000000000000000000000000
--- a/src/php/SerializationMapperFactory.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php declare(strict_types=1);
-/*
- * This file is part of "irstea/ng-model-generator-bundle".
- *
- * "irstea/ng-model-generator-bundle" generates Typescript interfaces for Angular using api-platform metadata.
- * Copyright (C) 2018-2019 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\Metadata\SerializationMetadata;
-
-/**
- * Class SerializationMapperFactory.
- */
-final class SerializationMapperFactory implements SerializationMapperFactoryInterface
-{
-    /** @var SerializationMapper[] */
-    private $cache = [];
-
-    /**
-     * @param SerializationMetadata $serialization
-     * @param bool                  $withAtFields
-     *
-     * @return SerializationMapper
-     */
-    public function create(SerializationMetadata $serialization, bool $withAtFields): SerializationMapper
-    {
-        $key = sprintf(
-            '%s:%s:%s:%s',
-            $serialization->getRoot(),
-            $serialization->isNormalization() ? 'norm' : 'denorm',
-            $withAtFields ? 'with' : 'without',
-            implode(',', $serialization->getGroups())
-        );
-
-        if (!isset($this->cache[$key])) {
-            $this->cache[$key] = new SerializationMapper($serialization, $withAtFields);
-        }
-
-        return $this->cache[$key];
-    }
-}