Repository: asyncapi/modelina (ts)

Issue:
should render additionalProperties 
#### Reason/Context
Currently the `CommonModel` does include additionalProperties, however they are not rendered as properties in the generator 

Questions:
- How should we handle `additionalProperties` if we already have a property called `additionalProperties`?

Pull request:
feat: render additionalProperties
**Description**
This PR ensures that additionalProperties are rendered. It introduces:
- Disables `no-unused-vars` and enable `@typescript-eslint/no-unused-vars` as a replacement as the normal one is faulty in typescript.
- Introduces a `type` argument to rendering properties as we have different types of properties that should be rendered differently. 
- Adds the rendering of `additionalProperties` alongside accessor functions.
- Ensures that the correct property name is used since we must handle if a model already contains the property `additionalProperties`.

**Related issue(s)**
fixes #68 

Patch:
diff --git a/.eslintrc b/.eslintrc
index de151c1e..05f03a87 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -48,12 +48,7 @@
     "no-empty-character-class": 2,
     "no-self-compare": 2,
     "valid-typeof": 2,
-    "no-unused-vars": [
-      2,
-      {
-        "args": "none"
-      }
-    ],
+    "no-unused-vars": 0,
     "handle-callback-err": 2,
     "no-shadow-restricted-names": 2,
     "no-new-require": 2,
@@ -187,7 +182,8 @@
     "prefer-arrow-callback": 2,
     "prefer-const": 2,
     "prefer-spread": 2,
-    "prefer-template": 2
+    "prefer-template": 2,
+    "@typescript-eslint/no-unused-vars": 2
   },
   "overrides": [
     {
diff --git a/src/generators/java/JavaRenderer.ts b/src/generators/java/JavaRenderer.ts
index 206d3f6d..01e67452 100644
--- a/src/generators/java/JavaRenderer.ts
+++ b/src/generators/java/JavaRenderer.ts
@@ -19,6 +19,11 @@ export abstract class JavaRenderer extends AbstractRenderer<JavaOptions> {
     super(options, presets, model, inputModel);
   }
 
+  /**
+   * Renders model(s) to Java type(s).
+   * 
+   * @param model
+   */
   renderType(model: CommonModel | CommonModel[]): string {
     if (Array.isArray(model) || Array.isArray(model.type)) {
       return 'Object'; // fallback
@@ -30,6 +35,11 @@ export abstract class JavaRenderer extends AbstractRenderer<JavaOptions> {
     return this.toClassType(this.toJavaType(format || model.type, model));
   }
 
+  /**
+   * Returns the Java corresponding type from CommonModel type or JSON schema format
+   * @param type 
+   * @param model 
+   */
   toJavaType(type: string | undefined, model: CommonModel): string {
     switch (type) {
     case 'integer':
diff --git a/src/generators/java/presets/CommonPreset.ts b/src/generators/java/presets/CommonPreset.ts
index 02a6732a..78f90369 100644
--- a/src/generators/java/presets/CommonPreset.ts
+++ b/src/generators/java/presets/CommonPreset.ts
@@ -1,7 +1,7 @@
 import { JavaRenderer } from '../JavaRenderer';
 import { JavaPreset } from '../JavaPreset';
 
-import { FormatHelpers } from '../../../helpers';
+import { getUniquePropertyName, FormatHelpers, DefaultPropertyNames } from '../../../helpers';
 import { CommonModel } from '../../../models';
 
 export interface JavaCommonPresetOptions {
@@ -19,7 +19,8 @@ function renderEqual({ renderer, model }: {
 }): string {
   const formattedModelName = model.$id && FormatHelpers.toPascalCase(model.$id);
   const properties = model.properties || {};
-  const equalProperties = Object.keys(properties).map(prop => {
+  const propertyKeys = [...Object.keys(properties), getUniquePropertyName(model, DefaultPropertyNames.additionalProperties)];
+  const equalProperties = propertyKeys.map(prop => {
     const camelCasedProp = FormatHelpers.toCamelCase(prop);
     return `Objects.equals(this.${camelCasedProp}, self.${camelCasedProp})`;
   }).join(' &&\n');
@@ -46,7 +47,8 @@ function renderHashCode({ renderer, model }: {
   model: CommonModel,
 }): string {
   const properties = model.properties || {};
-  const hashProperties = Object.keys(properties).map(prop => FormatHelpers.toCamelCase(prop)).join(', ');
+  const propertyKeys = [...Object.keys(properties), getUniquePropertyName(model, DefaultPropertyNames.additionalProperties)];
+  const hashProperties = propertyKeys.map(prop => FormatHelpers.toCamelCase(prop)).join(', ');
 
   return `${renderer.renderAnnotation('Override')}
 public int hashCode() {
@@ -63,7 +65,8 @@ function renderToString({ renderer, model }: {
 }): string {
   const formattedModelName = model.$id && FormatHelpers.toPascalCase(model.$id);
   const properties = model.properties || {};
-  const toStringProperties = Object.keys(properties).map(prop => 
+  const propertyKeys = [...Object.keys(properties), getUniquePropertyName(model, DefaultPropertyNames.additionalProperties)];
+  const toStringProperties = propertyKeys.map(prop => 
     `"    ${prop}: " + toIndentedString(${FormatHelpers.toCamelCase(prop)}) + "\\n" +`
   );
 
diff --git a/src/generators/java/presets/ConstraintsPreset.ts b/src/generators/java/presets/ConstraintsPreset.ts
index b32655fc..2a4073c3 100644
--- a/src/generators/java/presets/ConstraintsPreset.ts
+++ b/src/generators/java/presets/ConstraintsPreset.ts
@@ -1,7 +1,4 @@
 import { JavaPreset } from '../JavaPreset';
-
-import { CommonModel } from '../../../models';
-
 /**
  * Preset which extends class's getters with annotations from `javax.validation.constraints` package
  * 
@@ -14,9 +11,6 @@ export const JAVA_CONSTRAINTS_PRESET: JavaPreset = {
       return content;
     },
     getter({ renderer, model, propertyName, property, content }) {
-      if (!(property instanceof CommonModel)) {
-        return content;
-      }
       const annotations: string[] = [];
       
       const isRequired = model.isRequired(propertyName);
diff --git a/src/generators/java/presets/DescriptioPreset.ts b/src/generators/java/presets/DescriptioPreset.ts
index 47104346..8b49ef4e 100644
--- a/src/generators/java/presets/DescriptioPreset.ts
+++ b/src/generators/java/presets/DescriptioPreset.ts
@@ -1,6 +1,5 @@
 import { JavaRenderer } from '../JavaRenderer';
 import { JavaPreset } from '../JavaPreset';
-
 import { FormatHelpers } from '../../../helpers';
 import { CommonModel } from '../../../models';
 
@@ -9,10 +8,6 @@ function renderDescription({ renderer, content, item }: {
   content: string,
   item: CommonModel,
 }): string {
-  if (!(item instanceof CommonModel)) {
-    return content;
-  }
-
   let desc = item.getFromSchema('description');
   const examples = item.getFromSchema('examples');
 
diff --git a/src/generators/java/presets/JacksonPreset.ts b/src/generators/java/presets/JacksonPreset.ts
index 6895a162..2038ef6a 100644
--- a/src/generators/java/presets/JacksonPreset.ts
+++ b/src/generators/java/presets/JacksonPreset.ts
@@ -1,7 +1,8 @@
+import { PropertyType } from '../../../models';
 import { JavaPreset } from '../JavaPreset';
 
 /**
- * Preset which adds `com.fasterxml.jackson` related annotations to class's getters.
+ * Preset which adds `com.fasterxml.jackson` related annotations to class's property getters.
  * 
  * @implements {JavaPreset}
  */
@@ -11,9 +12,12 @@ export const JAVA_JACKSON_PRESET: JavaPreset = {
       renderer.addDependency('import com.fasterxml.jackson.annotation.*;');
       return content;
     },
-    getter({ renderer, propertyName, content }) {
-      const annotation = renderer.renderAnnotation('JsonProperty', `"${propertyName}"`);
-      return renderer.renderBlock([annotation, content]);
+    getter({ renderer, propertyName, content, type }) {
+      if (type === PropertyType.property) {
+        const annotation = renderer.renderAnnotation('JsonProperty', `"${propertyName}"`);
+        return renderer.renderBlock([annotation, content]);
+      }
+      return renderer.renderBlock([content]);
     },
   }
 };
diff --git a/src/generators/java/renderers/ClassRenderer.ts b/src/generators/java/renderers/ClassRenderer.ts
index d1cb388b..35cb78ca 100644
--- a/src/generators/java/renderers/ClassRenderer.ts
+++ b/src/generators/java/renderers/ClassRenderer.ts
@@ -1,7 +1,7 @@
 import { JavaRenderer } from '../JavaRenderer';
 
-import { CommonModel, ClassPreset } from '../../../models';
-import { FormatHelpers } from '../../../helpers';
+import { CommonModel, ClassPreset, PropertyType } from '../../../models';
+import { DefaultPropertyNames, FormatHelpers, getUniquePropertyName } from '../../../helpers';
 
 /**
  * Renderer for Java's `class` type
@@ -16,6 +16,10 @@ export class ClassRenderer extends JavaRenderer {
       await this.renderAccessors(),
       await this.runAdditionalContentPreset(),
     ];
+
+    if (this.model.additionalProperties !== undefined) {
+      this.addDependency('import java.util.Map;');
+    }
     
     const formattedName = this.model.$id && FormatHelpers.toPascalCase(this.model.$id);
     return `public class ${formattedName} {
@@ -35,12 +39,18 @@ ${this.indent(this.renderBlock(content, 2))}
       const rendererProperty = await this.runPropertyPreset(propertyName, property);
       content.push(rendererProperty);
     }
+    
+    if (this.model.additionalProperties !== undefined) {
+      const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties);
+      const additionalProperty = await this.runPropertyPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty);
+      content.push(additionalProperty);
+    }
 
     return this.renderBlock(content);
   }
 
-  runPropertyPreset(propertyName: string, property: CommonModel): Promise<string> {
-    return this.runPreset('property', { propertyName, property });
+  runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
+    return this.runPreset('property', { propertyName, property, type});
   }
 
   async renderAccessors(): Promise<string> {
@@ -53,15 +63,22 @@ ${this.indent(this.renderBlock(content, 2))}
       content.push(this.renderBlock([getter, setter]));
     }
 
+    if (this.model.additionalProperties !== undefined) {
+      const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties);
+      const getter = await this.runGetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty);
+      const setter = await this.runSetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty);
+      content.push(this.renderBlock([getter, setter]));
+    }
+
     return this.renderBlock(content, 2);
   }
 
-  runGetterPreset(propertyName: string, property: CommonModel): Promise<string> {
-    return this.runPreset('getter', { propertyName, property });
+  runGetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
+    return this.runPreset('getter', { propertyName, property, type });
   }
 
-  runSetterPreset(propertyName: string, property: CommonModel): Promise<string> {
-    return this.runPreset('setter', { propertyName, property });
+  runSetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
+    return this.runPreset('setter', { propertyName, property, type });
   }
 }
 
@@ -69,20 +86,30 @@ export const JAVA_DEFAULT_CLASS_PRESET: ClassPreset<ClassRenderer> = {
   self({ renderer }) {
     return renderer.defaultSelf();
   },
-  property({ renderer, propertyName, property }) {
+  property({ renderer, propertyName, property, type }) {
     propertyName = FormatHelpers.toCamelCase(propertyName);
-    return `private ${renderer.renderType(property)} ${propertyName};`;
+    let propertyType = renderer.renderType(property);
+    if (type === PropertyType.additionalProperty) {
+      propertyType = `Map<String, ${propertyType}>`;
+    }
+    return `private ${propertyType} ${propertyName};`;
   },
-  getter({ renderer, propertyName, property }) {
+  getter({ renderer, propertyName, property, type }) {
     propertyName = FormatHelpers.toCamelCase(propertyName);
-    const getterName = FormatHelpers.toPascalCase(propertyName);
-    const type = renderer.renderType(property);
-    return `public ${type} get${getterName}() { return this.${propertyName}; }`;
+    const getterName = `get${FormatHelpers.toPascalCase(propertyName)}`;
+    let getterType = renderer.renderType(property);
+    if (type === PropertyType.additionalProperty) {
+      getterType = `Map<String, ${getterType}>`;
+    }
+    return `public ${getterType} ${getterName}() { return this.${propertyName}; }`;
   },
-  setter({ renderer, propertyName, property }) {
+  setter({ renderer, propertyName, property, type }) {
     propertyName = FormatHelpers.toCamelCase(propertyName);
     const setterName = FormatHelpers.toPascalCase(propertyName);
-    const type = renderer.renderType(property);
-    return `public void set${setterName}(${type} ${propertyName}) { this.${propertyName} = ${propertyName}; }`;
+    let setterType = renderer.renderType(property);
+    if (type === PropertyType.additionalProperty) {
+      setterType = `Map<String, ${setterType}>`;
+    }
+    return `public void set${setterName}(${setterType} ${propertyName}) { this.${propertyName} = ${propertyName}; }`;
   },
 };
diff --git a/src/generators/javascript/JavaScriptRenderer.ts b/src/generators/javascript/JavaScriptRenderer.ts
index fb462f9e..e9151a0d 100644
--- a/src/generators/javascript/JavaScriptRenderer.ts
+++ b/src/generators/javascript/JavaScriptRenderer.ts
@@ -1,8 +1,8 @@
 import { AbstractRenderer } from '../AbstractRenderer';
 import { JavaScriptOptions } from './JavaScriptGenerator';
 
-import { FormatHelpers } from '../../helpers';
-import { CommonModel, CommonInputModel, Preset } from '../../models';
+import { getUniquePropertyName, FormatHelpers, DefaultPropertyNames } from '../../helpers';
+import { CommonModel, CommonInputModel, Preset, PropertyType } from '../../models';
 
 /**
  * Common renderer for JavaScript types
@@ -36,10 +36,15 @@ ${content}
       content.push(rendererProperty);
     }
 
+    if (this.model.additionalProperties !== undefined) {
+      const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties);
+      const additionalProperty = await this.runPropertyPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty);
+      content.push(additionalProperty);
+    }
     return this.renderBlock(content);
   }
 
-  runPropertyPreset(propertyName: string, property: CommonModel): Promise<string> {
-    return this.runPreset('property', { propertyName, property });
+  runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
+    return this.runPreset('property', { propertyName, property, type});
   }
 }
diff --git a/src/generators/typescript/TypeScriptRenderer.ts b/src/generators/typescript/TypeScriptRenderer.ts
index 3a324b73..4943b005 100644
--- a/src/generators/typescript/TypeScriptRenderer.ts
+++ b/src/generators/typescript/TypeScriptRenderer.ts
@@ -2,7 +2,8 @@ import { AbstractRenderer } from '../AbstractRenderer';
 import { TypeScriptOptions } from './TypeScriptGenerator';
 
 import { FormatHelpers } from '../../helpers';
-import { CommonModel, CommonInputModel, Preset } from '../../models';
+import { CommonModel, CommonInputModel, Preset, PropertyType } from '../../models';
+import { DefaultPropertyNames, getUniquePropertyName } from '../../helpers/NameHelpers';
 
 /**
  * Common renderer for TypeScript types
@@ -87,6 +88,9 @@ ${lines.map(line => ` * ${line}`).join('\n')}
  */`;
   }
 
+  /**
+   * Render all the properties for the model by calling the property preset per property.
+   */
   async renderProperties(): Promise<string> {
     const properties = this.model.properties || {};
     const content: string[] = [];
@@ -96,16 +100,31 @@ ${lines.map(line => ` * ${line}`).join('\n')}
       content.push(rendererProperty);
     }
 
+    if (this.model.additionalProperties !== undefined) {
+      const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties);
+      const additionalProperty = await this.runPropertyPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty);
+      content.push(additionalProperty);
+    }
+
     return this.renderBlock(content);
   }
 
-  renderProperty(propertyName: string, property: CommonModel): string {
+  renderProperty(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): string {
     const name = FormatHelpers.toCamelCase(propertyName);
-    const signature = this.renderTypeSignature(property, { isRequired: this.model.isRequired(propertyName) });
-    return `${name}${signature};`;
+    let signature: string;
+    switch (type) {
+    case PropertyType.property:
+      signature = this.renderTypeSignature(property, { isRequired: this.model.isRequired(propertyName) });
+      return `${name}${signature};`;
+    case PropertyType.additionalProperty:
+      signature = this.renderType(property);
+      return `${name}?: Map<String, ${signature}>;`;
+    default:
+      return '';
+    }
   }
 
-  runPropertyPreset(propertyName: string, property: CommonModel): Promise<string> {
-    return this.runPreset('property', { propertyName, property });
+  runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
+    return this.runPreset('property', { propertyName, property, type });
   }
 }
diff --git a/src/generators/typescript/renderers/ClassRenderer.ts b/src/generators/typescript/renderers/ClassRenderer.ts
index a5916de8..2ace927f 100644
--- a/src/generators/typescript/renderers/ClassRenderer.ts
+++ b/src/generators/typescript/renderers/ClassRenderer.ts
@@ -1,7 +1,7 @@
 import { TypeScriptRenderer } from '../TypeScriptRenderer';
 
-import { CommonModel, ClassPreset } from '../../../models';
-import { FormatHelpers } from '../../../helpers';
+import { CommonModel, ClassPreset, PropertyType } from '../../../models';
+import { getUniquePropertyName, FormatHelpers, DefaultPropertyNames } from '../../../helpers';
 
 /**
  * Renderer for TypeScript's `class` type
@@ -36,16 +36,22 @@ ${this.indent(this.renderBlock(content, 2))}
       const setter = await this.runSetterPreset(propertyName, property);
       content.push(this.renderBlock([getter, setter]));
     }
+    if (this.model.additionalProperties !== undefined) {
+      const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties);
+      const getter = await this.runGetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty);
+      const setter = await this.runSetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty);
+      content.push(this.renderBlock([getter, setter]));
+    }
 
     return this.renderBlock(content, 2);
   }
 
-  runGetterPreset(propertyName: string, property: CommonModel): Promise<string> {
-    return this.runPreset('getter', { propertyName, property });
+  runGetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
+    return this.runPreset('getter', { propertyName, property, type });
   }
 
-  runSetterPreset(propertyName: string, property: CommonModel): Promise<string> {
-    return this.runPreset('setter', { propertyName, property });
+  runSetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise<string> {
+    return this.runPreset('setter', { propertyName, property, type });
   }
 }
 
@@ -55,7 +61,7 @@ export const TS_DEFAULT_CLASS_PRESET: ClassPreset<ClassRenderer> = {
   },
   ctor({ renderer, model }) : string {
     const properties = model.properties || {};
-    const assigments = Object.keys(properties).map(property => {
+    const assignments = Object.keys(properties).map(property => {
       property = FormatHelpers.toCamelCase(property);
       return `this._${property} = input.${property};`;
     });
@@ -68,23 +74,34 @@ export const TS_DEFAULT_CLASS_PRESET: ClassPreset<ClassRenderer> = {
     return `constructor(input: {
 ${renderer.indent(renderer.renderBlock(ctorProperties))}
 }) {
-${renderer.indent(renderer.renderBlock(assigments))}
+${renderer.indent(renderer.renderBlock(assignments))}
 }`;
   },
-  property({ renderer, propertyName, property }): string {
-    return `private _${renderer.renderProperty(propertyName, property)}`;
+  property({ renderer, propertyName, property, type }): string {
+    return `private _${renderer.renderProperty(propertyName, property, type)}`;
   },
-  getter({ renderer, model, propertyName, property }): string {
+  getter({ renderer, model, propertyName, property, type }): string {
     const isRequired = model.isRequired(propertyName);
-    const formattedName = FormatHelpers.toCamelCase(propertyName);
-    const signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired });
-    return `get ${formattedName}()${signature} { return this._${formattedName}; }`;
+    propertyName = FormatHelpers.toCamelCase(propertyName);
+    let signature = ''; 
+    if (type === PropertyType.property) {
+      signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired });
+    } else if (type === PropertyType.additionalProperty) {
+      const additionalPropertyType = renderer.renderType(property);
+      signature = `: Map<String, ${additionalPropertyType}> | undefined`;
+    }
+    return `get ${propertyName}()${signature} { return this._${propertyName}; }`;
   },
-  setter({ renderer, model, propertyName, property }): string {
+  setter({ renderer, model, propertyName, property, type }): string {
     const isRequired = model.isRequired(propertyName);
-    const formattedName = FormatHelpers.toCamelCase(propertyName);
-    const signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired });
-    const arg = `${formattedName}${signature}`;
-    return `set ${formattedName}(${arg}) { this._${formattedName} = ${formattedName}; }`;
+    propertyName = FormatHelpers.toCamelCase(propertyName);
+    let signature = ''; 
+    if (type === PropertyType.property) {
+      signature = renderer.renderTypeSignature(property, { orUndefined: !isRequired });
+    } else if (type === PropertyType.additionalProperty) {
+      const additionalPropertyType = renderer.renderType(property);
+      signature = `: Map<String, ${additionalPropertyType}> | undefined`;
+    }
+    return `set ${propertyName}(${propertyName}${signature}) { this._${propertyName} = ${propertyName}; }`;
   },
 };
diff --git a/src/generators/typescript/renderers/InterfaceRenderer.ts b/src/generators/typescript/renderers/InterfaceRenderer.ts
index 5616b282..cd421779 100644
--- a/src/generators/typescript/renderers/InterfaceRenderer.ts
+++ b/src/generators/typescript/renderers/InterfaceRenderer.ts
@@ -26,7 +26,7 @@ export const TS_DEFAULT_INTERFACE_PRESET: InterfacePreset<InterfaceRenderer> = {
   async self({ renderer }) {
     return `export ${await renderer.defaultSelf()}`;
   },
-  property({ renderer, propertyName, property }) {
-    return renderer.renderProperty(propertyName, property);
-  },
+  property({ renderer, propertyName, property, type }) {
+    return renderer.renderProperty(propertyName, property, type);
+  }
 };
diff --git a/src/helpers/NameHelpers.ts b/src/helpers/NameHelpers.ts
new file mode 100644
index 00000000..f79d73ab
--- /dev/null
+++ b/src/helpers/NameHelpers.ts
@@ -0,0 +1,23 @@
+import { CommonModel } from 'models';
+
+/**
+ * Default property names for different aspects of the common model
+ */
+export enum DefaultPropertyNames {
+  additionalProperties = 'additionalProperties'
+}
+
+/**
+ * Recursively find the proper property name for additionalProperties
+ * 
+ * This function ensures that the property name for additionalProperties is unique 
+ * 
+ * @param rootModel 
+ * @param propertyName 
+ */
+export function getUniquePropertyName(rootModel: CommonModel, propertyName: string): string {
+  if (Object.keys(rootModel.properties || {}).includes(propertyName)) {
+    return getUniquePropertyName(rootModel, `_${propertyName}`);
+  }
+  return propertyName;
+}
diff --git a/src/helpers/index.ts b/src/helpers/index.ts
index ae92ed1b..fc4d76bb 100644
--- a/src/helpers/index.ts
+++ b/src/helpers/index.ts
@@ -1,2 +1,3 @@
 export * from './FormatHelpers';
 export * from './TypeHelpers';
+export * from './NameHelpers';
diff --git a/src/models/Preset.ts b/src/models/Preset.ts
index dbfaeb7b..37a32be7 100644
--- a/src/models/Preset.ts
+++ b/src/models/Preset.ts
@@ -16,9 +16,14 @@ export interface CommonPreset<R extends AbstractRenderer, O extends object = any
   additionalContent?: (args: PresetArgs<R, O>) => Promise<string> | string;
 }
 
+export enum PropertyType {
+  property,
+  additionalProperty
+}
 export interface PropertyArgs {
   propertyName: string;
   property: CommonModel;
+  type: PropertyType
 }
 
 export interface ClassPreset<R extends AbstractRenderer, O extends object = any> extends CommonPreset<R, O> {


Test patch:
diff --git a/test/generators/java/JavaGenerator.spec.ts b/test/generators/java/JavaGenerator.spec.ts
index 35b06983..27dd77bb 100644
--- a/test/generators/java/JavaGenerator.spec.ts
+++ b/test/generators/java/JavaGenerator.spec.ts
@@ -29,6 +29,7 @@ describe('JavaGenerator', () => {
   private Boolean marriage;
   private Object members;
   private Object[] arrayType;
+  private Map<String, Object> additionalProperties;
 
   public String getStreetName() { return this.streetName; }
   public void setStreetName(String streetName) { this.streetName = streetName; }
@@ -50,18 +51,22 @@ describe('JavaGenerator', () => {
 
   public Object[] getArrayType() { return this.arrayType; }
   public void setArrayType(Object[] arrayType) { this.arrayType = arrayType; }
+
+  public Map<String, Object> getAdditionalProperties() { return this.additionalProperties; }
+  public void setAdditionalProperties(Map<String, Object> additionalProperties) { this.additionalProperties = additionalProperties; }
 }`;
 
     const inputModel = await generator.process(doc);
     const model = inputModel.models['Address'];
 
     let classModel = await generator.renderClass(model, inputModel);
+    const expectedDependencies = ['import java.util.Map;'];
     expect(classModel.result).toEqual(expected);
-    expect(classModel.dependencies).toEqual([]);
+    expect(classModel.dependencies).toEqual(expectedDependencies);
 
     classModel = await generator.render(model, inputModel);
     expect(classModel.result).toEqual(expected);
-    expect(classModel.dependencies).toEqual([]);
+    expect(classModel.dependencies).toEqual(expectedDependencies);
   });
 
   test('should work custom preset for `class` type', async () => {
@@ -75,11 +80,18 @@ describe('JavaGenerator', () => {
     const expected = `public class CustomClass {
   @JsonProperty("property")
   private String property;
+  @JsonProperty("additionalProperties")
+  private Map<String, Object> additionalProperties;
 
   @JsonProperty("property")
   public String getProperty() { return this.property; }
   @JsonProperty("property")
   public void setProperty(String property) { this.property = property; }
+
+  @JsonProperty("additionalProperties")
+  public Map<String, Object> getAdditionalProperties() { return this.additionalProperties; }
+  @JsonProperty("additionalProperties")
+  public void setAdditionalProperties(Map<String, Object> additionalProperties) { this.additionalProperties = additionalProperties; }
 }`;
 
     generator = new JavaGenerator({ presets: [
@@ -105,12 +117,13 @@ describe('JavaGenerator', () => {
     const model = inputModel.models['CustomClass'];
 
     let classModel = await generator.renderClass(model, inputModel);
+    const expectedDependencies = ['import java.util.Map;'];
     expect(classModel.result).toEqual(expected);
-    expect(classModel.dependencies).toEqual([]);
+    expect(classModel.dependencies).toEqual(expectedDependencies);
 
     classModel = await generator.render(model, inputModel);
     expect(classModel.result).toEqual(expected);
-    expect(classModel.dependencies).toEqual([]);
+    expect(classModel.dependencies).toEqual(expectedDependencies);
   });
 
   test('should render `enum` type (string type)', async () => {
diff --git a/test/generators/java/presets/CommonPreset.spec.ts b/test/generators/java/presets/CommonPreset.spec.ts
index 532a9fc4..54a3f6a9 100644
--- a/test/generators/java/presets/CommonPreset.spec.ts
+++ b/test/generators/java/presets/CommonPreset.spec.ts
@@ -13,6 +13,7 @@ describe('JAVA_COMMON_PRESET', () => {
     const expected = `public class Clazz {
   private String stringProp;
   private Double numberProp;
+  private Map<String, Object> additionalProperties;
 
   public String getStringProp() { return this.stringProp; }
   public void setStringProp(String stringProp) { this.stringProp = stringProp; }
@@ -20,6 +21,9 @@ describe('JAVA_COMMON_PRESET', () => {
   public Double getNumberProp() { return this.numberProp; }
   public void setNumberProp(Double numberProp) { this.numberProp = numberProp; }
 
+  public Map<String, Object> getAdditionalProperties() { return this.additionalProperties; }
+  public void setAdditionalProperties(Map<String, Object> additionalProperties) { this.additionalProperties = additionalProperties; }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -31,12 +35,13 @@ describe('JAVA_COMMON_PRESET', () => {
     Clazz self = (Clazz) o;
       return 
         Objects.equals(this.stringProp, self.stringProp) &&
-        Objects.equals(this.numberProp, self.numberProp);
+        Objects.equals(this.numberProp, self.numberProp) &&
+        Objects.equals(this.additionalProperties, self.additionalProperties);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(stringProp, numberProp);
+    return Objects.hash(stringProp, numberProp, additionalProperties);
   }
 
   @Override
@@ -44,6 +49,7 @@ describe('JAVA_COMMON_PRESET', () => {
     return "class Clazz {\\n" +   
       "    stringProp: " + toIndentedString(stringProp) + "\\n" +
       "    numberProp: " + toIndentedString(numberProp) + "\\n" +
+      "    additionalProperties: " + toIndentedString(additionalProperties) + "\\n" +
     "}";
   }
 
@@ -64,8 +70,9 @@ describe('JAVA_COMMON_PRESET', () => {
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
+    const expectedDependencies = ['import java.util.Map;'];
     expect(classModel.result).toEqual(expected);
-    expect(classModel.dependencies).toEqual([]);
+    expect(classModel.dependencies).toEqual(expectedDependencies);
   });
 
   test('should skip rendering of disabled functions', async () => {
@@ -80,6 +87,7 @@ describe('JAVA_COMMON_PRESET', () => {
     const expected = `public class Clazz {
   private String stringProp;
   private Double numberProp;
+  private Map<String, Object> additionalProperties;
 
   public String getStringProp() { return this.stringProp; }
   public void setStringProp(String stringProp) { this.stringProp = stringProp; }
@@ -87,9 +95,12 @@ describe('JAVA_COMMON_PRESET', () => {
   public Double getNumberProp() { return this.numberProp; }
   public void setNumberProp(Double numberProp) { this.numberProp = numberProp; }
 
+  public Map<String, Object> getAdditionalProperties() { return this.additionalProperties; }
+  public void setAdditionalProperties(Map<String, Object> additionalProperties) { this.additionalProperties = additionalProperties; }
+
   @Override
   public int hashCode() {
-    return Objects.hash(stringProp, numberProp);
+    return Objects.hash(stringProp, numberProp, additionalProperties);
   }
 }`;
 
@@ -104,7 +115,8 @@ describe('JAVA_COMMON_PRESET', () => {
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
+    const expectedDependencies = ['import java.util.Map;'];
     expect(classModel.result).toEqual(expected);
-    expect(classModel.dependencies).toEqual([]);
+    expect(classModel.dependencies).toEqual(expectedDependencies);
   });
 });
diff --git a/test/generators/java/presets/ConstraintsPreset.spec.ts b/test/generators/java/presets/ConstraintsPreset.spec.ts
index 4e442ebb..99eec2ef 100644
--- a/test/generators/java/presets/ConstraintsPreset.spec.ts
+++ b/test/generators/java/presets/ConstraintsPreset.spec.ts
@@ -23,6 +23,7 @@ describe('JAVA_CONSTRAINTS_PRESET', () => {
   private Double maxNumberProp;
   private Object[] arrayProp;
   private String stringProp;
+  private Map<String, Object> additionalProperties;
 
   @NotNull
   @Min(0)
@@ -42,13 +43,17 @@ describe('JAVA_CONSTRAINTS_PRESET', () => {
   @Size(min=3)
   public String getStringProp() { return this.stringProp; }
   public void setStringProp(String stringProp) { this.stringProp = stringProp; }
+
+  public Map<String, Object> getAdditionalProperties() { return this.additionalProperties; }
+  public void setAdditionalProperties(Map<String, Object> additionalProperties) { this.additionalProperties = additionalProperties; }
 }`;
 
     const inputModel = await generator.process(doc);
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
+    const expectedDependencies = ['import java.util.Map;', 'import javax.validation.constraints.*;'];
     expect(classModel.result).toEqual(expected);
-    expect(classModel.dependencies).toEqual(['import javax.validation.constraints.*;']);
+    expect(classModel.dependencies).toEqual(expectedDependencies);
   });
 });
diff --git a/test/generators/java/presets/DescriptionPreset.spec.ts b/test/generators/java/presets/DescriptionPreset.spec.ts
index 89450586..df59929f 100644
--- a/test/generators/java/presets/DescriptionPreset.spec.ts
+++ b/test/generators/java/presets/DescriptionPreset.spec.ts
@@ -22,6 +22,7 @@ describe('JAVA_DESCRIPTION_PRESET', () => {
  */
 public class Clazz {
   private String prop;
+  private Map<String, Object> additionalProperties;
 
   /**
    * Description for prop
@@ -29,14 +30,18 @@ public class Clazz {
    */
   public String getProp() { return this.prop; }
   public void setProp(String prop) { this.prop = prop; }
+
+  public Map<String, Object> getAdditionalProperties() { return this.additionalProperties; }
+  public void setAdditionalProperties(Map<String, Object> additionalProperties) { this.additionalProperties = additionalProperties; }
 }`;
 
     const inputModel = await generator.process(doc);
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
+    const expectedDependencies = ['import java.util.Map;'];
     expect(classModel.result).toEqual(expected);
-    expect(classModel.dependencies).toEqual([]);
+    expect(classModel.dependencies).toEqual(expectedDependencies);
   });
 
   test('should render description and examples for enum', async () => {
diff --git a/test/generators/java/presets/JacksonPreset.spec.ts b/test/generators/java/presets/JacksonPreset.spec.ts
index 9cd82809..cb198ddd 100644
--- a/test/generators/java/presets/JacksonPreset.spec.ts
+++ b/test/generators/java/presets/JacksonPreset.spec.ts
@@ -18,6 +18,7 @@ describe('JAVA_DESCRIPTION_PRESET', () => {
     const expected = `public class Clazz {
   private Double minNumberProp;
   private Double maxNumberProp;
+  private Map<String, Object> additionalProperties;
 
   @JsonProperty("min_number_prop")
   public Double getMinNumberProp() { return this.minNumberProp; }
@@ -26,13 +27,17 @@ describe('JAVA_DESCRIPTION_PRESET', () => {
   @JsonProperty("max_number_prop")
   public Double getMaxNumberProp() { return this.maxNumberProp; }
   public void setMaxNumberProp(Double maxNumberProp) { this.maxNumberProp = maxNumberProp; }
+
+  public Map<String, Object> getAdditionalProperties() { return this.additionalProperties; }
+  public void setAdditionalProperties(Map<String, Object> additionalProperties) { this.additionalProperties = additionalProperties; }
 }`;
 
     const inputModel = await generator.process(doc);
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
+    const expectedDependencies = ['import java.util.Map;', 'import com.fasterxml.jackson.annotation.*;'];
     expect(classModel.result).toEqual(expected);
-    expect(classModel.dependencies).toEqual(['import com.fasterxml.jackson.annotation.*;']);
+    expect(classModel.dependencies).toEqual(expectedDependencies);
   });
 });
diff --git a/test/generators/javascript/JavaScriptGenerator.spec.ts b/test/generators/javascript/JavaScriptGenerator.spec.ts
index c453dcdb..9b149f9e 100644
--- a/test/generators/javascript/JavaScriptGenerator.spec.ts
+++ b/test/generators/javascript/JavaScriptGenerator.spec.ts
@@ -29,6 +29,7 @@ describe('JavaScriptGenerator', () => {
   marriage;
   members;
   arrayType;
+  additionalProperties;
 
   constructor(input) {
     this.streetName = input.streetName;
@@ -99,6 +100,7 @@ describe('JavaScriptGenerator', () => {
     };
     const expected = `export class CustomClass {
   #property;
+  #additionalProperties;
 
   constructor(input) {
     this.#property = input.property;
diff --git a/test/generators/typescript/TypeScriptGenerator.spec.ts b/test/generators/typescript/TypeScriptGenerator.spec.ts
index d24ed091..cefc24b9 100644
--- a/test/generators/typescript/TypeScriptGenerator.spec.ts
+++ b/test/generators/typescript/TypeScriptGenerator.spec.ts
@@ -30,6 +30,7 @@ describe('TypeScriptGenerator', () => {
   private _members?: string | number | boolean;
   private _tupleType?: [string, number];
   private _arrayType: Array<string>;
+  private _additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null | number>;
 
   constructor(input: {
     streetName: string,
@@ -74,6 +75,9 @@ describe('TypeScriptGenerator', () => {
 
   get arrayType(): Array<string> { return this._arrayType; }
   set arrayType(arrayType: Array<string>) { this._arrayType = arrayType; }
+
+  get additionalProperties(): Map<String, object | string | number | Array<unknown> | boolean | null | number> | undefined { return this._additionalProperties; }
+  set additionalProperties(additionalProperties: Map<String, object | string | number | Array<unknown> | boolean | null | number> | undefined) { this._additionalProperties = additionalProperties; }
 }`;
 
     const inputModel = await generator.process(doc);
@@ -99,6 +103,8 @@ describe('TypeScriptGenerator', () => {
     const expected = `export class CustomClass {
   @JsonProperty("property")
   private _property?: string;
+  @JsonProperty("additionalProperties")
+  private _additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null | number>;
 
   constructor(input: {
     property?: string,
@@ -108,6 +114,9 @@ describe('TypeScriptGenerator', () => {
 
   get property(): string | undefined { return this._property; }
   set property(property: string | undefined) { this._property = property; }
+
+  get additionalProperties(): Map<String, object | string | number | Array<unknown> | boolean | null | number> | undefined { return this._additionalProperties; }
+  set additionalProperties(additionalProperties: Map<String, object | string | number | Array<unknown> | boolean | null | number> | undefined) { this._additionalProperties = additionalProperties; }
 }`;
 
     generator = new TypeScriptGenerator({ presets: [
@@ -154,6 +163,7 @@ ${content}`;
   members?: string | number | boolean;
   tupleType?: [string, number];
   arrayType: Array<string>;
+  additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null | number>;
 }`;
 
     const interfaceGenerator = new TypeScriptGenerator({modelType: 'interface'});
@@ -175,6 +185,7 @@ ${content}`;
     };
     const expected = `export interface CustomInterface {
   property?: string;
+  additionalProperties?: Map<String, object | string | number | Array<unknown> | boolean | null | number>;
 }`;
 
     generator = new TypeScriptGenerator({ presets: [
diff --git a/test/helpers/NameHelpers.spec.ts b/test/helpers/NameHelpers.spec.ts
new file mode 100644
index 00000000..d6fcd7a0
--- /dev/null
+++ b/test/helpers/NameHelpers.spec.ts
@@ -0,0 +1,21 @@
+import { DefaultPropertyNames, getUniquePropertyName } from '../../src/helpers'; 
+import { CommonModel } from '../../src/models';
+
+describe('NameHelpers', () => {
+  describe('getUniquePropertyName', () => {
+    test('should return correct name for additionalProperties', () => {  
+      const model = CommonModel.toCommonModel({});
+
+      const additionalPropertiesName = getUniquePropertyName(model, DefaultPropertyNames.additionalProperties);
+      
+      expect(additionalPropertiesName).toEqual('additionalProperties');
+    });
+    test('should handle duplicate names', () => {  
+      const model = CommonModel.toCommonModel({properties: {additionalProperties: {}}});
+
+      const additionalPropertiesName = getUniquePropertyName(model, DefaultPropertyNames.additionalProperties);
+      
+      expect(additionalPropertiesName).toEqual('_additionalProperties');
+    });
+  });
+});