Skip to content

Commit

Permalink
Merge pull request #130 from ThomasAribart/enable-json-schema-extensions
Browse files Browse the repository at this point in the history
Enable json schema extensions
  • Loading branch information
ThomasAribart committed Apr 17, 2023
2 parents 6cbff72 + e15e934 commit f96e577
Show file tree
Hide file tree
Showing 15 changed files with 416 additions and 43 deletions.
54 changes: 52 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ type Dog = FromSchema<typeof dogSchema>;
// => Will work as well 🙌
```

Since TS 4.9, you can also use the `satisfies` operator to benefit from type-checking and autocompletion:

```typescript
import type { JSONSchema } from "json-schema-to-ts";

const dogSchema = {
// Type-checked and autocompleted 🙌
type: "object"
...
} as const satisfies JSONSchema

type Dog = FromSchema<typeof dogSchema>
// => Still work 🙌
```
## Why use `json-schema-to-ts`?
If you're looking for runtime validation with added types, libraries like [yup](https://github.com/jquense/yup), [zod](https://github.com/vriad/zod) or [runtypes](https://github.com/pelotom/runtypes) may suit your needs while being easier to use!
Expand Down Expand Up @@ -151,6 +166,7 @@ type Address = FromSchema<typeof addressSchema>;
- [Definitions](#definitions)
- [References](#references)
- [Deserialization](#deserialization)
- [Extensions](#extensions)
- [Typeguards](#typeguards)
- [Validators](#validators)
- [Compilers](#compilers)
Expand Down Expand Up @@ -739,7 +755,7 @@ type User = FromSchema<
format: "date-time";
};
output: Date;
}
},
];
}
>;
Expand All @@ -750,6 +766,41 @@ type User = FromSchema<
// }
```

## Extensions

If you need to extend the JSON Schema spec with custom properties, use the `ExtendedJSONSchema` and `FromExtendedSchema` types to benefit from `json-schema-to-ts`:

```typescript
import type { ExtendedJSONSchema, FromExtendedSchema } from "json-schema-to-ts";

type CustomProps = {
numberType: "int" | "float" | "bigInt";
};

const bigIntSchema = {
type: "number",
numberType: "bigInt",
// 👇 Ensures mySchema is correct (includes extension)
} as const satisfies ExtendedJSONSchema<CustomProps>;

type BigInt = FromExtendedSchema<
CustomProps,
typeof bigIntSchema,
{
// 👇 Works very well with the deserialize option!
deserialize: [
{
pattern: {
type: "number";
numberType: "bigInt";
};
output: bigint;
},
];
}
>;
```

## Typeguards

You can use `FromSchema` to implement your own typeguard:
Expand Down Expand Up @@ -904,6 +955,5 @@ const compile = wrapCompilerAsTypeGuard<
## Frequently Asked Questions

- [Does `json-schema-to-ts` work on _.json_ file schemas?](./documentation/FAQs/does-json-schema-to-ts-work-on-json-file-schemas.md)
- [Can I assign `JSONSchema` to my schema and use `FromSchema` at the same time?](./documentation/FAQs/can-i-assign-jsonschema-to-my-schema-and-use-fromschema-at-the-same-time.md)
- [Will `json-schema-to-ts` impact the performances of my IDE/compiler?](./documentation/FAQs/will-json-schema-to-ts-impact-the-performances-of-my-ide-compiler.md)
- [I get a `type instantiation is excessively deep and potentially infinite` error, what should I do?](./documentation/FAQs/i-get-a-type-instantiation-is-excessively-deep-and-potentially-infinite-error-what-should-i-do.md)
66 changes: 63 additions & 3 deletions builds/deno/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ declare type DeserializationPattern = {
output: unknown;
};

declare const $JSONSchema7: unique symbol;
declare type $JSONSchema7 = typeof $JSONSchema7;
declare type JSONSchema7$1 = boolean | (Omit<JSONSchema7$2, "const" | "enum" | "items" | "additionalItems" | "contains" | "properties" | "patternProperties" | "additionalProperties" | "dependencies" | "propertyNames" | "if" | "then" | "else" | "allOf" | "anyOf" | "oneOf" | "not" | "definitions" | "examples" | "default"> & {
[$JSONSchema7]?: $JSONSchema7;
const?: unknown;
enum?: unknown;
items?: JSONSchema7$1 | JSONSchema7$1[];
Expand Down Expand Up @@ -37,12 +40,63 @@ declare type JSONSchema7Reference$1 = JSONSchema7$1 & {
$id: string;
};

declare type JSONSchema7Extension = Record<string, unknown>;
declare type ExtendedJSONSchema7$1<E extends JSONSchema7Extension = JSONSchema7Extension> = boolean | (Omit<JSONSchema7$2, "const" | "enum" | "items" | "additionalItems" | "contains" | "properties" | "patternProperties" | "additionalProperties" | "dependencies" | "propertyNames" | "if" | "then" | "else" | "allOf" | "anyOf" | "oneOf" | "not" | "definitions" | "examples" | "default"> & {
const?: unknown;
enum?: unknown;
items?: ExtendedJSONSchema7$1<E> | ExtendedJSONSchema7$1<E>[];
additionalItems?: ExtendedJSONSchema7$1<E>;
contains?: ExtendedJSONSchema7$1<E>;
properties?: Record<string, ExtendedJSONSchema7$1<E>>;
patternProperties?: Record<string, ExtendedJSONSchema7$1<E>>;
additionalProperties?: ExtendedJSONSchema7$1<E>;
dependencies?: {
[key: string]: ExtendedJSONSchema7$1<E> | string[];
};
propertyNames?: ExtendedJSONSchema7$1<E>;
if?: ExtendedJSONSchema7$1<E>;
then?: ExtendedJSONSchema7$1<E>;
else?: ExtendedJSONSchema7$1<E>;
allOf?: ExtendedJSONSchema7$1<E>[];
anyOf?: ExtendedJSONSchema7$1<E>[];
oneOf?: ExtendedJSONSchema7$1<E>[];
not?: ExtendedJSONSchema7$1<E>;
nullable?: boolean;
definitions?: {
[key: string]: ExtendedJSONSchema7$1<E>;
};
examples?: unknown[];
default?: unknown;
} & Partial<E>);
declare type ExtendedJSONSchema7Reference$1<E extends JSONSchema7Extension = JSONSchema7Extension> = ExtendedJSONSchema7$1<E> & {
$id: string;
};
declare type UnextendJSONSchema7Tuple<E extends JSONSchema7Extension, S extends ExtendedJSONSchema7$1<E>[]> = S extends [infer H, ...infer T] ? H extends ExtendedJSONSchema7$1<E> ? T extends ExtendedJSONSchema7$1<E>[] ? [UnextendJSONSchema7<E, H>, ...UnextendJSONSchema7Tuple<E, T>] : never : never : [];
declare type UnextendJSONSchema7Record<E extends JSONSchema7Extension, S extends Record<string, unknown>> = {
[key in keyof S]: S[key] extends ExtendedJSONSchema7$1<E> ? UnextendJSONSchema7<E, S[key]> : S[key];
};
declare type UnextendJSONSchema7<E extends JSONSchema7Extension, S extends ExtendedJSONSchema7$1<E>> = S extends boolean ? S : {
[key in $JSONSchema7 | keyof S]: key extends keyof S ? S extends {
[k in key]: ExtendedJSONSchema7$1<E>;
} ? UnextendJSONSchema7<E, S[key]> : S extends {
[k in key]: ExtendedJSONSchema7$1<E>[];
} ? number extends S[key]["length"] ? UnextendJSONSchema7<E, S[key][number]>[] : S[key] extends ExtendedJSONSchema7$1<E>[] ? UnextendJSONSchema7Tuple<E, S[key]> : never : S extends {
[k in key]: Record<string, unknown>;
} ? UnextendJSONSchema7Record<E, S[key]> : S[key] : key extends $JSONSchema7 ? $JSONSchema7 : never;
};

declare type FromSchemaOptions = {
parseNotKeyword?: boolean;
parseIfThenElseKeywords?: boolean;
references?: JSONSchema7Reference[] | false;
deserialize?: DeserializationPattern[] | false;
};
declare type FromExtendedSchemaOptions<E extends JSONSchema7Extension> = {
parseNotKeyword?: boolean;
parseIfThenElseKeywords?: boolean;
references?: ExtendedJSONSchema7Reference$1<E>[] | false;
deserialize?: DeserializationPattern[] | false;
};
declare type FromSchemaDefaultOptions = {
parseNotKeyword: false;
parseIfThenElseKeywords: false;
Expand All @@ -52,6 +106,8 @@ declare type FromSchemaDefaultOptions = {

declare type And<A, B> = A extends true ? B extends true ? true : false : false;

declare type Cast<A, B> = A extends B ? A : never;

declare type DoesExtend<A, B> = [A] extends [B] ? true : false;

declare type If<B extends boolean, T, E = never> = B extends true ? T : E;
Expand Down Expand Up @@ -87,7 +143,7 @@ declare type DeepReadonly<T> = T extends Record<string | number | symbol, any> ?

declare type Reverse<L extends unknown[]> = L extends [infer H, ...infer T] ? [...Reverse<T>, H] : L;

declare type RecSplit<S extends string, D extends string = "", R extends string[] = []> = S extends `${infer BS}${D}${infer AS}` ? RecSplit<AS, D, [...R, BS]> : [...R, S];
declare type RecSplit<S extends string, D extends string = ""> = S extends `${infer BS}${D}${infer AS}` ? [BS, ...RecSplit<AS, D>] : [S];
declare type Split<S extends string, D extends string = "", R extends string[] = RecSplit<S, D>> = D extends "" ? Pop<R> : R;

declare type Tail<L extends unknown[]> = L extends readonly [] ? L : L extends readonly [unknown?, ...infer T] ? T : L;
Expand Down Expand Up @@ -153,7 +209,7 @@ declare type TupleSchema = JSONSchema7$1 & {
items: JSONSchema7$1[];
};
declare type ParseArraySchema<S extends ArraySchema, O extends ParseSchemaOptions> = S extends SimpleArraySchema ? M.$Array<ParseSchema<S["items"], O>> : S extends TupleSchema ? M.$Union<FromTreeTuple<ParseTuple<S["items"], O>, S, O>> : M.$Array;
declare type ParseTuple<S extends JSONSchema7$1[], O extends ParseSchemaOptions, R extends any[] = []> = S extends [infer H, ...infer T] ? H extends JSONSchema7$1 ? T extends JSONSchema7$1[] ? ParseTuple<T, O, [ParseSchema<H, O>, ...R]> : never : never : R;
declare type ParseTuple<S extends JSONSchema7$1[], O extends ParseSchemaOptions> = S extends [infer H, ...infer T] ? H extends JSONSchema7$1 ? T extends JSONSchema7$1[] ? [...ParseTuple<T, O>, ParseSchema<H, O>] : never : never : [];
declare type FromTreeTuple<T extends any[], S extends ArraySchema, O extends ParseSchemaOptions> = ApplyAdditionalItems<ApplyBoundaries<T, S extends {
minItems: number;
} ? S["minItems"] : 0, S extends {
Expand Down Expand Up @@ -321,8 +377,12 @@ declare const wrapValidatorAsTypeGuard: ValidatorWrapper;
declare const asConst: <A>(narrowed: Narrow<A>) => Narrow<A>;

declare type JSONSchema7 = JSONSchema7$1 | DeepReadonly<JSONSchema7$1>;
declare type ExtendedJSONSchema7<E extends JSONSchema7Extension> = ExtendedJSONSchema7$1<E> | DeepReadonly<ExtendedJSONSchema7$1<E>>;
declare type JSONSchema7Reference = JSONSchema7Reference$1 | DeepReadonly<JSONSchema7Reference$1>;
declare type ExtendedJSONSchema7Reference<E extends JSONSchema7Extension> = ExtendedJSONSchema7Reference$1<E> | DeepReadonly<ExtendedJSONSchema7Reference$1<E>>;
declare type JSONSchema = JSONSchema7;
declare type ExtendedJSONSchema<E extends JSONSchema7Extension> = ExtendedJSONSchema7<E>;
declare type FromSchema<S extends JSONSchema, Opt extends FromSchemaOptions = FromSchemaDefaultOptions, W extends JSONSchema7$1 = S extends Record<string | number | symbol, unknown> ? DeepWritable<S> : S> = M.$Resolve<ParseSchema<W, ParseOptions<W, Opt>>>;
declare type FromExtendedSchema<E extends JSONSchema7Extension, S extends ExtendedJSONSchema<E>, Opt extends FromExtendedSchemaOptions<E> = FromSchemaDefaultOptions, W extends ExtendedJSONSchema7$1<E> = Cast<S extends Record<string | number | symbol, unknown> ? DeepWritable<S> : S, ExtendedJSONSchema7$1<E>>> = FromSchema<Cast<UnextendJSONSchema7<E, W>, JSONSchema>, Opt>;

export { $Compiler, $Validator, Compiler, DeserializationPattern, FromSchema, FromSchemaDefaultOptions, FromSchemaOptions, JSONSchema, JSONSchema7, JSONSchema7Reference, Validator, asConst, wrapCompilerAsTypeGuard, wrapValidatorAsTypeGuard };
export { $Compiler, $Validator, Compiler, DeserializationPattern, ExtendedJSONSchema, ExtendedJSONSchema7, ExtendedJSONSchema7Reference, FromExtendedSchema, FromExtendedSchemaOptions, FromSchema, FromSchemaDefaultOptions, FromSchemaOptions, JSONSchema, JSONSchema7, JSONSchema7Extension, JSONSchema7Reference, Validator, asConst, wrapCompilerAsTypeGuard, wrapValidatorAsTypeGuard };

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "json-schema-to-ts",
"version": "2.7.2",
"version": "2.8.0-beta.0",
"description": "Infer typescript types from your JSON schemas!",
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
Expand Down
110 changes: 110 additions & 0 deletions src/definitions/extendedJsonSchema7.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import type { JSONSchema7 as OriginalJSONSchema7 } from "json-schema";

import { $JSONSchema7 } from "./jsonSchema7";

export type JSONSchema7Extension = Record<string, unknown>;

export type ExtendedJSONSchema7<
E extends JSONSchema7Extension = JSONSchema7Extension,
> =
| boolean
| (Omit<
OriginalJSONSchema7,
| "const"
| "enum"
| "items"
| "additionalItems"
| "contains"
| "properties"
| "patternProperties"
| "additionalProperties"
| "dependencies"
| "propertyNames"
| "if"
| "then"
| "else"
| "allOf"
| "anyOf"
| "oneOf"
| "not"
| "definitions"
| "examples"
| "default"
> & {
const?: unknown;
enum?: unknown;
items?: ExtendedJSONSchema7<E> | ExtendedJSONSchema7<E>[];
additionalItems?: ExtendedJSONSchema7<E>;
contains?: ExtendedJSONSchema7<E>;
properties?: Record<string, ExtendedJSONSchema7<E>>;
patternProperties?: Record<string, ExtendedJSONSchema7<E>>;
additionalProperties?: ExtendedJSONSchema7<E>;
dependencies?: {
[key: string]: ExtendedJSONSchema7<E> | string[];
};
propertyNames?: ExtendedJSONSchema7<E>;
if?: ExtendedJSONSchema7<E>;
then?: ExtendedJSONSchema7<E>;
else?: ExtendedJSONSchema7<E>;
allOf?: ExtendedJSONSchema7<E>[];
anyOf?: ExtendedJSONSchema7<E>[];
oneOf?: ExtendedJSONSchema7<E>[];
not?: ExtendedJSONSchema7<E>;
nullable?: boolean;
definitions?: { [key: string]: ExtendedJSONSchema7<E> };
// Required to avoid applying Readonly to Array interface, which results in invalid type (Array is treated as Object):
// https://github.com/ThomasAribart/json-schema-to-ts/issues/48
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/0e40d820c92ec6457854fa6726bbff2ffea4e7dd/types/json-schema/index.d.ts#L590
// https://github.com/microsoft/TypeScript/issues/3496#issuecomment-128553540
examples?: unknown[];
// Required to allow array values in default field
// https://github.com/ThomasAribart/json-schema-to-ts/issues/80
default?: unknown;
} & Partial<E>);

export type ExtendedJSONSchema7Reference<
E extends JSONSchema7Extension = JSONSchema7Extension,
> = ExtendedJSONSchema7<E> & { $id: string };

type UnextendJSONSchema7Tuple<
E extends JSONSchema7Extension,
S extends ExtendedJSONSchema7<E>[],
> = S extends [infer H, ...infer T]
? H extends ExtendedJSONSchema7<E>
? T extends ExtendedJSONSchema7<E>[]
? [UnextendJSONSchema7<E, H>, ...UnextendJSONSchema7Tuple<E, T>]
: never
: never
: [];

type UnextendJSONSchema7Record<
E extends JSONSchema7Extension,
S extends Record<string, unknown>,
> = {
[key in keyof S]: S[key] extends ExtendedJSONSchema7<E>
? UnextendJSONSchema7<E, S[key]>
: S[key];
};

export type UnextendJSONSchema7<
E extends JSONSchema7Extension,
S extends ExtendedJSONSchema7<E>,
> = S extends boolean
? S
: {
[key in $JSONSchema7 | keyof S]: key extends keyof S
? S extends { [k in key]: ExtendedJSONSchema7<E> }
? UnextendJSONSchema7<E, S[key]>
: S extends { [k in key]: ExtendedJSONSchema7<E>[] }
? number extends S[key]["length"]
? UnextendJSONSchema7<E, S[key][number]>[]
: S[key] extends ExtendedJSONSchema7<E>[]
? UnextendJSONSchema7Tuple<E, S[key]>
: never
: S extends { [k in key]: Record<string, unknown> }
? UnextendJSONSchema7Record<E, S[key]>
: S[key]
: key extends $JSONSchema7
? $JSONSchema7
: never;
};
13 changes: 13 additions & 0 deletions src/definitions/fromSchemaOptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { JSONSchema7Reference } from "~/index";

import type { DeserializationPattern } from "./deserializationPattern";
import type {
JSONSchema7Extension,
ExtendedJSONSchema7Reference,
} from "./extendedJsonSchema7";

/**
* FromSchema options constraints
Expand All @@ -11,6 +15,15 @@ export type FromSchemaOptions = {
references?: JSONSchema7Reference[] | false;
deserialize?: DeserializationPattern[] | false;
};
/**
* FromExtendedSchema options constraints
*/
export type FromExtendedSchemaOptions<E extends JSONSchema7Extension> = {
parseNotKeyword?: boolean;
parseIfThenElseKeywords?: boolean;
references?: ExtendedJSONSchema7Reference<E>[] | false;
deserialize?: DeserializationPattern[] | false;
};

/**
* FromSchema default options
Expand Down
7 changes: 7 additions & 0 deletions src/definitions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,12 @@ export type { DeserializationPattern } from "./deserializationPattern";
export type { JSONSchema7, JSONSchema7Reference } from "./jsonSchema7";
export type {
FromSchemaOptions,
FromExtendedSchemaOptions,
FromSchemaDefaultOptions,
} from "./fromSchemaOptions";
export type {
JSONSchema7Extension,
ExtendedJSONSchema7,
ExtendedJSONSchema7Reference,
UnextendJSONSchema7,
} from "./extendedJsonSchema7";
Loading

0 comments on commit f96e577

Please sign in to comment.