diff --git a/src/index.ts b/src/index.ts index 1943b17d..f9ad477e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import {validate} from './validator' import {isDeepStrictEqual} from 'util' import {link} from './linker' import {validateOptions} from './optionValidator' +import {JSONSchema as LinkedJSONSchema} from './types/JSONSchema' export {EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema} from './types/JSONSchema' @@ -76,6 +77,10 @@ export interface Options { * Generate unknown type instead of any */ unknownAny: boolean + /** + * Custom function to provide a type name for a given schema + */ + customName?: (schema: LinkedJSONSchema, keyNameFromDefinition: string | undefined) => string | undefined } export const DEFAULT_OPTIONS: Options = { diff --git a/src/parser.ts b/src/parser.ts index a52479fc..7e82d7f6 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -147,7 +147,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: schema.allOf!.map(_ => parse(_, options, undefined, processed, usedNames)), type: 'INTERSECTION', } @@ -157,14 +157,14 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), } case 'ANY_OF': return { comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: schema.anyOf!.map(_ => parse(_, options, undefined, processed, usedNames)), type: 'UNION', } @@ -173,7 +173,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'BOOLEAN', } case 'CUSTOM_TYPE': @@ -182,7 +182,7 @@ function parseNonLiteral( deprecated: schema.deprecated, keyName, params: schema.tsType!, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'CUSTOM_TYPE', } case 'NAMED_ENUM': @@ -190,7 +190,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition ?? keyName, usedNames)!, + standaloneName: standaloneName(schema, keyNameFromDefinition ?? keyName, usedNames, options)!, params: schema.enum!.map((_, n) => ({ ast: parseLiteral(_, undefined), keyName: schema.tsEnumNames![n], @@ -204,7 +204,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'NEVER', } case 'NULL': @@ -212,7 +212,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'NULL', } case 'NUMBER': @@ -220,14 +220,14 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'NUMBER', } case 'OBJECT': return { comment: schema.description, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'OBJECT', deprecated: schema.deprecated, } @@ -236,7 +236,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: schema.oneOf!.map(_ => parse(_, options, undefined, processed, usedNames)), type: 'UNION', } @@ -247,7 +247,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'STRING', } case 'TYPED_ARRAY': @@ -261,7 +261,7 @@ function parseNonLiteral( keyName, maxItems, minItems, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: schema.items.map(_ => parse(_, options, undefined, processed, usedNames)), type: 'TUPLE', } @@ -276,7 +276,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: parse(schema.items!, options, `{keyNameFromDefinition}Items`, processed, usedNames), type: 'ARRAY', } @@ -286,7 +286,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: (schema.type as JSONSchema4TypeName[]).map(type => { const member: LinkedJSONSchema = {...omit(schema, '$id', 'description', 'title'), type} return parse(maybeStripDefault(member as any), options, undefined, processed, usedNames) @@ -298,7 +298,7 @@ function parseNonLiteral( comment: schema.description, deprecated: schema.deprecated, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), params: schema.enum!.map(_ => parseLiteral(_, undefined)), type: 'UNION', } @@ -320,7 +320,7 @@ function parseNonLiteral( params: Array(Math.max(maxItems, minItems) || 0).fill(params), // if there is no maximum, then add a spread item to collect the rest spreadParam: maxItems >= 0 ? undefined : params, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'TUPLE', } } @@ -330,7 +330,7 @@ function parseNonLiteral( deprecated: schema.deprecated, keyName, params, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'ARRAY', } } @@ -343,8 +343,10 @@ function standaloneName( schema: LinkedJSONSchema, keyNameFromDefinition: string | undefined, usedNames: UsedNames, + options: Options, ): string | undefined { - const name = schema.title || schema.$id || keyNameFromDefinition + const name = + options.customName?.(schema, keyNameFromDefinition) || schema.title || schema.$id || keyNameFromDefinition if (name) { return generateName(name, usedNames) } @@ -358,7 +360,7 @@ function newInterface( keyName?: string, keyNameFromDefinition?: string, ): TInterface { - const name = standaloneName(schema, keyNameFromDefinition, usedNames)! + const name = standaloneName(schema, keyNameFromDefinition, usedNames, options)! return { comment: schema.description, deprecated: schema.deprecated, diff --git a/test/__snapshots__/test/test.ts.md b/test/__snapshots__/test/test.ts.md index 53378d6b..c4b78567 100644 --- a/test/__snapshots__/test/test.ts.md +++ b/test/__snapshots__/test/test.ts.md @@ -748,6 +748,31 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## customName.1.js + +> Expected output to match snapshot for e2e test: customName.1.js + + `/* eslint-disable */␊ + /**␊ + * This file was automatically generated by json-schema-to-typescript.␊ + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,␊ + * and run json-schema-to-typescript to regenerate this file.␊ + */␊ + ␊ + export interface FooTitle {␊ + propaaa?: CustomPrefixDefaa;␊ + propaab?: CustomPrefixDefaa;␊ + propbbb?: PropbbbTitle;␊ + [k: string]: unknown;␊ + }␊ + export interface CustomPrefixDefaa {␊ + [k: string]: unknown;␊ + }␊ + export interface PropbbbTitle {␊ + [k: string]: unknown;␊ + }␊ + ` + ## customType.js > Expected output to match snapshot for e2e test: customType.js diff --git a/test/__snapshots__/test/test.ts.snap b/test/__snapshots__/test/test.ts.snap index b4205e7d..b29536b4 100644 Binary files a/test/__snapshots__/test/test.ts.snap and b/test/__snapshots__/test/test.ts.snap differ diff --git a/test/e2e/customName.1.ts b/test/e2e/customName.1.ts new file mode 100644 index 00000000..378ed896 --- /dev/null +++ b/test/e2e/customName.1.ts @@ -0,0 +1,24 @@ +import {Options} from '../../src/' + +export const input = { + type: 'object', + id: 'FooId', + title: 'FooTitle', + definitions: { + defaa: {id: 'defaa-id'}, + defab: {id: 'defab-id'}, + }, + properties: { + propaaa: {$ref: '#/definitions/defaa'}, + propaab: {$ref: '#/definitions/defaa'}, + propbbb: {id: 'propbbb-id', title: 'propbbb-title'}, + }, +} + +export const options: Partial = { + customName: (_schema, keyName) => { + if (!keyName) + return undefined // Fallback to default naming + return 'CustomPrefix_' + keyName + }, +}