11import type { OpenAPIV3 } from "openapi-types" ;
22import { traverse } from './utils/OpenApiTraverser' ;
33import isEqual from 'lodash.isequal' ;
4- import { compressMultipleUnderscores , isPrimitiveType , resolveObj , isReferenceObject , isEmptyObjectSchema , is_simple_ref } from './utils/helper' ;
4+ import { compressMultipleUnderscores , isPrimitiveType , resolveObj , isReferenceObject , isEmptyObjectSchema , is_simple_ref , toSnakeCase } from './utils/helper' ;
55import logger from "./utils/logger" ;
66
77
@@ -21,7 +21,6 @@ export class SchemaModifier {
2121 this . convertNullTypeToNullValue ( schema )
2222 this . deduplicateOneOfWithArrayType ( schema )
2323 this . collapseSingleItemComposite ( schema ) ;
24- this . removeArrayOfMapWrapper ( schema )
2524 } ,
2625 onSchema : ( schema , schemaName ) => {
2726 if ( ! schema || isReferenceObject ( schema ) ) return ;
@@ -33,7 +32,6 @@ export class SchemaModifier {
3332 this . deduplicateOneOfWithArrayType ( schema )
3433 this . collapseSingleItemComposite ( schema ) ;
3534 this . collapseOneOfObjectPropContainsTitleSchema ( schema )
36- this . removeArrayOfMapWrapper ( schema )
3735 this . convertOneOfToMinMaxProperties ( schema )
3836 } ,
3937 } ) ;
@@ -280,50 +278,70 @@ export class SchemaModifier {
280278 }
281279
282280 /**
283- * Transforms SchemaObject that single-key maps (`minProperties = 1` and `maxProperties = 1`) into standard schema by reconstructing
284- * the additional property definitions.
281+ * Extracts type name from $ref or title
282+ * Note: Schema names have already been sanitized by Sanitizer, so '___' prefixes are already removed
283+ **/
284+ private getTypeName ( schema : OpenAPIV3 . SchemaObject | OpenAPIV3 . ReferenceObject ) : string | null {
285+ if ( '$ref' in schema ) {
286+ const parts = schema . $ref . split ( '/' ) ;
287+ return parts [ parts . length - 1 ] ;
288+ }
289+ if ( 'title' in schema && schema . title ) {
290+ return schema . title ;
291+ }
292+ return null ;
293+ }
294+
295+ /**
296+ * Transforms SchemaObject that single-key maps (`minProperties = 1` and `maxProperties = 1`) into a new wrapper schema.
297+ *
298+ *
285299 * Example:
286300 * Input:
287301 * {
288302 * type: "object",
289303 * additionalProperties: {
290- * - ref: "#/components/schemas/Model "
304+ * $ ref: "#/components/schemas/SortOrder "
291305 * },
292306 * minProperties: 1,
293307 * maxProperties: 1,
294- * };
295- * Model:
296- * properties: {
297- * properties1: string
298- * properties2: string
299- * }
300- *
308+ * }
301309 *
302- *Output:
303- * {
304- * ref: "#/components/schemas/Example
305- * }
306- *
307- * Model:
308- * properties: {
309- * field: string
310- * properties1: string
311- * properties2: string
312- * }
310+ * Output:
311+ * {
312+ * type: "object",
313+ * title: "SortOrderMap",
314+ * properties: {
315+ * field: { type: "string" },
316+ * sort_order: { $ref: "#/components/schemas/SortOrder" }
317+ * },
318+ * required: ["field", "sort_order"]
319+ * }
313320 *
314321 **/
315322 simplifySingleMapSchema ( schema : OpenAPIV3 . SchemaObject , visit : Set < any > ) : void {
316323 if ( schema . type === 'object' && typeof schema . additionalProperties === 'object' &&
317324 ! Array . isArray ( schema . additionalProperties ) && schema . minProperties === 1 && schema . maxProperties === 1 ) {
318325
319- const reconstructAdditionalPropertySchema = this . reconstructAdditionalPropertySchema ( schema . additionalProperties , visit ) ;
326+ const valueSchema = schema . additionalProperties ;
327+ const typeName = this . getTypeName ( valueSchema ) || 'Value' ; // Use 'Value' as fallback
320328
321- Object . assign ( schema , reconstructAdditionalPropertySchema )
329+ // Create new wrapper schema
330+ // Avoid collision with the reserved 'field' key property name
331+ const rawPropertyName = toSnakeCase ( typeName ) ;
332+ const valuePropertyName = rawPropertyName === 'field' ? `${ rawPropertyName } _value` : rawPropertyName ;
333+ const wrapperTitle = schema . title || `${ typeName } Map` ;
334+
335+ schema . title = wrapperTitle ;
336+ schema . properties = {
337+ field : { type : 'string' as const } ,
338+ [ valuePropertyName ] : valueSchema
339+ } ;
340+ schema . required = [ 'field' , valuePropertyName ] ;
322341
323342 delete schema . additionalProperties ;
324343 delete schema . minProperties ;
325344 delete schema . maxProperties ;
326- delete schema . type
327345 if ( 'propertyNames' in schema ) {
328346 delete schema . propertyNames ;
329347 }
@@ -449,44 +467,6 @@ export class SchemaModifier {
449467 logger . info ( `Converted additionalProperties to named property '${ propertyName } ' with type: object` ) ;
450468 }
451469
452- /**
453- * Removes the array wrapper if the schema is an array of maps (additionalProperties).
454- * Converts array of objects with only additionalProperties into just the additionalProperties schema.
455- *
456- * Example:
457- * Input:
458- * {
459- * type: "array",
460- * items: {
461- * type: "object",
462- * additionalProperties: {
463- * $ref: "#/components/schemas/Value"
464- * }
465- * }
466- * }
467- *
468- * Output:
469- * {
470- * type: "object",
471- * additionalProperties: {
472- * $ref: "#/components/schemas/Value"
473- * }
474- * }
475- **/
476- removeArrayOfMapWrapper ( schema : OpenAPIV3 . SchemaObject ) : void {
477- if ( schema . type === 'array' && schema . items && typeof schema . items === 'object' && ! ( '$ref' in schema . items ) ) {
478- const items = schema . items as OpenAPIV3 . SchemaObject ;
479-
480- if ( items . type === 'object' && items . additionalProperties && ! items . properties ) {
481- ( schema as any ) . type = 'object' ;
482- schema . additionalProperties = items . additionalProperties ;
483- delete ( schema as any ) . items ;
484-
485- logger . info ( `Removed array wrapper from array of maps schema` ) ;
486- }
487- }
488- }
489-
490470 /**
491471 * Converts oneOf pattern with single-property objects into minProperties/maxProperties pattern.
492472 * For AggregationContainer
0 commit comments