Repository: lukeautry/tsoa
Pull request #415 (2019-08-14 09:51:56)

ObjectTypeLiteral support
Currently open issue: https://github.com/lukeautry/tsoa/issues/50

R4R

dgreene1: I see that this is still marked as "Draft" Do you want to remove that @WoH or are you still waiting for more feedback from other members or something?
HoldYourWaffle: I'm a little confused (probably due to severe lack of sleep) so sorry if this is a stupid question, but what kind of 'objects' can we use know? Is it an implementation of #353?
WoH: > I'm a little confused (probably due to severe lack of sleep) so sorry if this is a stupid question, but what kind of 'objects' can we use know? Is it an implementation of #353?

No. The primary concern of this PR are type definitions wrapped inside `{}`.
See [here](https://github.com/lukeautry/tsoa/pull/415/files#diff-c5f67349853619c9f3b6b1b7b9bb6df7) for reference:

```ts
interface Model {
  prop: {
    nested: string
  }
}
```

or 
```ts
function(): { value: number } {
  ...
}
```

> I see that this is still marked as "Draft" Do you want to remove that @WoH or are you still waiting for more feedback from other members or something?

Mostly I'm dreading the tests I'll want to write around this. This PR should already cover nested JSDoc annotations and pretty much everything we support on interface declarations. However, that means we want to make sure it does and will not regress at any point in the future.
HoldYourWaffle: > The primary concern of this PR are type definitions wrapped inside {}

Wait how is that different from my implementation of #353 (supporting `refObject`s)? Does this only support 'objects' with a single property?
Again sorry for my stupidity, I just can't get it to click in my head for some reason...
WoH: > Wait how is that different from my implementation of #353 (supporting refObjects)? Does this only support 'objects' with a single property?
Again sorry for my stupidity, I just can't get it to click in my head for some reason...

Ok, so a `refObject` is the internal representation for a named interface, sometimes a type alias in tsoa. Simply called a `referenceType`, so that's the term for this I will use in the context of this explanation. This thing is not supported as QueryParam.

What I'm doing is adding a way to resolve `ObjectLiteralTypes`. That's what would allow you to not create a new interface every time you want to add an arbitrary amount of sub-properties to your property (of a `referenceType`) but to do it the TypeScript way of adding another set of angle brackets and specifying the sub-properties between. These types would then be inlined into the property schema of the `referenceType`
HoldYourWaffle: Oh wait I think I get it, you're adding support for an "anonymous" object, just not in `@Query`.
That makes sense, thank you for making my dumb and tired brain understand.

As a side note, I might post a very work-in-progress PR with my implementation of `refObject` (and I suppose these literals too in the future) in `@Query`. I made this implementation 2 months ago in a really big rush (needed it for a project with a tight deadline) so it's nowhere near a "mergeable" state (I'm not even sure what some parts are doing anymore), but it might be good to get some feedback on the general direction I'm going.
WoH: I'll open this one up, got some (admittedly lazy) JSDoc validations in.
Excess prop handling should be covered in the koa no additional integration spec, but there's probably room for more.
HoldYourWaffle: Just like with #444 we should consider which PR we want to merge first, my cleanup (#445) or this.
(Again assuming my cleanup gets merged)
WoH: Anticipating some of @dgreene1's review in https://github.com/lukeautry/tsoa/pull/415/commits/f3d9fb0f8d6991e864ba52a29cf3a3a3a8d9e532 and https://github.com/lukeautry/tsoa/pull/415/commits/55f9ca79bb1d8f60c52d80f693faf903ca623bf3.

I tried to improve UX for validations, throw if metadata is missing, added an assertion that a nested object value is an object in the first place.

Got around to spec excess prop handling (ignore, remove, throw), see https://github.com/lukeautry/tsoa/pull/415/commits/55f9ca79bb1d8f60c52d80f693faf903ca623bf3

Happy to get reviews at this point.
WoH: Rebased for the green checkmark.
WoH: @dgreene1 I just rebased and realized checks are not running anymore. Is this intended? 
dgreene1: > @dgreene1 I just rebased and realized checks are not running anymore. Is this intended?

I don’t know what’s up with github actions. I just made a change that should hopefully force the tests to be run on each commit.

Patch:
diff --git a/src/metadataGeneration/tsoa.ts b/src/metadataGeneration/tsoa.ts
index a9fbb8c6..31c90c56 100644
--- a/src/metadataGeneration/tsoa.ts
+++ b/src/metadataGeneration/tsoa.ts
@@ -87,12 +87,13 @@ export namespace Tsoa {
     | 'any'
     | 'refEnum'
     | 'refObject'
+    | 'nestedObjectLiteral'
     | 'union'
     | 'intersection';
 
   export type RefTypeLiteral = 'refObject' | 'refEnum';
 
-  export type PrimitiveTypeLiteral = Exclude<TypeStringLiteral, RefTypeLiteral | 'enum' | 'array' | 'void' | 'union' | 'intersection'>;
+  export type PrimitiveTypeLiteral = Exclude<TypeStringLiteral, RefTypeLiteral | 'enum' | 'array' | 'void' | 'nestedObjectLiteral' | 'union' | 'intersection'>;
 
   export interface Type {
     dataType: TypeStringLiteral;
@@ -107,6 +108,12 @@ export namespace Tsoa {
     enums: string[];
   }
 
+  export interface ObjectLiteralType extends Type {
+    dataType: 'nestedObjectLiteral';
+    properties: Property[];
+    additionalProperties?: Type;
+  }
+
   export interface ArrayType extends Type {
     dataType: 'array';
     elementType: Type;
diff --git a/src/metadataGeneration/typeResolver.ts b/src/metadataGeneration/typeResolver.ts
index 6c0d2232..ed0e7004 100644
--- a/src/metadataGeneration/typeResolver.ts
+++ b/src/metadataGeneration/typeResolver.ts
@@ -17,7 +17,7 @@ syntaxKindMap[ts.SyntaxKind.VoidKeyword] = 'void';
 const localReferenceTypeCache: { [typeName: string]: Tsoa.ReferenceType } = {};
 const inProgressTypes: { [typeName: string]: boolean } = {};
 
-type UsableDeclaration = ts.InterfaceDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration;
+type UsableDeclaration = ts.InterfaceDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration | ts.PropertySignature;
 
 export class TypeResolver {
   constructor(private readonly typeNode: ts.TypeNode, private readonly current: MetadataGenerator, private readonly parentNode?: ts.Node, private readonly extractEnum = true) {}
@@ -89,8 +89,43 @@ export class TypeResolver {
       return { dataType: 'any' } as Tsoa.Type;
     }
 
-    if (this.typeNode.kind === ts.SyntaxKind.TypeLiteral) {
-      return { dataType: 'any' } as Tsoa.Type;
+    if (ts.isTypeLiteralNode(this.typeNode)) {
+      const properties = this.typeNode.members
+        .filter(member => ts.isPropertySignature(member))
+        .reduce((res, propertySignature: ts.PropertySignature) => {
+          const type = new TypeResolver(propertySignature.type as ts.TypeNode, this.current, propertySignature).resolve();
+          const property: Tsoa.Property = {
+            default: getJSDocComment(propertySignature, 'default'),
+            description: this.getNodeDescription(propertySignature),
+            format: this.getNodeFormat(propertySignature),
+            name: (propertySignature.name as ts.Identifier).text,
+            required: !propertySignature.questionToken,
+            type,
+            validators: getPropertyValidators(propertySignature) || {},
+          };
+
+          return [property, ...res];
+        }, []);
+
+      const indexMember = this.typeNode.members.find(member => ts.isIndexSignatureDeclaration(member));
+      let additionalType: Tsoa.Type | undefined;
+
+      if (indexMember) {
+        const indexSignatureDeclaration = indexMember as ts.IndexSignatureDeclaration;
+        const indexType = new TypeResolver(indexSignatureDeclaration.parameters[0].type as ts.TypeNode, this.current).resolve();
+        if (indexType.dataType !== 'string') {
+          throw new GenerateMetadataError(`Only string indexers are supported.`);
+        }
+
+        additionalType = new TypeResolver(indexSignatureDeclaration.type as ts.TypeNode, this.current).resolve();
+      }
+
+      const objLiteral: Tsoa.ObjectLiteralType = {
+        additionalProperties: indexMember && additionalType,
+        dataType: 'nestedObjectLiteral',
+        properties,
+      };
+      return objLiteral;
     }
 
     if (this.typeNode.kind === ts.SyntaxKind.ObjectKeyword) {
@@ -492,7 +527,7 @@ export class TypeResolver {
 
       const modelTypeDeclaration = node as UsableDeclaration;
       return (modelTypeDeclaration.name as ts.Identifier).text === typeName;
-    }) as UsableDeclaration[];
+    }) as Array<Exclude<UsableDeclaration, ts.PropertySignature>>;
 
     if (!modelTypes.length) {
       throw new GenerateMetadataError(
@@ -694,7 +729,7 @@ export class TypeResolver {
     return undefined;
   }
 
-  private getModelInheritedProperties(modelTypeDeclaration: UsableDeclaration): Tsoa.Property[] {
+  private getModelInheritedProperties(modelTypeDeclaration: Exclude<UsableDeclaration, ts.PropertySignature>): Tsoa.Property[] {
     const properties = [] as Tsoa.Property[];
     if (modelTypeDeclaration.kind === ts.SyntaxKind.TypeAliasDeclaration) {
       return [];
diff --git a/src/routeGeneration/routeGenerator.ts b/src/routeGeneration/routeGenerator.ts
index 9cd3c150..095e91fd 100644
--- a/src/routeGeneration/routeGenerator.ts
+++ b/src/routeGeneration/routeGenerator.ts
@@ -228,6 +228,16 @@ export class RouteGenerator {
       schema.subSchemas = (type as Tsoa.IntersectionType | Tsoa.UnionType).types.map(type => this.buildProperty(type));
     }
 
+    if (type.dataType === 'nestedObjectLiteral') {
+      const objLiteral = type as Tsoa.ObjectLiteralType;
+
+      schema.nestedProperties = objLiteral.properties.reduce((acc, prop) => {
+        return { ...acc, [prop.name]: this.buildPropertySchema(prop) };
+      }, {});
+
+      schema.additionalProperties = objLiteral.additionalProperties && this.buildProperty(objLiteral.additionalProperties);
+    }
+
     return schema;
   }
 }
diff --git a/src/routeGeneration/templateHelpers.ts b/src/routeGeneration/templateHelpers.ts
index 7002024e..43716343 100644
--- a/src/routeGeneration/templateHelpers.ts
+++ b/src/routeGeneration/templateHelpers.ts
@@ -71,6 +71,8 @@ export class ValidationService {
         return this.validateIntersection(name, value, fieldErrors, minimalSwaggerConfig, property.subSchemas, parent);
       case 'any':
         return value;
+      case 'nestedObjectLiteral':
+        return this.validateNestedObjectLiteral(name, value, fieldErrors, minimalSwaggerConfig, property.nestedProperties, property.additionalProperties, parent);
       default:
         if (property.ref) {
           return this.validateModel({ name, value, refName: property.ref, fieldErrors, parent, minimalSwaggerConfig });
@@ -79,6 +81,65 @@ export class ValidationService {
     }
   }
 
+  public validateNestedObjectLiteral(
+    name: string,
+    value: any,
+    fieldErrors: FieldErrors,
+    swaggerConfig: SwaggerConfigRelatedToRoutes,
+    nestedProperties: { [name: string]: TsoaRoute.PropertySchema } | undefined,
+    additionalProperties: TsoaRoute.PropertySchema | boolean | undefined,
+    parent: string,
+  ) {
+    if (!(value instanceof Object)) {
+      fieldErrors[parent + name] = {
+        message: `invalid object`,
+        value,
+      };
+      return;
+    }
+
+    if (!nestedProperties) {
+      throw new Error(
+        'internal tsoa error: ' +
+          'the metadata that was generated should have had nested property schemas since it’s for a nested object,' +
+          'however it did not. ' +
+          'Please file an issue with tsoa at https://github.com/lukeautry/tsoa/issues',
+      );
+    }
+
+    const propHandling = this.resolveAdditionalPropSetting(swaggerConfig);
+    if (propHandling !== 'ignore') {
+      const excessProps = this.getExcessPropertiesFor({ properties: nestedProperties, additionalProperties }, Object.keys(value), swaggerConfig);
+      if (excessProps.length > 0) {
+        if (propHandling === 'silently-remove-extras') {
+          excessProps.forEach(excessProp => {
+            delete value[excessProp];
+          });
+        }
+        if (propHandling === 'throw-on-extras') {
+          fieldErrors[parent + name] = {
+            message: `"${excessProps}" is an excess property and therefore is not allowed`,
+            value: excessProps.reduce((acc, propName) => ({ [propName]: value[propName], ...acc }), {}),
+          };
+        }
+      }
+    }
+
+    Object.keys(value).forEach(key => {
+      if (!nestedProperties[key]) {
+        if (additionalProperties && additionalProperties !== true) {
+          return this.ValidateParam(additionalProperties, value[key], key, fieldErrors, parent + name + '.', swaggerConfig);
+        } else {
+          return key;
+        }
+      }
+
+      return this.ValidateParam(nestedProperties[key], value[key], key, fieldErrors, parent + name + '.', swaggerConfig);
+    });
+
+    return value;
+  }
+
   public validateInt(name: string, value: any, fieldErrors: FieldErrors, validators?: IntegerValidator, parent = '') {
     if (!validator.isInt(String(value))) {
       let message = `invalid integer number`;
diff --git a/src/routeGeneration/tsoa-route.ts b/src/routeGeneration/tsoa-route.ts
index 8b94986c..c124c0df 100644
--- a/src/routeGeneration/tsoa-route.ts
+++ b/src/routeGeneration/tsoa-route.ts
@@ -30,6 +30,8 @@ export namespace TsoaRoute {
     subSchemas?: PropertySchema[];
     validators?: ValidatorSchema;
     default?: any;
+    additionalProperties?: boolean | PropertySchema;
+    nestedProperties?: { [name: string]: PropertySchema };
   }
 
   export interface ParameterSchema extends PropertySchema {
diff --git a/src/swagger/specGenerator.ts b/src/swagger/specGenerator.ts
index 9fbfdbe6..65175d00 100644
--- a/src/swagger/specGenerator.ts
+++ b/src/swagger/specGenerator.ts
@@ -79,6 +79,8 @@ export abstract class SpecGenerator {
       return this.getSwaggerTypeForUnionType(type as Tsoa.UnionType);
     } else if (type.dataType === 'intersection') {
       return this.getSwaggerTypeForIntersectionType(type as Tsoa.IntersectionType);
+    } else if (type.dataType === 'nestedObjectLiteral') {
+      return this.getSwaggerTypeForObjectLiteral(type as Tsoa.ObjectLiteralType);
     } else {
       return assertNever(type.dataType);
     }
@@ -87,6 +89,24 @@ export abstract class SpecGenerator {
   protected abstract getSwaggerTypeForUnionType(type: Tsoa.UnionType);
 
   protected abstract getSwaggerTypeForIntersectionType(type: Tsoa.IntersectionType);
+  public getSwaggerTypeForObjectLiteral(objectLiteral: Tsoa.ObjectLiteralType): Swagger.Schema {
+    const properties = objectLiteral.properties.reduce((acc, property: Tsoa.Property) => {
+      return {
+        [property.name]: this.getSwaggerType(property.type),
+        ...acc,
+      };
+    }, {});
+
+    const required = objectLiteral.properties.filter(prop => prop.required).map(prop => prop.name);
+
+    // An empty list required: [] is not valid.
+    // If all properties are optional, do not specify the required keyword.
+    return {
+      properties,
+      ...(required && required.length && { required }),
+      type: 'object',
+    };
+  }
 
   protected getSwaggerTypeForReferenceType(referenceType: Tsoa.ReferenceType): Swagger.BaseSchema {
     return {
diff --git a/src/utils/validatorUtils.ts b/src/utils/validatorUtils.ts
index 2db18fdf..ecfb0e51 100644
--- a/src/utils/validatorUtils.ts
+++ b/src/utils/validatorUtils.ts
@@ -108,7 +108,7 @@ export function getParameterValidators(parameter: ts.ParameterDeclaration, param
   );
 }
 
-export function getPropertyValidators(property: ts.PropertyDeclaration): Tsoa.Validators | undefined {
+export function getPropertyValidators(property: ts.PropertyDeclaration | ts.PropertySignature): Tsoa.Validators | undefined {
   const tags = getJSDocTags(property, tag => {
     return getParameterTagSupport().some(value => value === tag.tagName.text);
   });


Test patch:
diff --git a/tests/fixtures/controllers/getController.ts b/tests/fixtures/controllers/getController.ts
index 85f09261..dd38ab38 100644
--- a/tests/fixtures/controllers/getController.ts
+++ b/tests/fixtures/controllers/getController.ts
@@ -24,6 +24,9 @@ export class GetTestController extends Controller {
     modelsArray: new Array<TestSubModel>(),
     numberArray: [1, 2, 3],
     numberValue: 1,
+    objLiteral: {
+      name: 'a string',
+    },
     object: {
       a: 'a',
     },
diff --git a/tests/fixtures/inversify/managedService.ts b/tests/fixtures/inversify/managedService.ts
index 77ffb99f..fcd13ad0 100644
--- a/tests/fixtures/inversify/managedService.ts
+++ b/tests/fixtures/inversify/managedService.ts
@@ -16,6 +16,9 @@ export class ManagedService {
       modelsArray: new Array<TestSubModel>(),
       numberArray: [1, 2, 3],
       numberValue: 1,
+      objLiteral: {
+        name: 'hello',
+      },
       object: {
         a: 'a',
       },
diff --git a/tests/fixtures/services/modelService.ts b/tests/fixtures/services/modelService.ts
index 2ae18c16..3cb1eae8 100644
--- a/tests/fixtures/services/modelService.ts
+++ b/tests/fixtures/services/modelService.ts
@@ -14,6 +14,9 @@ export class ModelService {
       modelsArray: new Array<TestSubModel>(),
       numberArray: [1, 2, 3],
       numberValue: 1,
+      objLiteral: {
+        name: 'hello',
+      },
       object: {
         a: 'a',
       },
diff --git a/tests/fixtures/testModel.ts b/tests/fixtures/testModel.ts
index 9c9c0c93..30e09173 100644
--- a/tests/fixtures/testModel.ts
+++ b/tests/fixtures/testModel.ts
@@ -75,6 +75,21 @@ export interface TestModel extends Model {
   genericNestedArrayKeyword2?: GenericRequest<Array<TypeAliasModel2>>;
   genericNestedArrayCharacter2?: GenericRequest<TypeAliasModel2[]>;
   mixedUnion?: string | TypeAliasModel1;
+
+  objLiteral: {
+    name: string;
+    nested?: {
+      bool: boolean;
+      optional?: number;
+      allNestedOptional: {
+        one?: string;
+        two?: string;
+      };
+      additionals?: {
+        [name: string]: TypeAliasModel1;
+      };
+    };
+  };
 }
 
 export interface TypeAliasModel1 {
@@ -268,6 +283,80 @@ export class ValidateModel {
   public intersection?: TypeAliasModel1 & TypeAliasModel2;
   public intersectionNoAdditional?: TypeAliasModel1 & TypeAliasModel2;
   public mixedUnion?: string | TypeAliasModel1;
+
+  public nestedObject: {
+    /**
+     * @isFloat Invalid float error message.
+     */
+    floatValue: number;
+    /**
+     * @isDouble Invalid double error message.
+     */
+    doubleValue: number;
+    /**
+     * @isInt invalid integer number
+     */
+    intValue: number;
+    /**
+     * @isLong Custom Required long number.
+     */
+    longValue: number;
+    /**
+     * @isBoolean
+     */
+    booleanValue: boolean;
+    /**
+     * @isArray
+     */
+    arrayValue: number[];
+    /**
+     * @isDate invalid ISO 8601 date format, i.e. YYYY-MM-DD
+     */
+    dateValue: Date;
+    /**
+     * @isDateTime
+     */
+    datetimeValue: Date;
+
+    /**
+     * @maximum 10
+     */
+    numberMax10: number;
+    /**
+     * @minimum 5
+     */
+    numberMin5: number;
+    /**
+     * @maxLength 10
+     */
+    stringMax10Lenght: string;
+    /**
+     * @minLength 5
+     */
+    stringMin5Lenght: string;
+    /**
+     *  @pattern ^[a-zA-Z]+$
+     */
+    stringPatternAZaz: string;
+
+    /**
+     * @maxItems 5
+     */
+    arrayMax5Item: number[];
+    /**
+     * @minItems 2
+     */
+    arrayMin2Item: number[];
+    /**
+     * @uniqueItems
+     */
+    arrayUniqueItem: number[];
+
+    model: TypeAliasModel1;
+    intersection?: TypeAliasModel1 & TypeAliasModel2;
+    intersectionNoAdditional?: TypeAliasModel1 & TypeAliasModel2;
+    mixedUnion?: string | TypeAliasModel1;
+  };
 }
 
 export interface ValidateMapStringToNumber {
@@ -333,12 +422,6 @@ export class TestClassModel extends TestClassBaseModel {
   stringProperty: string;
   protected protectedStringProperty: string;
 
-  public static typeLiterals = {
-    booleanTypeLiteral: { $type: Boolean },
-    numberTypeLiteral: { $type: Number },
-    stringTypeLiteral: { $type: String },
-  };
-
   /**
    * @param publicConstructorVar This is a description for publicConstructorVar
    */
diff --git a/tests/integration/dynamic-controllers-express-server.spec.ts b/tests/integration/dynamic-controllers-express-server.spec.ts
index 84657a0c..844c88b4 100644
--- a/tests/integration/dynamic-controllers-express-server.spec.ts
+++ b/tests/integration/dynamic-controllers-express-server.spec.ts
@@ -499,6 +499,31 @@ describe('Express Server', () => {
       bodyModel.arrayUniqueItem = [0, 1, 2, 3];
       bodyModel.model = { value1: 'abcdef' };
       bodyModel.mixedUnion = { value1: '' };
+      bodyModel.intersection = { value1: 'one', value2: 'two' };
+
+      bodyModel.nestedObject = {
+        floatValue: 1.2,
+        doubleValue: 1.2,
+        intValue: 120,
+        longValue: 120,
+        booleanValue: true,
+        arrayValue: [0, 2],
+        dateValue: new Date('2017-01-01'),
+        datetimeValue: new Date('2017-01-01T00:00:00'),
+
+        numberMax10: 10,
+        numberMin5: 5,
+        stringMax10Lenght: 'abcdef',
+        stringMin5Lenght: 'abcdef',
+        stringPatternAZaz: 'aBcD',
+
+        arrayMax5Item: [0, 1, 2, 3],
+        arrayMin2Item: [0, 1],
+        arrayUniqueItem: [0, 1, 2, 3],
+        model: { value1: 'abcdef' },
+        mixedUnion: { value1: '' },
+        intersection: { value1: 'one', value2: 'two' },
+      };
 
       return verifyPostRequest(
         basePath + `/Validate/body`,
@@ -527,6 +552,30 @@ describe('Express Server', () => {
           expect(body.arrayUniqueItem).to.deep.equal(bodyModel.arrayUniqueItem);
           expect(body.model).to.deep.equal(bodyModel.model);
           expect(body.mixedUnion).to.deep.equal(bodyModel.mixedUnion);
+          expect(body.intersection).to.deep.equal(bodyModel.intersection);
+
+          expect(body.nestedObject.floatValue).to.equal(bodyModel.nestedObject.floatValue);
+          expect(body.nestedObject.doubleValue).to.equal(bodyModel.nestedObject.doubleValue);
+          expect(body.nestedObject.intValue).to.equal(bodyModel.nestedObject.intValue);
+          expect(body.nestedObject.longValue).to.equal(bodyModel.nestedObject.longValue);
+          expect(body.nestedObject.booleanValue).to.equal(bodyModel.nestedObject.booleanValue);
+          expect(body.nestedObject.arrayValue).to.deep.equal(bodyModel.nestedObject.arrayValue);
+
+          expect(new Date(body.nestedObject.dateValue)).to.deep.equal(new Date(bodyModel.nestedObject.dateValue));
+          expect(new Date(body.nestedObject.datetimeValue)).to.deep.equal(new Date(bodyModel.nestedObject.datetimeValue));
+
+          expect(body.nestedObject.numberMax10).to.equal(bodyModel.nestedObject.numberMax10);
+          expect(body.nestedObject.numberMin5).to.equal(bodyModel.nestedObject.numberMin5);
+          expect(body.nestedObject.stringMax10Lenght).to.equal(bodyModel.nestedObject.stringMax10Lenght);
+          expect(body.nestedObject.stringMin5Lenght).to.equal(bodyModel.nestedObject.stringMin5Lenght);
+          expect(body.nestedObject.stringPatternAZaz).to.equal(bodyModel.nestedObject.stringPatternAZaz);
+
+          expect(body.nestedObject.arrayMax5Item).to.deep.equal(bodyModel.nestedObject.arrayMax5Item);
+          expect(body.nestedObject.arrayMin2Item).to.deep.equal(bodyModel.nestedObject.arrayMin2Item);
+          expect(body.nestedObject.arrayUniqueItem).to.deep.equal(bodyModel.nestedObject.arrayUniqueItem);
+          expect(body.nestedObject.model).to.deep.equal(bodyModel.nestedObject.model);
+          expect(body.nestedObject.mixedUnion).to.deep.equal(bodyModel.nestedObject.mixedUnion);
+          expect(body.nestedObject.intersection).to.deep.equal(bodyModel.nestedObject.intersection);
         },
         200,
       );
@@ -553,6 +602,30 @@ describe('Express Server', () => {
       bodyModel.arrayUniqueItem = [0, 0, 1, 1];
       bodyModel.model = 1 as any;
       bodyModel.mixedUnion = 123 as any;
+      bodyModel.intersection = { value1: 'one' } as any;
+
+      bodyModel.nestedObject = {
+        floatValue: '120a' as any,
+        doubleValue: '120a' as any,
+        intValue: 1.2,
+        longValue: 1.2,
+        booleanValue: 'abc' as any,
+        dateValue: 'abc' as any,
+        datetimeValue: 'abc' as any,
+
+        numberMax10: 20,
+        numberMin5: 0,
+        stringMax10Lenght: 'abcdefghijk',
+        stringMin5Lenght: 'abcd',
+        stringPatternAZaz: 'ab01234',
+
+        arrayMax5Item: [0, 1, 2, 3, 4, 6, 7, 8, 9],
+        arrayMin2Item: [0],
+        arrayUniqueItem: [0, 0, 1, 1],
+        model: 1 as any,
+        mixedUnion: 123 as any,
+        intersection: { value1: 'one' } as any,
+      } as any;
 
       return verifyPostRequest(
         basePath + `/Validate/body`,
@@ -600,6 +673,51 @@ describe('Express Server', () => {
               'Issues: [{"body.mixedUnion":{"message":"invalid string value","value":123}},' +
               '{"body.mixedUnion":{"message":"invalid object","value":123}}]',
           );
+          expect(body.fields['body.intersection'].message).to.equal('Could not match the intersection against every type. Issues: [{"body.value2":{"message":"\'value2\' is required"}}]');
+
+          expect(body.fields['body.nestedObject.floatValue'].message).to.equal('Invalid float error message.');
+          expect(body.fields['body.nestedObject.floatValue'].value).to.equal(bodyModel.floatValue);
+          expect(body.fields['body.nestedObject.doubleValue'].message).to.equal('Invalid double error message.');
+          expect(body.fields['body.nestedObject.doubleValue'].value).to.equal(bodyModel.doubleValue);
+          expect(body.fields['body.nestedObject.intValue'].message).to.equal('invalid integer number');
+          expect(body.fields['body.nestedObject.intValue'].value).to.equal(bodyModel.intValue);
+          expect(body.fields['body.nestedObject.longValue'].message).to.equal('Custom Required long number.');
+          expect(body.fields['body.nestedObject.longValue'].value).to.equal(bodyModel.longValue);
+          expect(body.fields['body.nestedObject.booleanValue'].message).to.equal('invalid boolean value');
+          expect(body.fields['body.nestedObject.booleanValue'].value).to.equal(bodyModel.booleanValue);
+
+          expect(body.fields['body.nestedObject.dateValue'].message).to.equal('invalid ISO 8601 date format, i.e. YYYY-MM-DD');
+          expect(body.fields['body.nestedObject.dateValue'].value).to.equal(bodyModel.dateValue);
+          expect(body.fields['body.nestedObject.datetimeValue'].message).to.equal('invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss');
+          expect(body.fields['body.nestedObject.datetimeValue'].value).to.equal(bodyModel.datetimeValue);
+
+          expect(body.fields['body.nestedObject.numberMax10'].message).to.equal('max 10');
+          expect(body.fields['body.nestedObject.numberMax10'].value).to.equal(bodyModel.numberMax10);
+          expect(body.fields['body.nestedObject.numberMin5'].message).to.equal('min 5');
+          expect(body.fields['body.nestedObject.numberMin5'].value).to.equal(bodyModel.numberMin5);
+          expect(body.fields['body.nestedObject.stringMax10Lenght'].message).to.equal('maxLength 10');
+          expect(body.fields['body.nestedObject.stringMax10Lenght'].value).to.equal(bodyModel.stringMax10Lenght);
+          expect(body.fields['body.nestedObject.stringMin5Lenght'].message).to.equal('minLength 5');
+          expect(body.fields['body.nestedObject.stringMin5Lenght'].value).to.equal(bodyModel.stringMin5Lenght);
+          expect(body.fields['body.nestedObject.stringPatternAZaz'].message).to.equal("Not match in '^[a-zA-Z]+$'");
+          expect(body.fields['body.nestedObject.stringPatternAZaz'].value).to.equal(bodyModel.stringPatternAZaz);
+
+          expect(body.fields['body.nestedObject.arrayMax5Item'].message).to.equal('maxItems 5');
+          expect(body.fields['body.nestedObject.arrayMax5Item'].value).to.deep.equal(bodyModel.arrayMax5Item);
+          expect(body.fields['body.nestedObject.arrayMin2Item'].message).to.equal('minItems 2');
+          expect(body.fields['body.nestedObject.arrayMin2Item'].value).to.deep.equal(bodyModel.arrayMin2Item);
+          expect(body.fields['body.nestedObject.arrayUniqueItem'].message).to.equal('required unique array');
+          expect(body.fields['body.nestedObject.arrayUniqueItem'].value).to.deep.equal(bodyModel.arrayUniqueItem);
+          expect(body.fields['body.nestedObject.model'].message).to.equal('invalid object');
+          expect(body.fields['body.nestedObject.model'].value).to.deep.equal(bodyModel.model);
+          expect(body.fields['body.nestedObject.mixedUnion'].message).to.equal(
+            'Could not match the union against any of the items. ' +
+              'Issues: [{"body.nestedObject.mixedUnion":{"message":"invalid string value","value":123}},' +
+              '{"body.nestedObject.mixedUnion":{"message":"invalid object","value":123}}]',
+          );
+          expect(body.fields['body.nestedObject.intersection'].message).to.equal(
+            'Could not match the intersection against every type. Issues: [{"body.nestedObject.value2":{"message":"\'value2\' is required"}}]',
+          );
         },
         400,
       );
@@ -937,6 +1055,9 @@ describe('Express Server', () => {
       },
       numberArray: [1, 2],
       numberValue: 5,
+      objLiteral: {
+        name: 'hello',
+      },
       object: { foo: 'bar' },
       objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
       optionalString: 'test1234',
diff --git a/tests/integration/express-server.spec.ts b/tests/integration/express-server.spec.ts
index e3219e85..1d1de360 100644
--- a/tests/integration/express-server.spec.ts
+++ b/tests/integration/express-server.spec.ts
@@ -132,6 +132,26 @@ describe('Express Server', () => {
     });
   });
 
+  it('removes additional properties', () => {
+    const model = getFakeModel();
+    const data = {
+      ...model,
+      objLiteral: {
+        ...model.objLiteral,
+        extra: 123,
+        nested: {
+          anotherExtra: 123,
+        },
+      },
+    };
+
+    return verifyPostRequest(basePath + '/PostTest', data, (err: any, res: any) => {
+      const resModel = res.body as TestModel;
+      expect(resModel).to.deep.equal({ ...model, objLiteral: { ...model.objLiteral, nested: {} } });
+      expect(res.status).to.eq(200);
+    });
+  });
+
   it('correctly returns status code', () => {
     const data = getFakeModel();
     const path = basePath + '/PostTest/WithDifferentReturnCode';
@@ -499,6 +519,31 @@ describe('Express Server', () => {
       bodyModel.arrayUniqueItem = [0, 1, 2, 3];
       bodyModel.model = { value1: 'abcdef' };
       bodyModel.mixedUnion = { value1: '' };
+      bodyModel.intersection = { value1: 'one', value2: 'two' };
+
+      bodyModel.nestedObject = {
+        floatValue: 1.2,
+        doubleValue: 1.2,
+        intValue: 120,
+        longValue: 120,
+        booleanValue: true,
+        arrayValue: [0, 2],
+        dateValue: new Date('2017-01-01'),
+        datetimeValue: new Date('2017-01-01T00:00:00'),
+
+        numberMax10: 10,
+        numberMin5: 5,
+        stringMax10Lenght: 'abcdef',
+        stringMin5Lenght: 'abcdef',
+        stringPatternAZaz: 'aBcD',
+
+        arrayMax5Item: [0, 1, 2, 3],
+        arrayMin2Item: [0, 1],
+        arrayUniqueItem: [0, 1, 2, 3],
+        model: { value1: 'abcdef' },
+        mixedUnion: { value1: '' },
+        intersection: { value1: 'one', value2: 'two' },
+      };
 
       return verifyPostRequest(
         basePath + `/Validate/body`,
@@ -527,6 +572,30 @@ describe('Express Server', () => {
           expect(body.arrayUniqueItem).to.deep.equal(bodyModel.arrayUniqueItem);
           expect(body.model).to.deep.equal(bodyModel.model);
           expect(body.mixedUnion).to.deep.equal(bodyModel.mixedUnion);
+          expect(body.intersection).to.deep.equal(bodyModel.intersection);
+
+          expect(body.nestedObject.floatValue).to.equal(bodyModel.nestedObject.floatValue);
+          expect(body.nestedObject.doubleValue).to.equal(bodyModel.nestedObject.doubleValue);
+          expect(body.nestedObject.intValue).to.equal(bodyModel.nestedObject.intValue);
+          expect(body.nestedObject.longValue).to.equal(bodyModel.nestedObject.longValue);
+          expect(body.nestedObject.booleanValue).to.equal(bodyModel.nestedObject.booleanValue);
+          expect(body.nestedObject.arrayValue).to.deep.equal(bodyModel.nestedObject.arrayValue);
+
+          expect(new Date(body.nestedObject.dateValue)).to.deep.equal(new Date(bodyModel.nestedObject.dateValue));
+          expect(new Date(body.nestedObject.datetimeValue)).to.deep.equal(new Date(bodyModel.nestedObject.datetimeValue));
+
+          expect(body.nestedObject.numberMax10).to.equal(bodyModel.nestedObject.numberMax10);
+          expect(body.nestedObject.numberMin5).to.equal(bodyModel.nestedObject.numberMin5);
+          expect(body.nestedObject.stringMax10Lenght).to.equal(bodyModel.nestedObject.stringMax10Lenght);
+          expect(body.nestedObject.stringMin5Lenght).to.equal(bodyModel.nestedObject.stringMin5Lenght);
+          expect(body.nestedObject.stringPatternAZaz).to.equal(bodyModel.nestedObject.stringPatternAZaz);
+
+          expect(body.nestedObject.arrayMax5Item).to.deep.equal(bodyModel.nestedObject.arrayMax5Item);
+          expect(body.nestedObject.arrayMin2Item).to.deep.equal(bodyModel.nestedObject.arrayMin2Item);
+          expect(body.nestedObject.arrayUniqueItem).to.deep.equal(bodyModel.nestedObject.arrayUniqueItem);
+          expect(body.nestedObject.model).to.deep.equal(bodyModel.nestedObject.model);
+          expect(body.nestedObject.mixedUnion).to.deep.equal(bodyModel.nestedObject.mixedUnion);
+          expect(body.nestedObject.intersection).to.deep.equal(bodyModel.nestedObject.intersection);
         },
         200,
       );
@@ -553,6 +622,30 @@ describe('Express Server', () => {
       bodyModel.arrayUniqueItem = [0, 0, 1, 1];
       bodyModel.model = 1 as any;
       bodyModel.mixedUnion = 123 as any;
+      bodyModel.intersection = { value1: 'one' } as any;
+
+      bodyModel.nestedObject = {
+        floatValue: '120a' as any,
+        doubleValue: '120a' as any,
+        intValue: 1.2,
+        longValue: 1.2,
+        booleanValue: 'abc' as any,
+        dateValue: 'abc' as any,
+        datetimeValue: 'abc' as any,
+
+        numberMax10: 20,
+        numberMin5: 0,
+        stringMax10Lenght: 'abcdefghijk',
+        stringMin5Lenght: 'abcd',
+        stringPatternAZaz: 'ab01234',
+
+        arrayMax5Item: [0, 1, 2, 3, 4, 6, 7, 8, 9],
+        arrayMin2Item: [0],
+        arrayUniqueItem: [0, 0, 1, 1],
+        model: 1 as any,
+        mixedUnion: 123 as any,
+        intersection: { value1: 'one' } as any,
+      } as any;
 
       return verifyPostRequest(
         basePath + `/Validate/body`,
@@ -600,6 +693,51 @@ describe('Express Server', () => {
               'Issues: [{"body.mixedUnion":{"message":"invalid string value","value":123}},' +
               '{"body.mixedUnion":{"message":"invalid object","value":123}}]',
           );
+          expect(body.fields['body.intersection'].message).to.equal('Could not match the intersection against every type. Issues: [{"body.value2":{"message":"\'value2\' is required"}}]');
+
+          expect(body.fields['body.nestedObject.floatValue'].message).to.equal('Invalid float error message.');
+          expect(body.fields['body.nestedObject.floatValue'].value).to.equal(bodyModel.floatValue);
+          expect(body.fields['body.nestedObject.doubleValue'].message).to.equal('Invalid double error message.');
+          expect(body.fields['body.nestedObject.doubleValue'].value).to.equal(bodyModel.doubleValue);
+          expect(body.fields['body.nestedObject.intValue'].message).to.equal('invalid integer number');
+          expect(body.fields['body.nestedObject.intValue'].value).to.equal(bodyModel.intValue);
+          expect(body.fields['body.nestedObject.longValue'].message).to.equal('Custom Required long number.');
+          expect(body.fields['body.nestedObject.longValue'].value).to.equal(bodyModel.longValue);
+          expect(body.fields['body.nestedObject.booleanValue'].message).to.equal('invalid boolean value');
+          expect(body.fields['body.nestedObject.booleanValue'].value).to.equal(bodyModel.booleanValue);
+
+          expect(body.fields['body.nestedObject.dateValue'].message).to.equal('invalid ISO 8601 date format, i.e. YYYY-MM-DD');
+          expect(body.fields['body.nestedObject.dateValue'].value).to.equal(bodyModel.dateValue);
+          expect(body.fields['body.nestedObject.datetimeValue'].message).to.equal('invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss');
+          expect(body.fields['body.nestedObject.datetimeValue'].value).to.equal(bodyModel.datetimeValue);
+
+          expect(body.fields['body.nestedObject.numberMax10'].message).to.equal('max 10');
+          expect(body.fields['body.nestedObject.numberMax10'].value).to.equal(bodyModel.numberMax10);
+          expect(body.fields['body.nestedObject.numberMin5'].message).to.equal('min 5');
+          expect(body.fields['body.nestedObject.numberMin5'].value).to.equal(bodyModel.numberMin5);
+          expect(body.fields['body.nestedObject.stringMax10Lenght'].message).to.equal('maxLength 10');
+          expect(body.fields['body.nestedObject.stringMax10Lenght'].value).to.equal(bodyModel.stringMax10Lenght);
+          expect(body.fields['body.nestedObject.stringMin5Lenght'].message).to.equal('minLength 5');
+          expect(body.fields['body.nestedObject.stringMin5Lenght'].value).to.equal(bodyModel.stringMin5Lenght);
+          expect(body.fields['body.nestedObject.stringPatternAZaz'].message).to.equal("Not match in '^[a-zA-Z]+$'");
+          expect(body.fields['body.nestedObject.stringPatternAZaz'].value).to.equal(bodyModel.stringPatternAZaz);
+
+          expect(body.fields['body.nestedObject.arrayMax5Item'].message).to.equal('maxItems 5');
+          expect(body.fields['body.nestedObject.arrayMax5Item'].value).to.deep.equal(bodyModel.arrayMax5Item);
+          expect(body.fields['body.nestedObject.arrayMin2Item'].message).to.equal('minItems 2');
+          expect(body.fields['body.nestedObject.arrayMin2Item'].value).to.deep.equal(bodyModel.arrayMin2Item);
+          expect(body.fields['body.nestedObject.arrayUniqueItem'].message).to.equal('required unique array');
+          expect(body.fields['body.nestedObject.arrayUniqueItem'].value).to.deep.equal(bodyModel.arrayUniqueItem);
+          expect(body.fields['body.nestedObject.model'].message).to.equal('invalid object');
+          expect(body.fields['body.nestedObject.model'].value).to.deep.equal(bodyModel.model);
+          expect(body.fields['body.nestedObject.mixedUnion'].message).to.equal(
+            'Could not match the union against any of the items. ' +
+              'Issues: [{"body.nestedObject.mixedUnion":{"message":"invalid string value","value":123}},' +
+              '{"body.nestedObject.mixedUnion":{"message":"invalid object","value":123}}]',
+          );
+          expect(body.fields['body.nestedObject.intersection'].message).to.equal(
+            'Could not match the intersection against every type. Issues: [{"body.nestedObject.value2":{"message":"\'value2\' is required"}}]',
+          );
         },
         400,
       );
@@ -937,6 +1075,9 @@ describe('Express Server', () => {
       },
       numberArray: [1, 2],
       numberValue: 5,
+      objLiteral: {
+        name: 'hello',
+      },
       object: { foo: 'bar' },
       objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
       optionalString: 'test1234',
diff --git a/tests/integration/hapi-server.spec.ts b/tests/integration/hapi-server.spec.ts
index 25e409e9..052edb0e 100644
--- a/tests/integration/hapi-server.spec.ts
+++ b/tests/integration/hapi-server.spec.ts
@@ -472,6 +472,31 @@ describe('Hapi Server', () => {
       bodyModel.arrayUniqueItem = [0, 1, 2, 3];
       bodyModel.model = { value1: 'abcdef' };
       bodyModel.mixedUnion = { value1: '' };
+      bodyModel.intersection = { value1: 'one', value2: 'two' };
+
+      bodyModel.nestedObject = {
+        floatValue: 1.2,
+        doubleValue: 1.2,
+        intValue: 120,
+        longValue: 120,
+        booleanValue: true,
+        arrayValue: [0, 2],
+        dateValue: new Date('2017-01-01'),
+        datetimeValue: new Date('2017-01-01T00:00:00'),
+
+        numberMax10: 10,
+        numberMin5: 5,
+        stringMax10Lenght: 'abcdef',
+        stringMin5Lenght: 'abcdef',
+        stringPatternAZaz: 'aBcD',
+
+        arrayMax5Item: [0, 1, 2, 3],
+        arrayMin2Item: [0, 1],
+        arrayUniqueItem: [0, 1, 2, 3],
+        model: { value1: 'abcdef' },
+        mixedUnion: { value1: '' },
+        intersection: { value1: 'one', value2: 'two' },
+      };
 
       return verifyPostRequest(
         basePath + `/Validate/body`,
@@ -500,6 +525,30 @@ describe('Hapi Server', () => {
           expect(body.arrayUniqueItem).to.deep.equal(bodyModel.arrayUniqueItem);
           expect(body.model).to.deep.equal(bodyModel.model);
           expect(body.mixedUnion).to.deep.equal(bodyModel.mixedUnion);
+          expect(body.intersection).to.deep.equal(bodyModel.intersection);
+
+          expect(body.nestedObject.floatValue).to.equal(bodyModel.nestedObject.floatValue);
+          expect(body.nestedObject.doubleValue).to.equal(bodyModel.nestedObject.doubleValue);
+          expect(body.nestedObject.intValue).to.equal(bodyModel.nestedObject.intValue);
+          expect(body.nestedObject.longValue).to.equal(bodyModel.nestedObject.longValue);
+          expect(body.nestedObject.booleanValue).to.equal(bodyModel.nestedObject.booleanValue);
+          expect(body.nestedObject.arrayValue).to.deep.equal(bodyModel.nestedObject.arrayValue);
+
+          expect(new Date(body.nestedObject.dateValue)).to.deep.equal(new Date(bodyModel.nestedObject.dateValue));
+          expect(new Date(body.nestedObject.datetimeValue)).to.deep.equal(new Date(bodyModel.nestedObject.datetimeValue));
+
+          expect(body.nestedObject.numberMax10).to.equal(bodyModel.nestedObject.numberMax10);
+          expect(body.nestedObject.numberMin5).to.equal(bodyModel.nestedObject.numberMin5);
+          expect(body.nestedObject.stringMax10Lenght).to.equal(bodyModel.nestedObject.stringMax10Lenght);
+          expect(body.nestedObject.stringMin5Lenght).to.equal(bodyModel.nestedObject.stringMin5Lenght);
+          expect(body.nestedObject.stringPatternAZaz).to.equal(bodyModel.nestedObject.stringPatternAZaz);
+
+          expect(body.nestedObject.arrayMax5Item).to.deep.equal(bodyModel.nestedObject.arrayMax5Item);
+          expect(body.nestedObject.arrayMin2Item).to.deep.equal(bodyModel.nestedObject.arrayMin2Item);
+          expect(body.nestedObject.arrayUniqueItem).to.deep.equal(bodyModel.nestedObject.arrayUniqueItem);
+          expect(body.nestedObject.model).to.deep.equal(bodyModel.nestedObject.model);
+          expect(body.nestedObject.mixedUnion).to.deep.equal(bodyModel.nestedObject.mixedUnion);
+          expect(body.nestedObject.intersection).to.deep.equal(bodyModel.nestedObject.intersection);
         },
         200,
       );
@@ -526,6 +575,30 @@ describe('Hapi Server', () => {
       bodyModel.arrayUniqueItem = [0, 0, 1, 1];
       bodyModel.model = 1 as any;
       bodyModel.mixedUnion = 123 as any;
+      bodyModel.intersection = { value1: 'one' } as any;
+
+      bodyModel.nestedObject = {
+        floatValue: '120a' as any,
+        doubleValue: '120a' as any,
+        intValue: 1.2,
+        longValue: 1.2,
+        booleanValue: 'abc' as any,
+        dateValue: 'abc' as any,
+        datetimeValue: 'abc' as any,
+
+        numberMax10: 20,
+        numberMin5: 0,
+        stringMax10Lenght: 'abcdefghijk',
+        stringMin5Lenght: 'abcd',
+        stringPatternAZaz: 'ab01234',
+
+        arrayMax5Item: [0, 1, 2, 3, 4, 6, 7, 8, 9],
+        arrayMin2Item: [0],
+        arrayUniqueItem: [0, 0, 1, 1],
+        model: 1 as any,
+        mixedUnion: 123 as any,
+        intersection: { value1: 'one' } as any,
+      } as any;
 
       return verifyPostRequest(
         basePath + `/Validate/body`,
@@ -573,6 +646,51 @@ describe('Hapi Server', () => {
               'Issues: [{"body.mixedUnion":{"message":"invalid string value","value":123}},' +
               '{"body.mixedUnion":{"message":"invalid object","value":123}}]',
           );
+          expect(body.fields['body.intersection'].message).to.equal('Could not match the intersection against every type. Issues: [{"body.value2":{"message":"\'value2\' is required"}}]');
+
+          expect(body.fields['body.nestedObject.floatValue'].message).to.equal('Invalid float error message.');
+          expect(body.fields['body.nestedObject.floatValue'].value).to.equal(bodyModel.floatValue);
+          expect(body.fields['body.nestedObject.doubleValue'].message).to.equal('Invalid double error message.');
+          expect(body.fields['body.nestedObject.doubleValue'].value).to.equal(bodyModel.doubleValue);
+          expect(body.fields['body.nestedObject.intValue'].message).to.equal('invalid integer number');
+          expect(body.fields['body.nestedObject.intValue'].value).to.equal(bodyModel.intValue);
+          expect(body.fields['body.nestedObject.longValue'].message).to.equal('Custom Required long number.');
+          expect(body.fields['body.nestedObject.longValue'].value).to.equal(bodyModel.longValue);
+          expect(body.fields['body.nestedObject.booleanValue'].message).to.equal('invalid boolean value');
+          expect(body.fields['body.nestedObject.booleanValue'].value).to.equal(bodyModel.booleanValue);
+
+          expect(body.fields['body.nestedObject.dateValue'].message).to.equal('invalid ISO 8601 date format, i.e. YYYY-MM-DD');
+          expect(body.fields['body.nestedObject.dateValue'].value).to.equal(bodyModel.dateValue);
+          expect(body.fields['body.nestedObject.datetimeValue'].message).to.equal('invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss');
+          expect(body.fields['body.nestedObject.datetimeValue'].value).to.equal(bodyModel.datetimeValue);
+
+          expect(body.fields['body.nestedObject.numberMax10'].message).to.equal('max 10');
+          expect(body.fields['body.nestedObject.numberMax10'].value).to.equal(bodyModel.numberMax10);
+          expect(body.fields['body.nestedObject.numberMin5'].message).to.equal('min 5');
+          expect(body.fields['body.nestedObject.numberMin5'].value).to.equal(bodyModel.numberMin5);
+          expect(body.fields['body.nestedObject.stringMax10Lenght'].message).to.equal('maxLength 10');
+          expect(body.fields['body.nestedObject.stringMax10Lenght'].value).to.equal(bodyModel.stringMax10Lenght);
+          expect(body.fields['body.nestedObject.stringMin5Lenght'].message).to.equal('minLength 5');
+          expect(body.fields['body.nestedObject.stringMin5Lenght'].value).to.equal(bodyModel.stringMin5Lenght);
+          expect(body.fields['body.nestedObject.stringPatternAZaz'].message).to.equal("Not match in '^[a-zA-Z]+$'");
+          expect(body.fields['body.nestedObject.stringPatternAZaz'].value).to.equal(bodyModel.stringPatternAZaz);
+
+          expect(body.fields['body.nestedObject.arrayMax5Item'].message).to.equal('maxItems 5');
+          expect(body.fields['body.nestedObject.arrayMax5Item'].value).to.deep.equal(bodyModel.arrayMax5Item);
+          expect(body.fields['body.nestedObject.arrayMin2Item'].message).to.equal('minItems 2');
+          expect(body.fields['body.nestedObject.arrayMin2Item'].value).to.deep.equal(bodyModel.arrayMin2Item);
+          expect(body.fields['body.nestedObject.arrayUniqueItem'].message).to.equal('required unique array');
+          expect(body.fields['body.nestedObject.arrayUniqueItem'].value).to.deep.equal(bodyModel.arrayUniqueItem);
+          expect(body.fields['body.nestedObject.model'].message).to.equal('invalid object');
+          expect(body.fields['body.nestedObject.model'].value).to.deep.equal(bodyModel.model);
+          expect(body.fields['body.nestedObject.mixedUnion'].message).to.equal(
+            'Could not match the union against any of the items. ' +
+              'Issues: [{"body.nestedObject.mixedUnion":{"message":"invalid string value","value":123}},' +
+              '{"body.nestedObject.mixedUnion":{"message":"invalid object","value":123}}]',
+          );
+          expect(body.fields['body.nestedObject.intersection'].message).to.equal(
+            'Could not match the intersection against every type. Issues: [{"body.nestedObject.value2":{"message":"\'value2\' is required"}}]',
+          );
         },
         400,
       );
@@ -866,6 +984,9 @@ describe('Hapi Server', () => {
       },
       numberArray: [1, 2],
       numberValue: 5,
+      objLiteral: {
+        name: 'hello',
+      },
       object: { foo: 'bar' },
       objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
       optionalString: 'test1234',
diff --git a/tests/integration/inversify-server.spec.ts b/tests/integration/inversify-server.spec.ts
index 3360559a..38b0f5b8 100644
--- a/tests/integration/inversify-server.spec.ts
+++ b/tests/integration/inversify-server.spec.ts
@@ -41,6 +41,9 @@ describe('Inversify Express Server', () => {
         },
         numberArray: [1, 2, 3],
         numberValue: 1,
+        objLiteral: {
+          name: 'hello',
+        },
         object: {
           a: 'a',
         },
diff --git a/tests/integration/koa-server-no-additional-allowed.spec.ts b/tests/integration/koa-server-no-additional-allowed.spec.ts
index 4aeecde5..7eefc78d 100644
--- a/tests/integration/koa-server-no-additional-allowed.spec.ts
+++ b/tests/integration/koa-server-no-additional-allowed.spec.ts
@@ -48,6 +48,51 @@ describe('Koa Server (with noImplicitAdditionalProperties turned on)', () => {
     );
   });
 
+  it('should call out any additionalProperties', () => {
+    const data = Object.assign({}, getFakeModel(), {
+      objLiteral: {
+        extra: 123,
+        nested: {
+          anotherExtra: 123,
+        },
+      },
+    });
+
+    return verifyPostRequest(
+      basePath + '/PostTest',
+      data,
+      (err: any, res: any) => {
+        const body = JSON.parse(err.text);
+        expect(body.fields['model.objLiteral'].message).to.eql('"extra" is an excess property and therefore is not allowed');
+        expect(body.fields['model.objLiteral.nested'].message).to.eql('"anotherExtra" is an excess property and therefore is not allowed');
+      },
+      400,
+    );
+  });
+
+  it('should respect additional props', () => {
+    const fakeModel = getFakeModel();
+    const data = {
+      ...fakeModel,
+      objLiteral: {
+        name: 'hello',
+        nested: {
+          additionals: {
+            one: { value1: '' },
+          },
+          allNestedOptional: {},
+          bool: true,
+        },
+      },
+    } as TestModel;
+
+    return verifyPostRequest(basePath + '/PostTest', data, (err: any, res: any) => {
+      expect(err).to.equal(false);
+      const model = res.body as TestModel;
+      expect(model).to.deep.equal(data);
+    });
+  });
+
   it('should be okay if there are no additionalProperties', () => {
     const data = getFakeModel();
 
@@ -149,6 +194,31 @@ describe('Koa Server (with noImplicitAdditionalProperties turned on)', () => {
       bodyModel.arrayUniqueItem = [0, 1, 2, 3];
       bodyModel.model = { value1: 'abcdef' };
       bodyModel.mixedUnion = { value1: '' };
+      bodyModel.intersection = { value1: 'one', value2: 'two' };
+
+      bodyModel.nestedObject = {
+        floatValue: 1.2,
+        doubleValue: 1.2,
+        intValue: 120,
+        longValue: 120,
+        booleanValue: true,
+        arrayValue: [0, 2],
+        dateValue: new Date('2017-01-01'),
+        datetimeValue: new Date('2017-01-01T00:00:00'),
+
+        numberMax10: 10,
+        numberMin5: 5,
+        stringMax10Lenght: 'abcdef',
+        stringMin5Lenght: 'abcdef',
+        stringPatternAZaz: 'aBcD',
+
+        arrayMax5Item: [0, 1, 2, 3],
+        arrayMin2Item: [0, 1],
+        arrayUniqueItem: [0, 1, 2, 3],
+        model: { value1: 'abcdef' },
+        mixedUnion: { value1: '' },
+        intersection: { value1: 'one', value2: 'two' },
+      };
 
       return verifyPostRequest(
         basePath + `/Validate/body`,
@@ -177,6 +247,30 @@ describe('Koa Server (with noImplicitAdditionalProperties turned on)', () => {
           expect(body.arrayUniqueItem).to.deep.equal(bodyModel.arrayUniqueItem);
           expect(body.model).to.deep.equal(bodyModel.model);
           expect(body.mixedUnion).to.deep.equal(bodyModel.mixedUnion);
+          expect(body.intersection).to.deep.equal(bodyModel.intersection);
+
+          expect(body.nestedObject.floatValue).to.equal(bodyModel.nestedObject.floatValue);
+          expect(body.nestedObject.doubleValue).to.equal(bodyModel.nestedObject.doubleValue);
+          expect(body.nestedObject.intValue).to.equal(bodyModel.nestedObject.intValue);
+          expect(body.nestedObject.longValue).to.equal(bodyModel.nestedObject.longValue);
+          expect(body.nestedObject.booleanValue).to.equal(bodyModel.nestedObject.booleanValue);
+          expect(body.nestedObject.arrayValue).to.deep.equal(bodyModel.nestedObject.arrayValue);
+
+          expect(new Date(body.nestedObject.dateValue)).to.deep.equal(new Date(bodyModel.nestedObject.dateValue));
+          expect(new Date(body.nestedObject.datetimeValue)).to.deep.equal(new Date(bodyModel.nestedObject.datetimeValue));
+
+          expect(body.nestedObject.numberMax10).to.equal(bodyModel.nestedObject.numberMax10);
+          expect(body.nestedObject.numberMin5).to.equal(bodyModel.nestedObject.numberMin5);
+          expect(body.nestedObject.stringMax10Lenght).to.equal(bodyModel.nestedObject.stringMax10Lenght);
+          expect(body.nestedObject.stringMin5Lenght).to.equal(bodyModel.nestedObject.stringMin5Lenght);
+          expect(body.nestedObject.stringPatternAZaz).to.equal(bodyModel.nestedObject.stringPatternAZaz);
+
+          expect(body.nestedObject.arrayMax5Item).to.deep.equal(bodyModel.nestedObject.arrayMax5Item);
+          expect(body.nestedObject.arrayMin2Item).to.deep.equal(bodyModel.nestedObject.arrayMin2Item);
+          expect(body.nestedObject.arrayUniqueItem).to.deep.equal(bodyModel.nestedObject.arrayUniqueItem);
+          expect(body.nestedObject.model).to.deep.equal(bodyModel.nestedObject.model);
+          expect(body.nestedObject.mixedUnion).to.deep.equal(bodyModel.nestedObject.mixedUnion);
+          expect(body.nestedObject.intersection).to.deep.equal(bodyModel.nestedObject.intersection);
         },
         200,
       );
@@ -355,6 +449,9 @@ describe('Koa Server (with noImplicitAdditionalProperties turned on)', () => {
       modelsArray: [{ email: 'test@test.com', id: 1 }],
       numberArray: [1, 2],
       numberValue: 5,
+      objLiteral: {
+        name: 'hello',
+      },
       object: { foo: 'bar' },
       objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
       optionalString: 'test1234',
diff --git a/tests/integration/koa-server.spec.ts b/tests/integration/koa-server.spec.ts
index 9ca9a309..86b67a5c 100644
--- a/tests/integration/koa-server.spec.ts
+++ b/tests/integration/koa-server.spec.ts
@@ -450,6 +450,31 @@ describe('Koa Server', () => {
       bodyModel.arrayUniqueItem = [0, 1, 2, 3];
       bodyModel.model = { value1: 'abcdef' };
       bodyModel.mixedUnion = { value1: '' };
+      bodyModel.intersection = { value1: 'one', value2: 'two' };
+
+      bodyModel.nestedObject = {
+        floatValue: 1.2,
+        doubleValue: 1.2,
+        intValue: 120,
+        longValue: 120,
+        booleanValue: true,
+        arrayValue: [0, 2],
+        dateValue: new Date('2017-01-01'),
+        datetimeValue: new Date('2017-01-01T00:00:00'),
+
+        numberMax10: 10,
+        numberMin5: 5,
+        stringMax10Lenght: 'abcdef',
+        stringMin5Lenght: 'abcdef',
+        stringPatternAZaz: 'aBcD',
+
+        arrayMax5Item: [0, 1, 2, 3],
+        arrayMin2Item: [0, 1],
+        arrayUniqueItem: [0, 1, 2, 3],
+        model: { value1: 'abcdef' },
+        mixedUnion: { value1: '' },
+        intersection: { value1: 'one', value2: 'two' },
+      };
 
       return verifyPostRequest(
         basePath + `/Validate/body`,
@@ -478,6 +503,30 @@ describe('Koa Server', () => {
           expect(body.arrayUniqueItem).to.deep.equal(bodyModel.arrayUniqueItem);
           expect(body.model).to.deep.equal(bodyModel.model);
           expect(body.mixedUnion).to.deep.equal(bodyModel.mixedUnion);
+          expect(body.intersection).to.deep.equal(bodyModel.intersection);
+
+          expect(body.nestedObject.floatValue).to.equal(bodyModel.nestedObject.floatValue);
+          expect(body.nestedObject.doubleValue).to.equal(bodyModel.nestedObject.doubleValue);
+          expect(body.nestedObject.intValue).to.equal(bodyModel.nestedObject.intValue);
+          expect(body.nestedObject.longValue).to.equal(bodyModel.nestedObject.longValue);
+          expect(body.nestedObject.booleanValue).to.equal(bodyModel.nestedObject.booleanValue);
+          expect(body.nestedObject.arrayValue).to.deep.equal(bodyModel.nestedObject.arrayValue);
+
+          expect(new Date(body.nestedObject.dateValue)).to.deep.equal(new Date(bodyModel.nestedObject.dateValue));
+          expect(new Date(body.nestedObject.datetimeValue)).to.deep.equal(new Date(bodyModel.nestedObject.datetimeValue));
+
+          expect(body.nestedObject.numberMax10).to.equal(bodyModel.nestedObject.numberMax10);
+          expect(body.nestedObject.numberMin5).to.equal(bodyModel.nestedObject.numberMin5);
+          expect(body.nestedObject.stringMax10Lenght).to.equal(bodyModel.nestedObject.stringMax10Lenght);
+          expect(body.nestedObject.stringMin5Lenght).to.equal(bodyModel.nestedObject.stringMin5Lenght);
+          expect(body.nestedObject.stringPatternAZaz).to.equal(bodyModel.nestedObject.stringPatternAZaz);
+
+          expect(body.nestedObject.arrayMax5Item).to.deep.equal(bodyModel.nestedObject.arrayMax5Item);
+          expect(body.nestedObject.arrayMin2Item).to.deep.equal(bodyModel.nestedObject.arrayMin2Item);
+          expect(body.nestedObject.arrayUniqueItem).to.deep.equal(bodyModel.nestedObject.arrayUniqueItem);
+          expect(body.nestedObject.model).to.deep.equal(bodyModel.nestedObject.model);
+          expect(body.nestedObject.mixedUnion).to.deep.equal(bodyModel.nestedObject.mixedUnion);
+          expect(body.nestedObject.intersection).to.deep.equal(bodyModel.nestedObject.intersection);
         },
         200,
       );
@@ -504,6 +553,30 @@ describe('Koa Server', () => {
       bodyModel.arrayUniqueItem = [0, 0, 1, 1];
       bodyModel.model = 1 as any;
       bodyModel.mixedUnion = 123 as any;
+      bodyModel.intersection = { value1: 'one' } as any;
+
+      bodyModel.nestedObject = {
+        floatValue: '120a' as any,
+        doubleValue: '120a' as any,
+        intValue: 1.2,
+        longValue: 1.2,
+        booleanValue: 'abc' as any,
+        dateValue: 'abc' as any,
+        datetimeValue: 'abc' as any,
+
+        numberMax10: 20,
+        numberMin5: 0,
+        stringMax10Lenght: 'abcdefghijk',
+        stringMin5Lenght: 'abcd',
+        stringPatternAZaz: 'ab01234',
+
+        arrayMax5Item: [0, 1, 2, 3, 4, 6, 7, 8, 9],
+        arrayMin2Item: [0],
+        arrayUniqueItem: [0, 0, 1, 1],
+        model: 1 as any,
+        mixedUnion: 123 as any,
+        intersection: { value1: 'one' } as any,
+      } as any;
 
       return verifyPostRequest(
         basePath + `/Validate/body`,
@@ -551,6 +624,51 @@ describe('Koa Server', () => {
               'Issues: [{"body.mixedUnion":{"message":"invalid string value","value":123}},' +
               '{"body.mixedUnion":{"message":"invalid object","value":123}}]',
           );
+          expect(body.fields['body.intersection'].message).to.equal('Could not match the intersection against every type. Issues: [{"body.value2":{"message":"\'value2\' is required"}}]');
+
+          expect(body.fields['body.nestedObject.floatValue'].message).to.equal('Invalid float error message.');
+          expect(body.fields['body.nestedObject.floatValue'].value).to.equal(bodyModel.floatValue);
+          expect(body.fields['body.nestedObject.doubleValue'].message).to.equal('Invalid double error message.');
+          expect(body.fields['body.nestedObject.doubleValue'].value).to.equal(bodyModel.doubleValue);
+          expect(body.fields['body.nestedObject.intValue'].message).to.equal('invalid integer number');
+          expect(body.fields['body.nestedObject.intValue'].value).to.equal(bodyModel.intValue);
+          expect(body.fields['body.nestedObject.longValue'].message).to.equal('Custom Required long number.');
+          expect(body.fields['body.nestedObject.longValue'].value).to.equal(bodyModel.longValue);
+          expect(body.fields['body.nestedObject.booleanValue'].message).to.equal('invalid boolean value');
+          expect(body.fields['body.nestedObject.booleanValue'].value).to.equal(bodyModel.booleanValue);
+
+          expect(body.fields['body.nestedObject.dateValue'].message).to.equal('invalid ISO 8601 date format, i.e. YYYY-MM-DD');
+          expect(body.fields['body.nestedObject.dateValue'].value).to.equal(bodyModel.dateValue);
+          expect(body.fields['body.nestedObject.datetimeValue'].message).to.equal('invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss');
+          expect(body.fields['body.nestedObject.datetimeValue'].value).to.equal(bodyModel.datetimeValue);
+
+          expect(body.fields['body.nestedObject.numberMax10'].message).to.equal('max 10');
+          expect(body.fields['body.nestedObject.numberMax10'].value).to.equal(bodyModel.numberMax10);
+          expect(body.fields['body.nestedObject.numberMin5'].message).to.equal('min 5');
+          expect(body.fields['body.nestedObject.numberMin5'].value).to.equal(bodyModel.numberMin5);
+          expect(body.fields['body.nestedObject.stringMax10Lenght'].message).to.equal('maxLength 10');
+          expect(body.fields['body.nestedObject.stringMax10Lenght'].value).to.equal(bodyModel.stringMax10Lenght);
+          expect(body.fields['body.nestedObject.stringMin5Lenght'].message).to.equal('minLength 5');
+          expect(body.fields['body.nestedObject.stringMin5Lenght'].value).to.equal(bodyModel.stringMin5Lenght);
+          expect(body.fields['body.nestedObject.stringPatternAZaz'].message).to.equal("Not match in '^[a-zA-Z]+$'");
+          expect(body.fields['body.nestedObject.stringPatternAZaz'].value).to.equal(bodyModel.stringPatternAZaz);
+
+          expect(body.fields['body.nestedObject.arrayMax5Item'].message).to.equal('maxItems 5');
+          expect(body.fields['body.nestedObject.arrayMax5Item'].value).to.deep.equal(bodyModel.arrayMax5Item);
+          expect(body.fields['body.nestedObject.arrayMin2Item'].message).to.equal('minItems 2');
+          expect(body.fields['body.nestedObject.arrayMin2Item'].value).to.deep.equal(bodyModel.arrayMin2Item);
+          expect(body.fields['body.nestedObject.arrayUniqueItem'].message).to.equal('required unique array');
+          expect(body.fields['body.nestedObject.arrayUniqueItem'].value).to.deep.equal(bodyModel.arrayUniqueItem);
+          expect(body.fields['body.nestedObject.model'].message).to.equal('invalid object');
+          expect(body.fields['body.nestedObject.model'].value).to.deep.equal(bodyModel.model);
+          expect(body.fields['body.nestedObject.mixedUnion'].message).to.equal(
+            'Could not match the union against any of the items. ' +
+              'Issues: [{"body.nestedObject.mixedUnion":{"message":"invalid string value","value":123}},' +
+              '{"body.nestedObject.mixedUnion":{"message":"invalid object","value":123}}]',
+          );
+          expect(body.fields['body.nestedObject.intersection'].message).to.equal(
+            'Could not match the intersection against every type. Issues: [{"body.nestedObject.value2":{"message":"\'value2\' is required"}}]',
+          );
         },
         400,
       );
@@ -851,6 +969,9 @@ describe('Koa Server', () => {
       modelsArray: [{ email: 'test@test.com', id: 1 }],
       numberArray: [1, 2],
       numberValue: 5,
+      objLiteral: {
+        name: 'hello',
+      },
       object: { foo: 'bar' },
       objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
       optionalString: 'test1234',
diff --git a/tests/integration/openapi3-express.spec.ts b/tests/integration/openapi3-express.spec.ts
index 56ea93b6..c0b9b63f 100644
--- a/tests/integration/openapi3-express.spec.ts
+++ b/tests/integration/openapi3-express.spec.ts
@@ -29,6 +29,31 @@ describe('OpenAPI3 Express Server', () => {
     bodyModel.arrayUniqueItem = [0, 1, 2, 3];
     bodyModel.model = { value1: 'abcdef' };
     bodyModel.mixedUnion = { value1: '' };
+    bodyModel.intersection = { value1: 'one', value2: 'two' };
+
+    bodyModel.nestedObject = {
+      floatValue: 1.2,
+      doubleValue: 1.2,
+      intValue: 120,
+      longValue: 120,
+      booleanValue: true,
+      arrayValue: [0, 2],
+      dateValue: new Date('2017-01-01'),
+      datetimeValue: new Date('2017-01-01T00:00:00'),
+
+      numberMax10: 10,
+      numberMin5: 5,
+      stringMax10Lenght: 'abcdef',
+      stringMin5Lenght: 'abcdef',
+      stringPatternAZaz: 'aBcD',
+
+      arrayMax5Item: [0, 1, 2, 3],
+      arrayMin2Item: [0, 1],
+      arrayUniqueItem: [0, 1, 2, 3],
+      model: { value1: 'abcdef' },
+      mixedUnion: { value1: '' },
+      intersection: { value1: 'one', value2: 'two' },
+    };
 
     return verifyPostRequest(
       basePath + `/Validate/body`,
@@ -57,6 +82,30 @@ describe('OpenAPI3 Express Server', () => {
         expect(body.arrayUniqueItem).to.deep.equal(bodyModel.arrayUniqueItem);
         expect(body.model).to.deep.equal(bodyModel.model);
         expect(body.mixedUnion).to.deep.equal(bodyModel.mixedUnion);
+        expect(body.intersection).to.deep.equal(bodyModel.intersection);
+
+        expect(body.nestedObject.floatValue).to.equal(bodyModel.nestedObject.floatValue);
+        expect(body.nestedObject.doubleValue).to.equal(bodyModel.nestedObject.doubleValue);
+        expect(body.nestedObject.intValue).to.equal(bodyModel.nestedObject.intValue);
+        expect(body.nestedObject.longValue).to.equal(bodyModel.nestedObject.longValue);
+        expect(body.nestedObject.booleanValue).to.equal(bodyModel.nestedObject.booleanValue);
+        expect(body.nestedObject.arrayValue).to.deep.equal(bodyModel.nestedObject.arrayValue);
+
+        expect(new Date(body.nestedObject.dateValue)).to.deep.equal(new Date(bodyModel.nestedObject.dateValue));
+        expect(new Date(body.nestedObject.datetimeValue)).to.deep.equal(new Date(bodyModel.nestedObject.datetimeValue));
+
+        expect(body.nestedObject.numberMax10).to.equal(bodyModel.nestedObject.numberMax10);
+        expect(body.nestedObject.numberMin5).to.equal(bodyModel.nestedObject.numberMin5);
+        expect(body.nestedObject.stringMax10Lenght).to.equal(bodyModel.nestedObject.stringMax10Lenght);
+        expect(body.nestedObject.stringMin5Lenght).to.equal(bodyModel.nestedObject.stringMin5Lenght);
+        expect(body.nestedObject.stringPatternAZaz).to.equal(bodyModel.nestedObject.stringPatternAZaz);
+
+        expect(body.nestedObject.arrayMax5Item).to.deep.equal(bodyModel.nestedObject.arrayMax5Item);
+        expect(body.nestedObject.arrayMin2Item).to.deep.equal(bodyModel.nestedObject.arrayMin2Item);
+        expect(body.nestedObject.arrayUniqueItem).to.deep.equal(bodyModel.nestedObject.arrayUniqueItem);
+        expect(body.nestedObject.model).to.deep.equal(bodyModel.nestedObject.model);
+        expect(body.nestedObject.mixedUnion).to.deep.equal(bodyModel.nestedObject.mixedUnion);
+        expect(body.nestedObject.intersection).to.deep.equal(bodyModel.nestedObject.intersection);
       },
       200,
     );
@@ -85,6 +134,30 @@ describe('OpenAPI3 Express Server', () => {
     bodyModel.intersectionNoAdditional = { value1: '', value2: '', value3: 123, value4: 123 } as any;
     bodyModel.model = 1 as any;
     bodyModel.mixedUnion = 123 as any;
+    bodyModel.intersection = { value1: 'one' } as any;
+
+    bodyModel.nestedObject = {
+      floatValue: '120a' as any,
+      doubleValue: '120a' as any,
+      intValue: 1.2,
+      longValue: 1.2,
+      booleanValue: 'abc' as any,
+      dateValue: 'abc' as any,
+      datetimeValue: 'abc' as any,
+
+      numberMax10: 20,
+      numberMin5: 0,
+      stringMax10Lenght: 'abcdefghijk',
+      stringMin5Lenght: 'abcd',
+      stringPatternAZaz: 'ab01234',
+
+      arrayMax5Item: [0, 1, 2, 3, 4, 6, 7, 8, 9],
+      arrayMin2Item: [0],
+      arrayUniqueItem: [0, 0, 1, 1],
+      model: 1 as any,
+      mixedUnion: 123 as any,
+      intersection: { value1: 'one' } as any,
+    } as any;
 
     return verifyPostRequest(
       basePath + `/Validate/body`,
@@ -134,6 +207,51 @@ describe('OpenAPI3 Express Server', () => {
             'Issues: [{"body.mixedUnion":{"message":"invalid string value","value":123}},' +
             '{"body.mixedUnion":{"message":"invalid object","value":123}}]',
         );
+        expect(body.fields['body.intersection'].message).to.equal('Could not match the intersection against every type. Issues: [{"body.value2":{"message":"\'value2\' is required"}}]');
+
+        expect(body.fields['body.nestedObject.floatValue'].message).to.equal('Invalid float error message.');
+        expect(body.fields['body.nestedObject.floatValue'].value).to.equal(bodyModel.floatValue);
+        expect(body.fields['body.nestedObject.doubleValue'].message).to.equal('Invalid double error message.');
+        expect(body.fields['body.nestedObject.doubleValue'].value).to.equal(bodyModel.doubleValue);
+        expect(body.fields['body.nestedObject.intValue'].message).to.equal('invalid integer number');
+        expect(body.fields['body.nestedObject.intValue'].value).to.equal(bodyModel.intValue);
+        expect(body.fields['body.nestedObject.longValue'].message).to.equal('Custom Required long number.');
+        expect(body.fields['body.nestedObject.longValue'].value).to.equal(bodyModel.longValue);
+        expect(body.fields['body.nestedObject.booleanValue'].message).to.equal('invalid boolean value');
+        expect(body.fields['body.nestedObject.booleanValue'].value).to.equal(bodyModel.booleanValue);
+
+        expect(body.fields['body.nestedObject.dateValue'].message).to.equal('invalid ISO 8601 date format, i.e. YYYY-MM-DD');
+        expect(body.fields['body.nestedObject.dateValue'].value).to.equal(bodyModel.dateValue);
+        expect(body.fields['body.nestedObject.datetimeValue'].message).to.equal('invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss');
+        expect(body.fields['body.nestedObject.datetimeValue'].value).to.equal(bodyModel.datetimeValue);
+
+        expect(body.fields['body.nestedObject.numberMax10'].message).to.equal('max 10');
+        expect(body.fields['body.nestedObject.numberMax10'].value).to.equal(bodyModel.numberMax10);
+        expect(body.fields['body.nestedObject.numberMin5'].message).to.equal('min 5');
+        expect(body.fields['body.nestedObject.numberMin5'].value).to.equal(bodyModel.numberMin5);
+        expect(body.fields['body.nestedObject.stringMax10Lenght'].message).to.equal('maxLength 10');
+        expect(body.fields['body.nestedObject.stringMax10Lenght'].value).to.equal(bodyModel.stringMax10Lenght);
+        expect(body.fields['body.nestedObject.stringMin5Lenght'].message).to.equal('minLength 5');
+        expect(body.fields['body.nestedObject.stringMin5Lenght'].value).to.equal(bodyModel.stringMin5Lenght);
+        expect(body.fields['body.nestedObject.stringPatternAZaz'].message).to.equal("Not match in '^[a-zA-Z]+$'");
+        expect(body.fields['body.nestedObject.stringPatternAZaz'].value).to.equal(bodyModel.stringPatternAZaz);
+
+        expect(body.fields['body.nestedObject.arrayMax5Item'].message).to.equal('maxItems 5');
+        expect(body.fields['body.nestedObject.arrayMax5Item'].value).to.deep.equal(bodyModel.arrayMax5Item);
+        expect(body.fields['body.nestedObject.arrayMin2Item'].message).to.equal('minItems 2');
+        expect(body.fields['body.nestedObject.arrayMin2Item'].value).to.deep.equal(bodyModel.arrayMin2Item);
+        expect(body.fields['body.nestedObject.arrayUniqueItem'].message).to.equal('required unique array');
+        expect(body.fields['body.nestedObject.arrayUniqueItem'].value).to.deep.equal(bodyModel.arrayUniqueItem);
+        expect(body.fields['body.nestedObject.model'].message).to.equal('invalid object');
+        expect(body.fields['body.nestedObject.model'].value).to.deep.equal(bodyModel.model);
+        expect(body.fields['body.nestedObject.mixedUnion'].message).to.equal(
+          'Could not match the union against any of the items. ' +
+            'Issues: [{"body.nestedObject.mixedUnion":{"message":"invalid string value","value":123}},' +
+            '{"body.nestedObject.mixedUnion":{"message":"invalid object","value":123}}]',
+        );
+        expect(body.fields['body.nestedObject.intersection'].message).to.equal(
+          'Could not match the intersection against every type. Issues: [{"body.nestedObject.value2":{"message":"\'value2\' is required"}}]',
+        );
       },
       400,
     );
diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts
index 7cedc6c7..9087e368 100644
--- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts
+++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts
@@ -146,7 +146,6 @@ describe('Definition generation', () => {
             expect(definition.additionalProperties).to.eq(true, forSpec(currentSpec));
           }
         });
-
         /**
          * By creating a record of "keyof T" we ensure that contributors will need add a test for any new property that is added to the model
          */
@@ -422,6 +421,31 @@ describe('Definition generation', () => {
             expect(propertySchema.type).to.eq('object', `for property ${propertyName}`);
             expect(propertySchema).to.not.haveOwnProperty('additionalProperties', `for property ${propertyName}`);
           },
+          objLiteral: (propertyName, propertySchema) => {
+            expect(propertySchema).to.deep.equal({
+              default: undefined,
+              description: undefined,
+              format: undefined,
+              properties: {
+                name: { type: 'string' },
+                nested: {
+                  properties: {
+                    additionals: { properties: {}, type: 'object' },
+                    allNestedOptional: {
+                      properties: { one: { type: 'string' }, two: { type: 'string' } },
+                      type: 'object',
+                    },
+                    bool: { type: 'boolean' },
+                    optional: { type: 'number', format: 'double' },
+                  },
+                  required: ['allNestedOptional', 'bool'],
+                  type: 'object',
+                },
+              },
+              required: ['name'],
+              type: 'object',
+            });
+          },
         };
 
         Object.keys(assertionsPerProperty).forEach(aPropertyName => {
@@ -438,6 +462,11 @@ describe('Definition generation', () => {
           Object.keys(definition.properties!).length,
           `because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`,
         );
+
+        expect(Object.keys(assertionsPerProperty)).to.length(
+          Object.keys(definition.properties!).length,
+          `because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`,
+        );
       });
     });
 
diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts
index f481cde0..6a079e60 100644
--- a/tests/unit/swagger/schemaDetails3.spec.ts
+++ b/tests/unit/swagger/schemaDetails3.spec.ts
@@ -214,6 +214,33 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
             expect(propertySchema.description).to.eq(undefined, `for property ${propertyName}.description`);
             expect(propertySchema).to.not.haveOwnProperty('additionalProperties', `for property ${propertyName}`);
           },
+          objLiteral: (propertyName, propertySchema) => {
+            expect(propertySchema).to.deep.include({
+              properties: {
+                name: {
+                  type: 'string',
+                },
+                nested: {
+                  properties: {
+                    additionals: {
+                      properties: {},
+                      type: 'object',
+                    },
+                    allNestedOptional: {
+                      properties: { one: { type: 'string' }, two: { type: 'string' } },
+                      type: 'object',
+                    },
+                    bool: { type: 'boolean' },
+                    optional: { format: 'double', type: 'number' },
+                  },
+                  required: ['allNestedOptional', 'bool'],
+                  type: 'object',
+                },
+              },
+              required: ['name'],
+              type: 'object',
+            });
+          },
           object: (propertyName, propertySchema) => {
             expect(propertySchema.type).to.eq('object', `for property ${propertyName}`);
             if (currentSpec.specName === 'specWithNoImplicitExtras') {
diff --git a/tests/unit/templating/templateHelpers.spec.ts b/tests/unit/templating/templateHelpers.spec.ts
index 7f145ad0..7e558cb3 100644
--- a/tests/unit/templating/templateHelpers.spec.ts
+++ b/tests/unit/templating/templateHelpers.spec.ts
@@ -169,6 +169,113 @@ it('should throw if the data has additionalProperties (on a intersection) if noI
   }
 });
 
+it('should throw if the data has additionalProperties (on a nested Object) if noImplicitAdditionalProperties is set to throw-on-extras', () => {
+  // Arrange
+  const refName = 'ExampleModel';
+  const models: TsoaRoute.Models = {
+    [refName]: {
+      properties: {
+        objLiteral: {
+          dataType: 'nestedObjectLiteral',
+          nestedProperties: {
+            nested: {
+              dataType: 'nestedObjectLiteral',
+              nestedProperties: {
+                additionals: {
+                  dataType: 'nestedObjectLiteral',
+                  nestedProperties: {},
+                  additionalProperties: {
+                    ref: 'TypeAliasModel1',
+                  },
+                },
+                allNestedOptional: {
+                  dataType: 'nestedObjectLiteral',
+                  nestedProperties: {
+                    two: {
+                      dataType: 'string',
+                    },
+                    one: {
+                      dataType: 'string',
+                    },
+                  },
+                  required: true,
+                },
+                optional: {
+                  dataType: 'double',
+                },
+                bool: {
+                  dataType: 'boolean',
+                  required: true,
+                },
+              },
+            },
+            name: { dataType: 'string', required: true },
+          },
+          required: true,
+        },
+      },
+    },
+    TypeAliasModel1: {
+      properties: {
+        value1: { dataType: 'string', required: true },
+      },
+      additionalProperties: false,
+    },
+  };
+  const v = new ValidationService(models);
+  const minimalSwaggerConfig: SwaggerConfigRelatedToRoutes = {
+    noImplicitAdditionalProperties: true,
+  };
+  const errorDictionary: FieldErrors = {};
+  const dataToValidate = {
+    name: '',
+    // extra
+    extra: 123,
+    nested: {
+      bool: true,
+      allNestedOptional: {
+        // extra
+        removed: '123',
+      },
+      additionals: {
+        one: { value1: 'one' },
+        two: { value1: 'two' },
+      },
+    },
+  };
+
+  // Act
+  const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, minimalSwaggerConfig, models[refName].properties!.objLiteral.nestedProperties, false, refName + '.');
+
+  // Assert
+  expect(errorDictionary).to.deep.eq({
+    'ExampleModel.objLiteral': {
+      message: '"extra" is an excess property and therefore is not allowed',
+      value: { extra: 123 },
+    },
+    'ExampleModel.objLiteral.nested.allNestedOptional': {
+      message: '"removed" is an excess property and therefore is not allowed',
+      value: { removed: '123' },
+    },
+  });
+  expect(result).to.eql({
+    name: '',
+    // extra
+    extra: 123,
+    nested: {
+      bool: true,
+      allNestedOptional: {
+        // extra
+        removed: '123',
+      },
+      additionals: {
+        one: { value1: 'one' },
+        two: { value1: 'two' },
+      },
+    },
+  });
+});
+
 it('should not throw if the data has additionalProperties (on a intersection) if noImplicitAdditionalProperties is set to silently-remove-extras', () => {
   // Arrange
   const refName = 'ExampleModel';
@@ -274,3 +381,187 @@ it('should not throw if the data has additionalProperties (on a intersection) if
     [nameOfAdditionalProperty]: 'something extra',
   });
 });
+
+it('should not throw if the data has additionalProperties (on a nested Object) if noImplicitAdditionalProperties is set to silently-remove-extras', () => {
+  // Arrange
+  const refName = 'ExampleModel';
+  const models: TsoaRoute.Models = {
+    [refName]: {
+      properties: {
+        objLiteral: {
+          dataType: 'nestedObjectLiteral',
+          nestedProperties: {
+            nested: {
+              dataType: 'nestedObjectLiteral',
+              nestedProperties: {
+                additionals: {
+                  dataType: 'nestedObjectLiteral',
+                  nestedProperties: {},
+                  additionalProperties: {
+                    ref: 'TypeAliasModel1',
+                  },
+                },
+                allNestedOptional: {
+                  dataType: 'nestedObjectLiteral',
+                  nestedProperties: {
+                    two: { dataType: 'string' },
+                    one: {
+                      dataType: 'string',
+                    },
+                  },
+                  required: true,
+                },
+                optional: { dataType: 'double' },
+                bool: { dataType: 'boolean', required: true },
+              },
+            },
+            name: { dataType: 'string', required: true },
+          },
+          required: true,
+        },
+      },
+    },
+    TypeAliasModel1: {
+      properties: {
+        value1: { dataType: 'string', required: true },
+      },
+      additionalProperties: false,
+    },
+  };
+  const v = new ValidationService(models);
+  const minimalSwaggerConfig: SwaggerConfigRelatedToRoutes = {
+    noImplicitAdditionalProperties: 'silently-remove-extras',
+  };
+  const errorDictionary: FieldErrors = {};
+  const dataToValidate = {
+    name: '',
+    // extra
+    extra: 123,
+    nested: {
+      bool: true,
+      allNestedOptional: {
+        // extra
+        removed: '123',
+      },
+      additionals: {
+        one: { value1: 'one' },
+        two: { value1: 'two' },
+      },
+    },
+  };
+
+  // Act
+  const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, minimalSwaggerConfig, models[refName].properties!.objLiteral.nestedProperties, false, refName + '.');
+
+  // Assert
+  expect(errorDictionary).to.deep.eq({});
+  expect(result).to.eql({
+    name: '',
+    nested: {
+      bool: true,
+      allNestedOptional: {},
+      additionals: {
+        one: { value1: 'one' },
+        two: { value1: 'two' },
+      },
+    },
+  });
+});
+
+it('should not throw if the data has additionalProperties (on a nested Object) if noImplicitAdditionalProperties is set to false', () => {
+  // Arrange
+  const refName = 'ExampleModel';
+  const models: TsoaRoute.Models = {
+    [refName]: {
+      properties: {
+        objLiteral: {
+          dataType: 'nestedObjectLiteral',
+          nestedProperties: {
+            nested: {
+              dataType: 'nestedObjectLiteral',
+              nestedProperties: {
+                additionals: {
+                  dataType: 'nestedObjectLiteral',
+                  nestedProperties: {},
+                  additionalProperties: {
+                    ref: 'TypeAliasModel1',
+                  },
+                },
+                allNestedOptional: {
+                  dataType: 'nestedObjectLiteral',
+                  nestedProperties: {
+                    two: {
+                      dataType: 'string',
+                    },
+                    one: {
+                      dataType: 'string',
+                    },
+                  },
+                  required: true,
+                },
+                optional: {
+                  dataType: 'double',
+                },
+                bool: {
+                  dataType: 'boolean',
+                  required: true,
+                },
+              },
+            },
+            name: { dataType: 'string', required: true },
+          },
+          required: true,
+        },
+      },
+    },
+    TypeAliasModel1: {
+      properties: {
+        value1: { dataType: 'string', required: true },
+      },
+      additionalProperties: false,
+    },
+  };
+  const v = new ValidationService(models);
+  const minimalSwaggerConfig: SwaggerConfigRelatedToRoutes = {
+    noImplicitAdditionalProperties: false,
+  };
+  const errorDictionary: FieldErrors = {};
+  const dataToValidate = {
+    name: '',
+    // extra
+    extra: 123,
+    nested: {
+      bool: true,
+      allNestedOptional: {
+        // extra
+        removed: '123',
+      },
+      additionals: {
+        one: { value1: 'one' },
+        two: { value1: 'two' },
+      },
+    },
+  };
+
+  // Act
+  const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, minimalSwaggerConfig, models[refName].properties!.objLiteral.nestedProperties, false, refName + '.');
+
+  // Assert
+  expect(errorDictionary).to.deep.eq({});
+  expect(result).to.eql({
+    name: '',
+    // extra
+    extra: 123,
+    nested: {
+      bool: true,
+      allNestedOptional: {
+        // extra
+        removed: '123',
+      },
+      additionals: {
+        one: { value1: 'one' },
+        two: { value1: 'two' },
+      },
+    },
+  });
+});