Bug Report Checklist
Description
A property whose additionalProperties schema declares no type is an "any type" map. When such a property is
inherited through allOf, the generated value type silently changes from "any" to "free-form object".
The same property, declared once and inherited once, produces two different data types in the same run.
openapi-generator version
7.25.0-SNAPSHOT, built from master at commit 4aed9c1c635.
Not a recent regression: the responsible code path (mergeProperties → cloneSchema) has been in place
for several releases.
OpenAPI declaration file content or url
openapi: 3.0.3
info:
title: allOf inherited free-form map
version: 1.0.0
paths:
/child:
get:
operationId: getChild
responses:
"200":
description: ok
content:
application/json:
schema:
$ref: "#/components/schemas/Child"
components:
schemas:
Base:
required:
- requiredLoose
properties:
requiredLoose:
type: object
additionalProperties:
description: can be any type
optionalLoose:
type: object
additionalProperties:
description: can be any type
realObject:
type: object
additionalProperties:
type: object
Child:
allOf:
- $ref: "#/components/schemas/Base"
properties:
extra:
type: string
Generation Details
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate \
-i spec.yaml -g typescript-fetch -o out
typescript-fetch is used here because the difference is most visible (any vs object),
but the root cause is in DefaultCodegen/ModelUtils and is therefore generator-agnostic.
Steps to reproduce
- Save the spec above as
spec.yaml
- Run the command above
- Compare
out/models/Base.ts with out/models/Child.ts
Actual output
// models/Base.ts
requiredLoose: { [key: string]: any; };
optionalLoose?: { [key: string]: any; };
realObject?: { [key: string]: object; };
// models/Child.ts <-- inherited from Base via allOf
requiredLoose: { [key: string]: object; }; // changed
optionalLoose?: { [key: string]: object; }; // changed
realObject?: { [key: string]: object; };
Expected output
Child should carry the exact same types as Base for the inherited properties:
requiredLoose: { [key: string]: any; };
optionalLoose?: { [key: string]: any; };
realObject?: { [key: string]: object; };
Note realObject (which genuinely declares type: object) is correct in both — only the
typeless / "any type" schemas are affected.
Suggest a fix
The allOf composition branch in DefaultCodegen.fromModel calls mergeProperties():
https://github.com/OpenAPITools/openapi-generator/blob/4aed9c1c635/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java#L2854
mergeProperties() deep-copies every inherited property schema via ModelUtils.cloneSchema(), which
delegates to AnnotationsUtils.clone() — a JSON round-trip. swagger-core's deserializer materializes a
sub-schema that has no type as an ObjectSchema (i.e. type: object).
This can be observed directly:
Schema inner = new Schema().description("can be any type");
Schema outer = new ObjectSchema().additionalProperties(inner);
// before: class = Schema, type = null, isFreeFormObject = false
Schema cloned = ModelUtils.cloneSchema(outer, false);
// after: class = ObjectSchema, type = object, isFreeFormObject = true
Consequently, in DefaultCodegen.getPrimitiveType() the ModelUtils.isFreeFormObject(...) branch now
matches and returns "object", so execution never reaches the ModelUtils.isAnyType(...) branch that
would return "AnyType" (mapped to any by the TypeScript generators, Object/interface{}/... elsewhere).
cloneSchema() already has a guard that preserves a custom top-level type across the round-trip, but it
does not cover sub-schemas. Extending the same idea — after cloning, walk the schema tree and clear the type
wherever the original did not declare one — fixes it.
I have a patch with tests ready and will open a PR shortly.
Related issues/PRs
Searched open and closed issues/PRs for cloneSchema, mergeProperties, and allOf + free-form/additionalProperties
combinations; I could not find an existing report. #22150 is also about allOf-inherited properties but concerns
Spring bean-validation annotations, not the resolved data type.
Bug Report Checklist
Description
A property whose
additionalPropertiesschema declares notypeis an "any type" map. When such a property isinherited through
allOf, the generated value type silently changes from "any" to "free-form object".The same property, declared once and inherited once, produces two different data types in the same run.
openapi-generator version
7.25.0-SNAPSHOT, built frommasterat commit4aed9c1c635.Not a recent regression: the responsible code path (
mergeProperties→cloneSchema) has been in placefor several releases.
OpenAPI declaration file content or url
Generation Details
typescript-fetchis used here because the difference is most visible (anyvsobject),but the root cause is in
DefaultCodegen/ModelUtilsand is therefore generator-agnostic.Steps to reproduce
spec.yamlout/models/Base.tswithout/models/Child.tsActual output
Expected output
Childshould carry the exact same types asBasefor the inherited properties:Note
realObject(which genuinely declarestype: object) is correct in both — only thetypeless / "any type" schemas are affected.
Suggest a fix
The
allOfcomposition branch inDefaultCodegen.fromModelcallsmergeProperties():https://github.com/OpenAPITools/openapi-generator/blob/4aed9c1c635/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java#L2854
mergeProperties()deep-copies every inherited property schema viaModelUtils.cloneSchema(), whichdelegates to
AnnotationsUtils.clone()— a JSON round-trip. swagger-core's deserializer materializes asub-schema that has no
typeas anObjectSchema(i.e.type: object).This can be observed directly:
Consequently, in
DefaultCodegen.getPrimitiveType()theModelUtils.isFreeFormObject(...)branch nowmatches and returns
"object", so execution never reaches theModelUtils.isAnyType(...)branch thatwould return
"AnyType"(mapped toanyby the TypeScript generators,Object/interface{}/... elsewhere).cloneSchema()already has a guard that preserves a custom top-level type across the round-trip, but itdoes not cover sub-schemas. Extending the same idea — after cloning, walk the schema tree and clear the type
wherever the original did not declare one — fixes it.
I have a patch with tests ready and will open a PR shortly.
Related issues/PRs
Searched open and closed issues/PRs for
cloneSchema,mergeProperties, and allOf + free-form/additionalPropertiescombinations; I could not find an existing report. #22150 is also about
allOf-inherited properties but concernsSpring bean-validation annotations, not the resolved data type.