diff --git a/README.md b/README.md index c4b0847..1fd70b1 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,27 @@ type Object = FromSchema; // => { [x: string]: unknown; foo: string; bar?: number; } ``` +Defaulted properties (even optional ones) will be set as required in the resulting type. You can turn off this behavior by setting the `keepDefaultedPropertiesOptional` option to `true`: + +```typescript +const defaultedProp = { + type: "object", + properties: { + foo: { type: "string", default: "bar" }, + }, + additionalProperties: false, +} as const; + +type Object = FromSchema; +// => { foo: string; } + +type Object = FromSchema< + typeof defaultedProp, + { keepDefaultedPropertiesOptional: true } +>; +// => { foo?: string; } +``` + `FromSchema` partially supports the `additionalProperties` and `patternProperties` keywords: - `additionalProperties` can be used to deny additional properties. diff --git a/src/definitions/fromSchemaOptions.ts b/src/definitions/fromSchemaOptions.ts index 5ec7d34..417c37a 100644 --- a/src/definitions/fromSchemaOptions.ts +++ b/src/definitions/fromSchemaOptions.ts @@ -12,6 +12,7 @@ import type { export type FromSchemaOptions = { parseNotKeyword?: boolean; parseIfThenElseKeywords?: boolean; + keepDefaultedPropertiesOptional?: boolean; references?: JSONSchema7Reference[] | false; deserialize?: DeserializationPattern[] | false; }; @@ -23,6 +24,7 @@ export type FromExtendedSchemaOptions = { parseNotKeyword?: boolean; parseIfThenElseKeywords?: boolean; + keepDefaultedPropertiesOptional?: boolean; references?: ExtendedJSONSchema7Reference[] | false; deserialize?: DeserializationPattern[] | false; }; @@ -33,6 +35,7 @@ export type FromExtendedSchemaOptions = export type FromSchemaDefaultOptions = { parseNotKeyword: false; parseIfThenElseKeywords: false; + keepDefaultedPropertiesOptional: false; references: false; deserialize: false; }; diff --git a/src/parse-options.ts b/src/parse-options.ts index f7d45a2..d9621c1 100644 --- a/src/parse-options.ts +++ b/src/parse-options.ts @@ -33,6 +33,9 @@ export type ParseOptions< parseIfThenElseKeywords: OPTIONS["parseIfThenElseKeywords"] extends boolean ? OPTIONS["parseIfThenElseKeywords"] : FromSchemaDefaultOptions["parseIfThenElseKeywords"]; + keepDefaultedPropertiesOptional: OPTIONS["keepDefaultedPropertiesOptional"] extends boolean + ? OPTIONS["keepDefaultedPropertiesOptional"] + : FromSchemaDefaultOptions["keepDefaultedPropertiesOptional"]; rootSchema: ROOT_SCHEMA; references: OPTIONS["references"] extends JSONSchema7Reference[] ? IndexReferencesById diff --git a/src/parse-options.type.test.ts b/src/parse-options.type.test.ts index 0756d64..7ed7ca2 100644 --- a/src/parse-options.type.test.ts +++ b/src/parse-options.type.test.ts @@ -29,6 +29,7 @@ type ReceivedOptions = ParseOptions; type ExpectedOptions = { parseNotKeyword: FromSchemaDefaultOptions["parseNotKeyword"]; parseIfThenElseKeywords: FromSchemaDefaultOptions["parseIfThenElseKeywords"]; + keepDefaultedPropertiesOptional: FromSchemaDefaultOptions["keepDefaultedPropertiesOptional"]; deserialize: FromSchemaDefaultOptions["deserialize"]; rootSchema: RootSchema; references: IndexReferencesById; diff --git a/src/parse-schema/index.ts b/src/parse-schema/index.ts index b19c211..e956c21 100644 --- a/src/parse-schema/index.ts +++ b/src/parse-schema/index.ts @@ -31,6 +31,10 @@ export type ParseSchemaOptions = { * Wether to parse ifThenElse schemas or not (false by default) */ parseIfThenElseKeywords: boolean; + /** + * Wether to keep object defaulted properties optional or not (false by default) + */ + keepDefaultedPropertiesOptional: boolean; /** * The initial schema provided to `ParseSchema` */ diff --git a/src/parse-schema/object.ts b/src/parse-schema/object.ts index 9f89794..b14dd1e 100644 --- a/src/parse-schema/object.ts +++ b/src/parse-schema/object.ts @@ -39,12 +39,12 @@ export type ParseObjectSchema< OPTIONS >; }, - GetRequired, + GetRequired, GetOpenProps > : M.$Object< {}, - GetRequired, + GetRequired, GetOpenProps >; @@ -53,10 +53,27 @@ export type ParseObjectSchema< * @param OBJECT_SCHEMA JSONSchema (object type) * @returns String */ -type GetRequired = - OBJECT_SCHEMA extends Readonly<{ required: ReadonlyArray }> - ? OBJECT_SCHEMA["required"][number] - : never; +type GetRequired< + OBJECT_SCHEMA extends ObjectSchema, + OPTIONS extends ParseSchemaOptions, +> = + | (OBJECT_SCHEMA extends Readonly<{ required: ReadonlyArray }> + ? OBJECT_SCHEMA["required"][number] + : never) + | (OPTIONS["keepDefaultedPropertiesOptional"] extends true + ? never + : OBJECT_SCHEMA extends Readonly<{ + properties: Readonly>; + }> + ? { + [KEY in keyof OBJECT_SCHEMA["properties"] & + string]: OBJECT_SCHEMA["properties"][KEY] extends Readonly<{ + default: unknown; + }> + ? KEY + : never; + }[keyof OBJECT_SCHEMA["properties"] & string] + : never); /** * Extracts and parses the additional and pattern properties (if any exists) of an object JSON schema diff --git a/src/tests/readme/object.type.test.ts b/src/tests/readme/object.type.test.ts index 7603e3a..dbb2487 100644 --- a/src/tests/readme/object.type.test.ts +++ b/src/tests/readme/object.type.test.ts @@ -77,3 +77,36 @@ type AssertObjectWithTypedAdditionalProperties = A.Equals< >; const assertObjectWithTypedAdditionalProperties: AssertObjectWithTypedAdditionalProperties = 1; assertObjectWithTypedAdditionalProperties; + +// Defaulted property + +const objectWithDefaultedPropertySchema = { + type: "object", + properties: { + foo: { type: "string", default: "bar" }, + }, + additionalProperties: false, +} as const; + +type ReceivedObjectWithDefaultedProperty = FromSchema< + typeof objectWithDefaultedPropertySchema +>; +type ExpectedObjectWithDefaultedProperty = { foo: string }; +type AssertObjectWithDefaultedProperty = A.Equals< + ReceivedObjectWithDefaultedProperty, + ExpectedObjectWithDefaultedProperty +>; +const assertObjectWithDefaultedProperty: AssertObjectWithDefaultedProperty = 1; +assertObjectWithDefaultedProperty; + +type ReceivedObjectWithDefaultedProperty2 = FromSchema< + typeof objectWithDefaultedPropertySchema, + { keepDefaultedPropertiesOptional: true } +>; +type ExpectedObjectWithDefaultedProperty2 = { foo?: string }; +type AssertObjectWithDefaultedProperty2 = A.Equals< + ReceivedObjectWithDefaultedProperty2, + ExpectedObjectWithDefaultedProperty2 +>; +const assertObjectWithDefaultedProperty2: AssertObjectWithDefaultedProperty2 = 1; +assertObjectWithDefaultedProperty2;