26401-medium-json-schema-to-typescript

Back

type JSONSchema2TS<T>
  = T extends {type: 'string', enum?: infer E} ? E extends string[] ? E[number] : string
  : T extends {type: 'number', enum?: infer E} ? E extends number[] ? E[number] : number
  : T extends {type: 'boolean'} ? boolean
  : T extends {type: 'object', properties?: infer P, required?: infer R}
    ? P extends object ? ReturnType<<O extends (
        {[K in keyof P as [K] extends [R extends string[] ? R[number] : never] ? never : K]?: JSONSchema2TS<P[K]>}
      & {[K in keyof P as [K] extends [R extends string[] ? R[number] : never] ? K : never]: JSONSchema2TS<P[K]>}
      )>() => {[K in keyof O]: O[K]}>
    : Record<string, unknown>
  : T extends {type: 'array', items?: infer I} ? I extends object ? JSONSchema2TS<I>[] : unknown[]
  : T

Playground

Solution by teamchong #33591

type JSONPrimitiveType2TSType = {
  'string': string
  'number': number
  'boolean': boolean
}
type JSONPrimitiveType = keyof JSONPrimitiveType2TSType
type JSONType = JSONPrimitiveType | 'object' | 'array'
type JSONSchema = {
  type: JSONType,
  enum?: JSONPrimitiveType2TSType[JSONPrimitiveType][]
  properties?: Record<string, JSONSchema>
  items?: JSONSchema
  required?: string[]
}

type JSONObject2TS<T extends JSONSchema> = 
  T['properties'] extends Record<string, JSONSchema>
    ? T['required'] extends string[]
      ? Omit<{
          [k in Exclude<keyof T['properties'], T['required'][number]>]?: JSONSchema2TS<T['properties'][k]>
        } & {
          [k in T['required'][number]]: JSONSchema2TS<T['properties'][k & keyof T['properties']]>
        }, never>
      : {
          [k in keyof T['properties']]?: JSONSchema2TS<T['properties'][k]>
        }
    : Record<string, unknown>

type JSONArray2TS<T extends JSONSchema> = 
  T['items'] extends JSONSchema
    ? JSONSchema2TS<T['items']>[]
    : unknown[]

type JSONSchema2TS<T extends JSONSchema> = 
  T['type'] extends JSONPrimitiveType
    ? T['enum'] extends JSONPrimitiveType2TSType[JSONPrimitiveType][]
      ? T['enum'][number]
      : JSONPrimitiveType2TSType[T['type']]
    : T['type'] extends 'object'
      ? JSONObject2TS<T>
      : T['type'] extends 'array'
        ? JSONArray2TS<T>
        : never

Solution by hui0808 #33434

写完自己都晕了。

type Json = {
  type: TypeAll;
  enum?: any[];
  items?: Json;
  properties?: Record<PropertyKey, Json>;
  required?: Array<any>;
};
type PrimitiveTypeMap = {
  string: string;
  number: number;
  boolean: boolean;
};
type PrimitiveType = "string" | "number" | "boolean";
type ObjectType = "object";
type ArrayType = "array";
type CompositeType = ObjectType | ArrayType;
type TypeAll = PrimitiveType | CompositeType;

type IntersectionToObj<T> = {
  [P in keyof T]: T[P];
};

type JSONSchema2TS<T extends Json> = T["type"] extends PrimitiveType
  ? T["enum"] extends any[]
    ? T["enum"][number]
    : PrimitiveTypeMap[T["type"]]
  : T["type"] extends ObjectType
  ? T["properties"] extends Record<PropertyKey, Json>
    ? T["required"] extends any[]
      ? IntersectionToObj<
          {
            [P in T["required"][number]]: JSONSchema2TS<T["properties"][P]>;
          } & {
            [P in Exclude<
              keyof T["properties"],
              T["required"][number]
            >]?: JSONSchema2TS<T["properties"][P]>;
          }
        >
      : { [P in keyof T["properties"]]?: JSONSchema2TS<T["properties"][P]> }
    : Record<string, unknown>
  : T["type"] extends ArrayType
  ? T["items"] extends Json
    ? JSONSchema2TS<T["items"]>[]
    : unknown[]
  : T;

Solution by Vampirelee #32577

type JSONSchema2TS<T> = T extends { type: infer U }
  ? U extends "string"
    ? T extends { enum: infer E extends Array<unknown> }
      ? E[number]
      : string
    : U extends "number"
    ? T extends { enum: infer E extends Array<unknown> }
      ? E[number]
      : number
    : U extends "boolean"
    ? boolean
    : U extends "object"
    ? T extends { properties: infer P }
      ? T extends { required: infer R extends any[] }
        ? Omit<
            { [K in R[number]]: JSONSchema2TS<P[K]> } & {
              [K in Exclude<keyof P, R[number]>]?: JSONSchema2TS<P[K]>;
            },
            never
          >
        : { [K in keyof P]?: JSONSchema2TS<P[K]> }
      : Record<string, unknown>
    : U extends "array"
    ? T extends { items: infer I }
      ? JSONSchema2TS<I>[]
      : unknown[]
    : never
  : never;

Solution by vangie #32211

type SimpleType = {
  string: string,
  number: number,
  boolean: boolean,
  object: Record<string, unknown>
  array: unknown[]
}

type HandleSimpleType<T, Type> = T extends { enum: unknown[] } 
? T['enum'][number] 
: Type extends keyof SimpleType 
  ? SimpleType[Type] 
  : never

type Copy<T> = {
  [K in keyof T]: T[K]
}

type HandleProperties<T, Required extends string[] = []> = Copy<{
  [K in keyof T as K extends Required[number] ? K : never]: JSONSchema2TS<T[K]>
} & {
  [K in keyof T as K extends Required[number] ? never : K]?: JSONSchema2TS<T[K]>
}>

type JSONSchema2TS<T> = T extends { properties: infer Properties }
? (T extends { required: infer Required extends string[] } ? HandleProperties<Properties, Required> : HandleProperties<Properties>)
: T extends { items: infer Array }
  ? JSONSchema2TS<Array>[]
  : T extends { type: infer Type }
    ? HandleSimpleType<T, Type>
    : never

先将要处理的类型分为简单、复杂类型,带有 properties 的对象和带有 items 的数组为复杂类型,其他的为简单类型。 然后 JSONSchema2TS 就可以简单的分为三步了了,前两步处理复杂类型的对象和数组,如果不是这两个类型,最后一步就可以用简单类型对象 SimpleType 返回映射类型就可以了。

type SimpleType = {
  string: string,
  number: number,
  boolean: boolean,
  object: Record<string, unknown>
  array: unknown[]
}

接下来再看一下复杂数组类型,可以发现 items 里对应的数组子项类型对象,其实是可以递归的用 JSONSchema2TS 处理的,所以这里可以这样处理:

T extends { items: infer Array }
  ? JSONSchema2TS<Array>[]
  : ...

最后再看一下复杂对象类型,这里需要处理 requiredpropertiesrequired 是一个字符串数组,表示 properties 中的哪些属性是必须的,所以这里需要用到一个条件类型,将 properties 中的属性分为必须和可选两种类型,然后再将两种类型合并为一个对象类型。这里比较重要的代码是:

type Copy<T> = {
  [K in keyof T]: T[K]
}

type HandleProperties<T, Required extends string[] = []> = Copy<{
  [K in keyof T as K extends Required[number] ? K : never]: JSONSchema2TS<T[K]>
} & {
  [K in keyof T as K extends Required[number] ? never : K]?: JSONSchema2TS<T[K]>
}>

HandlePropertiesproperties 中的属性根据 Required 分为必须和可选两种类型,但是这样处理后的类型代码是这样的:

{ a: number } & { b?: string }

不符合要求,所以要将这样的代码进行合并,这里用 Copy 来处理,经过它处理后,上面的示例会变成:

{
    a: number,
    b?: string
}

Solution by woai3c #31881

export type Merge<T, U, R = Omit<T, keyof U> & U> = { [K in keyof R]: R[K] };

type TypeMap = {
  'string': string
  'number': number
  'boolean': boolean
  'object': Record<string, unknown>
  'array': unknown[]
}

type JSONSchema = {
  type: keyof TypeMap
  properties?: Record<PropertyKey, JSONSchema>
  items?: JSONSchema
  enum?: (string | number | boolean)[]
  required?: PropertyKey[]
}

type JSONSchema2TS<T extends JSONSchema, Type extends keyof TypeMap = T['type']> =
  Type extends 'object'
    ? T['properties'] extends infer P extends Record<PropertyKey, JSONSchema>
      ? T['required'] extends infer R extends PropertyKey[]
        ? Merge<
            {[K in Exclude<keyof P, R[number]>]?: JSONSchema2TS<P[K]>},
            {[K in R[number]]: JSONSchema2TS<P[K]>}
          >
        : {[K in keyof P]?: JSONSchema2TS<P[K]>}
      : TypeMap[Type]
    : Type extends 'array'
      ? T['items'] extends infer I extends JSONSchema
        ? JSONSchema2TS<I>[]
        : TypeMap[Type]
      : T['enum'] extends infer E extends unknown[]
        ? E[number]
        : TypeMap[Type]

Solution by milletlovemouse #30969

// your answers

// utils
type TypeMap = {
   "string": string,
   "number": number
   "boolean": boolean
   "object": Record<string, unknown>
   "array": Array<unknown>
}

type SupportedType = keyof TypeMap

type Flatten<T> = { [P in keyof T]: T[P]}

// main
type JSONSchema2TS<T> =
  T extends { "type": infer Type extends SupportedType } ?
    T extends { "enum" : infer E extends unknown[]} ?
      E[number]
      : 
      T extends { "items": infer I } ?
        JSONSchema2TS<I>[] 
        :
        T extends { "properties": infer P extends Record<string, unknown> } ?
          T extends { "required": infer Required extends (keyof P)[]} ?
            Flatten<{ [Key in Exclude<keyof P, Required[number]>]?: JSONSchema2TS<P[Key]>} & { [Key in Extract<keyof P, Required[number]>]: JSONSchema2TS<P[Key]>}>
            :
            { [Key in keyof P]?: JSONSchema2TS<P[Key]>}
          :
          TypeMap[Type]
    : never

Solution by Kakeru-Miyazaki #30923

type PrimitiveMap = {
  boolean: boolean
  number: number
  string: string
  null: null
}

type Primitives = keyof PrimitiveMap

type PrimitiveSchema<P extends Primitives> = {
  type: P
  enum?: PrimitiveMap[P][]
}

type PrimitiveSchema2TS<P extends Primitives, S extends PrimitiveSchema<P>> = S['enum'] extends infer E extends any[]
  ? E[number]
  : PrimitiveMap[P]

type PrimitivesSchema = { [K in Primitives]: PrimitiveSchema<K> }[Primitives]

type ObjectSchema = {
  type: 'object'
  properties?: {
    [K in string]: {
      type: SchemaTypes
    }
  }
  required?: string[]
}

type Prettify<T> = { [K in keyof T]: T[K] } & {}

type ObjectSchema2TS<S extends ObjectSchema> = ([unknown] | [undefined]) extends [S['properties']]
  ? Record<string, unknown>
  : [keyof S['properties'], S['properties']] extends infer T extends [string, Record<string, Schema>]
    ? { [K in T[0]]: JSONSchema2TS<T[1][K]> } extends infer Raw
      ? S['required'] extends infer R extends string[]
        ? Prettify<Pick<Raw, R[number] extends keyof Raw ? R[number] : never> & Partial<Omit<Raw, R[number]>>>
        : Partial<Raw>
      : never
    : never

type ArraySchema = {
  type: 'array'
  items?: Schema
}

type ArraySchema2TS<S extends ArraySchema> = ([unknown] | [undefined]) extends [S['items']]
  ? unknown[]
  : S['items'] extends Schema
    ? JSONSchema2TS<S['items']>[]
    : never

type SchemaTypes = Schema['type']

type Schema = PrimitivesSchema | ObjectSchema | ArraySchema

type JSONSchema2TS<S extends Schema> = [S['type'], S] extends infer T extends [Primitives, PrimitivesSchema]
  ? PrimitiveSchema2TS<T[0], T[1]>
  : [S['type'], S] extends infer T extends ['object', ObjectSchema]
    ? ObjectSchema2TS<T[1]>
    : [S['type'], S] extends infer T extends ['array', ArraySchema]
      ? ArraySchema2TS<T[1]>
      : never

Solution by DevilTea #30861

type TypeMap = {
  string: string
  number: number
  boolean: boolean
  object: Record<string, unknown>
  array: unknown[]
}

type JSONObject = {
  type: keyof TypeMap
  enum?: unknown[]
  properties?: Record<PropertyKey, JSONObject>
  required?: PropertyKey[]
  items?: JSONObject
}

type MakeObject<
  P extends Record<PropertyKey, JSONObject>,
  R extends PropertyKey,
  U extends PropertyKey = Exclude<keyof P, R>,
> = Omit<
  {
    [K in U]?: JSONSchema2TS<P[K]>
  } & {
    [K in R]: JSONSchema2TS<P[K]>
  },
  never
>

type JSONSchema2TS<T extends JSONObject> = T['type'] extends 'object'
  ? T['properties'] extends object
    ? MakeObject<
        T['properties'],
        T['required'] extends any[] ? T['required'][number] : never
      >
    : TypeMap['object']
  : T['type'] extends 'array'
    ? T['items'] extends JSONObject
      ? JSONSchema2TS<T['items']>[]
      : TypeMap['array']
    : T['enum'] extends TypeMap[T['type']][]
      ? T['enum'][number]
      : TypeMap[T['type']]

Solution by FlareZh #30823

type PrimitivesMap = {
  'string': string
  'number': number
  'boolean': boolean
  'object': object
  'array': Array<unknown>
}
interface Model {
  type: keyof PrimitivesMap
  enum?: Array<unknown>
  items?: Model
  properties?: { [x: string]: Model }
  required?: string[]
}

type HandleObject<T extends Model, K extends T['properties'] = T extends { properties: { [x: string]: Model } } ? T['properties'] : never> =
  [K] extends [never] ? { [x: string]: unknown }
    : T['required'] extends any[] ?
      Omit<{
        [P in T['required'][number]]: JSONSchema2TS<T['properties'][P]>
      } & {
        [P in Exclude<keyof K, T['required'][number]>]?: JSONSchema2TS<T['properties'][P]>
      }, never>
      : {
          [P in keyof T['properties']]?: JSONSchema2TS<T['properties'][P]>
        }

type HandleArray<T extends Model> = T['items'] extends Model ? JSONSchema2TS<T['items']>[] : unknown[]

type JSONSchema2TS<T extends Model> =
  T['enum'] extends any[] ? T['enum'][number]
    : T['type'] extends 'array' ? HandleArray<T>
      : T['type'] extends 'object' ? HandleObject<T>
        : PrimitivesMap[T['type']]

Solution by hesoso #29996

type Schema = {
  type: 'string',
  enum?: string[]
} | {
  type: 'boolean',
  enum?: boolean[]
} | {
  type: 'number'
  enum?: number[]
} | {
  type: 'object',
  properties?: Record<string, Schema>
  required?: unknown[]
} | {
  type: 'array',
  items?: Schema
}


type GetEnum<T, Fallback> = T extends Fallback[] ? T[number] : Fallback

type JSONSchema2TS<T extends Schema> = 
  T extends { type: 'string' }
  ? GetEnum<T['enum'], string>
  : T extends { type: 'boolean' }
  ? GetEnum<T['enum'], boolean>
  : T extends { type: 'number' }
  ? GetEnum<T['enum'], number>
  : T extends { type: 'object' }
  ? T['properties'] extends Record<string, Schema>
    ? T['required'] extends any[]
      ? Omit<{
        [x in T['required'][number] & keyof T['properties']]: JSONSchema2TS<T['properties'][x]>
      } & {
          [x in keyof Omit<T['properties'], T['required'][number]>]?: JSONSchema2TS<T['properties'][x]>
        }, never>
      : { [x in ((keyof T['properties']))]?: JSONSchema2TS<T['properties'][x]> }
    : Record<string, unknown>
  : T extends { type: 'array' }
  ? Array<T['items'] extends Schema ? JSONSchema2TS<T['items']> : unknown>
  : never

Solution by hinsxd #29576

type JSONSchema2TS<T>
  // enum
  = T extends { enum: infer Enum extends unknown[] } ? Enum[number]
  // typed array
  : T extends { items: infer Items } ? JSONSchema2TS<Items>[]
  // non-empty object
  : T extends { properties: infer Props extends object }
    ? T extends { required: infer Required extends string[] }
      ? Omit<
        & { [K in keyof Props & Required[number]]: JSONSchema2TS<Props[K]> }
        & { [K in Exclude<keyof Props, Required[number]>]?: JSONSchema2TS<Props[K]> }
        , never>
      : { [K in keyof Props]?: JSONSchema2TS<Props[K]> }
  // primitives, empty object, untyped array
  : T extends { type: infer Type extends 'number' | 'string' | 'boolean' | 'object' | 'array' }
    ? { number: number; string: string; boolean: boolean; object: Record<string, unknown>; array: unknown[] }[Type]
    : never

Solution by Alexsey #29373

interface PrimitiveMap {
  string: string
  number: number
  boolean: boolean
}

interface Schema {
  type: keyof PrimitiveMap | 'object' | 'array'
  enum?: string[] | number[]
  properties?: Record<string, Schema>
  items?: Schema
  required?: string[]
}

type Merge<T> = {
  [Key in keyof T]: T[Key]
}

type RequiredWith<T extends Record<string, unknown>, Keys extends keyof T> =
  Merge<Required<Pick<T, Keys>> & Omit<T, Keys>>

type JSONSchema2TS<T extends Schema> =
  T['type'] extends keyof PrimitiveMap
    ? T['enum'] extends unknown[]
      ? T['enum'][number]
      : PrimitiveMap[T['type']]
    : T['type'] extends 'object'
      ? T['properties'] extends Record<string, Schema>
        ? RequiredWith<{
          [Key in keyof T['properties']]?: JSONSchema2TS<T['properties'][Key]>
        }, T['required'] extends string[] ? T['required'][number] : never>
        : Record<string, unknown>
      : T['items'] extends Schema
        ? JSONSchema2TS<T['items']>[]
        : unknown[]

Solution by drylint #27435

type Expand<T> = T extends object ? {[key in keyof T]: Expand<T[key]>} : T;
type PrimitiveTypes = { string: string; number: number; boolean: boolean; array: unknown[] }

type JSONSchema2TS<T extends { type: string }> = 
Expand<
  (T extends { enum: unknown[] } ? T["enum"][number] :
    T extends { properties: any } ? { [K in keyof T["properties"]]?: JSONSchema2TS<T["properties"][K]> } :
      T extends { items: any } ? Array<JSONSchema2TS<T["items"]>> : 
        T extends { type: "object" } ? Record<string, unknown> : 
          PrimitiveTypes[T["type"] & keyof PrimitiveTypes])
  &
  (T extends { required: string[]; properties: any } ? 
    { [key in T["required"][number]]: JSONSchema2TS<T["properties"][key]> } : {})
>

Solution by mwashief #27236

type Item = {
  type: string
  enum?: any[]
  items?: Item
  properties?: Record<string, Item>
  required?: string[]
}

type JSONSchema2TS<T extends Item> = 
  T['enum'] extends PropertyKey[]
    ? T['enum'][number] 
    : T['type'] extends 'string'
      ? string
      : T['type'] extends 'number'
        ? number
        : T['type'] extends 'boolean'
          ? boolean
          : T['type'] extends 'array'
            ? T['items'] extends Item
              ? JSONSchema2TS<T['items']>[]
              : unknown[]
            : T['type'] extends 'object'
              ? T['properties'] extends Record<string, Item>
                ? T['required'] extends string[]
                  ? Omit<{
                      [K in T['required'][number]]: JSONSchema2TS<T['properties'][K]>
                    } & {
                      [K in keyof T['properties']]?: JSONSchema2TS<T['properties'][K]>
                    }, never>
                  : {
                    [K in keyof T['properties']]?: JSONSchema2TS<T['properties'][K]>
                  }
                : Record<string, unknown>
              : never

Solution by Minato1123 #27203

type Primitives = {
  string: string;
  number: number;
  boolean: boolean;
};

type HandlePrimitives<T, Type extends keyof Primitives> = T extends {
  enum: unknown[];
}
  ? T['enum'][number]
  : Primitives[Type];

type HandleObject<T> = T extends {
  properties: infer Properties extends Record<string, unknown>;
}
  ? T extends { required: infer Required extends unknown[] }
    ? Omit<
        {
          [K in Required[number] & keyof Properties]: JSONSchema2TS<
            Properties[K]
          >;
        } & {
          [K in Exclude<keyof Properties, Required[number]>]?: JSONSchema2TS<
            Properties[K]
          >;
        },
        never
      >
    : {
        [K in keyof Properties]?: JSONSchema2TS<Properties[K]>;
      }
  : Record<string, unknown>;

type HandleArray<T> = T extends { items: infer Items }
  ? JSONSchema2TS<Items>[]
  : unknown[];

type JSONSchema2TS<T> = T extends { type: infer Type }
  ? Type extends keyof Primitives
    ? HandlePrimitives<T, Type>
    : Type extends 'object'
    ? HandleObject<T>
    : HandleArray<T>
  : never;

Solution by JohnLi1999 #26864

For some reason the last test fails, but I feel like it shouldn't. https://tsplay.dev/N5A0MN

type SomeObject = {
  [k: string]: unknown
}

type NumberParser<T extends SomeObject> = T extends {
  enum: readonly number[];
}
  ? T["enum"][number]
  : number;

type StringParser<T extends SomeObject> = T extends {
  enum: readonly string[];
}
  ? T["enum"][number]
  : string;

type BaseTypeParser<T extends ValidInput> = T["type"] extends "string"
  ? StringParser<T>
  : T["type"] extends "number"
  ? NumberParser<T>
  : boolean;

type ObjParser<T extends SomeObject> = T extends {
  properties: unknown
}
  ? { [K in keyof T["properties"]]?: JSONSchema2TS<T["properties"][K]> } & (T extends {required: ReadonlyArray<keyof T['properties']>} ? { [K in T['required'][number]]-?: JSONSchema2TS<T['properties'][K]>} : unknown)
  : Record<string, unknown>;

type ArrayParser<T extends SomeObject> = T extends {
  items: unknown
} ? JSONSchema2TS<T['items']>[] : unknown[]

type ComplexTypes = 'object' | 'array'
type ValidTypes = BaseTypes | ComplexTypes;
type BaseTypes = "string" | "number" | "boolean";

type IsValidInput<T> = T extends { type: ValidTypes } ? true : false;
type ValidInput = { type: ValidTypes };

type ParseValidInput<T extends ValidInput> = T["type"] extends BaseTypes
  ? BaseTypeParser<T>
  : T['type'] extends 'array' ? ArrayParser<T> : ObjParser<T>;


type JSONSchema2TS<T> = IsValidInput<T> extends true
  ? ParseValidInput<T & ValidInput>
  : never;

Solution by amsuarez99 #26740

type Merge<T> = {
  [K in keyof T]:T[K]
}

type RequireByKeys<T, KS extends keyof T> = Merge< Required<Pick<T,KS>>& Omit<T,KS>>

type JSONSchema2TS<T> = 
T extends {type: "string"}?
  T extends {enum:string[] }? T['enum'][number]:string : 
    T extends {type:"number"}? 
      T extends {enum:number[]}? T['enum'][number]:number:
        T extends {type:"boolean"}?
          boolean:
          T extends {type: "object"}?
            T extends {properties:any}? 
              RequireByKeys<{[K in keyof T['properties']]?:JSONSchema2TS<T['properties'][K]>}, T extends {required:Array<keyof T['properties']>}?T['required'][number]: never  >
              :Record<string,unknown>:
              T extends {type: "array"}?
                T extends {items:any}? Array<JSONSchema2TS<T['items']>>:unknown[]
                :never

Solution by jiangshanmeta #26657

type PrimitiveTypes = {
  string: string
  number: number
  boolean: boolean
}

type ResolvePrimitiveType<T extends { type: string }> = T extends { enum: infer E extends unknown[] }
  ? E[number]
  : PrimitiveTypes[T['type'] & keyof PrimitiveTypes]

type ResolveObject<T> = T extends { properties: infer P }
  ? T extends { required: infer R extends unknown[] }
    ? MergeIntersection<Required<ResolveProperties<Pick<T['properties'], R[number] & keyof P>>> & ResolveProperties<Omit<T['properties'], R[number] & keyof P>>>
    : ResolveProperties<T['properties']>
  : Record<string, unknown>

type ResolveProperties<T> = {
  [K in keyof T]?: JSONSchema2TS<T[K]>
}

type MergeIntersection<T> = {
  [K in keyof T]: T[K]
}

type ResolveArray<T> = T extends { items: infer I }
  ? JSONSchema2TS<I>[]
  : unknown[]

type JSONSchema2TS<T> = T extends { type: infer Type extends string }
  ? Type extends keyof PrimitiveTypes
    ? ResolvePrimitiveType<T>
    : Type extends "object"
      ? ResolveObject<T>
      : Type extends 'array'
        ? ResolveArray<T>
        : never
  : never

Solution by Sun79 #26466

type JSONSchema = { type: string, enum?: any[], items?: JSONSchema, properties?: { [x: string]: JSONSchema }, required?: string[] }
type Primitive<T> = T extends `string` ? string :
  T extends `number` ? number :
  T extends `boolean` ? boolean :
  T;
type CombineObject<T extends object> = { [P in keyof T]: T[P] };
type Include<T, U> = T extends any[] ? U extends T[number] ? true : false : false;

type JSONSchema2TS<T extends JSONSchema> =
  //数组
  T[`type`] extends `array` ?
  (T[`items`] extends JSONSchema ?
    JSONSchema2TS<T[`items`]>[] :
    unknown[]) :
  //结构体
  T[`type`] extends `object` ?
  (T[`properties`] extends object ?
    (CombineObject<
      { [P in keyof T[`properties`]as Include<T[`required`], P> extends true ? never : P]?: JSONSchema2TS<T[`properties`][P]> }
      &
      { [P in keyof T[`properties`]as Include<T[`required`], P> extends true ? P : never]: JSONSchema2TS<T[`properties`][P]> }>) :
    Record<string, unknown>) :
  //原子
  T[`enum`] extends any[] ? T[`enum`][number] : Primitive<T[`type`]>;

Solution by E-uler #26443