diff --git a/package-lock.json b/package-lock.json index aa7cef85b..86b31daf8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.33.6", + "version": "0.33.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.33.6", + "version": "0.33.7", "license": "MIT", "devDependencies": { "@arethetypeswrong/cli": "^0.13.2", diff --git a/package.json b/package.json index 299a5e2dd..aae8d9de3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.33.6", + "version": "0.33.7", "description": "Json Schema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/src/value/default/default.ts b/src/value/default/default.ts index abda4cdd4..27b05f555 100644 --- a/src/value/default/default.ts +++ b/src/value/default/default.ts @@ -44,7 +44,7 @@ import type { TUnion } from '../../type/union/index' // ------------------------------------------------------------------ // ValueGuard // ------------------------------------------------------------------ -import { IsString, IsObject, IsArray, IsUndefined } from '../guard/index' +import { IsString, IsObject, IsArray, IsUndefined, HasPropertyKey } from '../guard/index' // ------------------------------------------------------------------ // TypeGuard // ------------------------------------------------------------------ @@ -52,8 +52,9 @@ import { IsKind } from '../../type/guard/kind' // ------------------------------------------------------------------ // ValueOrDefault // ------------------------------------------------------------------ -function ValueOrDefault(schema: TSchema, value: unknown) { - return value === undefined && 'default' in schema ? Clone(schema.default) : value +function ValueOrDefault(schema: TSchema, value: unknown): unknown { + const clone = HasPropertyKey(schema, 'default') ? Clone(schema.default) : undefined + return IsUndefined(value) ? clone : IsObject(value) && IsObject(clone) ? Object.assign(clone, value) : value } // ------------------------------------------------------------------ // HasDefaultProperty @@ -81,6 +82,7 @@ function FromIntersect(schema: TIntersect, references: TSchema[], value: unknown } function FromObject(schema: TObject, references: TSchema[], value: unknown): any { const defaulted = ValueOrDefault(schema, value) + // return defaulted if (!IsObject(defaulted)) return defaulted const knownPropertyKeys = Object.getOwnPropertyNames(schema.properties) // properties @@ -90,7 +92,7 @@ function FromObject(schema: TObject, references: TSchema[], value: unknown): any // a non assignable property and continue. const propertyValue = Visit(schema.properties[key], references, defaulted[key]) if (IsUndefined(propertyValue)) continue - defaulted[key] = propertyValue + defaulted[key] = Visit(schema.properties[key], references, defaulted[key]) } // return if not additional properties if (!HasDefaultProperty(schema.additionalProperties)) return defaulted diff --git a/test/runtime/value/default/object.ts b/test/runtime/value/default/object.ts index ddfad94a6..86a7df19f 100644 --- a/test/runtime/value/default/object.ts +++ b/test/runtime/value/default/object.ts @@ -213,19 +213,47 @@ describe('value/default/Object', () => { // Traveral: https://github.com/sinclairzx81/typebox/issues/962 // ---------------------------------------------------------------- it('Should traverse into an object 1 (initialize)', () => { - const Child = Type.Object({ a: Type.String({ default: 'x' }) }) - const Parent = Type.Object({ child: Child }) - Assert.IsEqual(Value.Default(Child, {}), { a: 'x' }) - Assert.IsEqual(Value.Default(Parent, { child: {} }), { child: { a: 'x' } }) + const Y = Type.Object({ y: Type.String({ default: 'y' }) }) + const X = Type.Object({ x: Y }) + Assert.IsEqual(Value.Default(Y, {}), { y: 'y' }) + Assert.IsEqual(Value.Default(X, { x: {} }), { x: { y: 'y' } }) }) it('Should traverse into an object 2 (retain)', () => { - const Child = Type.Object({ a: Type.String({ default: 'x' }) }) - const Parent = Type.Object({ child: Child }) - Assert.IsEqual(Value.Default(Parent, { child: { a: 1 } }), { child: { a: 1 } }) + const Y = Type.Object({ y: Type.String({ default: 'y' }) }) + const X = Type.Object({ x: Y }) + Assert.IsEqual(Value.Default(X, { x: { y: 1 } }), { x: { y: 1 } }) }) it('Should traverse into an object 3 (ignore on undefined)', () => { - const Child = Type.Object({ a: Type.String({ default: 'x' }) }) - const Parent = Type.Object({ child: Child }) - Assert.IsEqual(Value.Default(Parent, { child: undefined }), { child: undefined }) + const Y = Type.Object({ y: Type.String({ default: 'y' }) }) + const X = Type.Object({ x: Y }) + Assert.IsEqual(Value.Default(X, { x: undefined }), { x: undefined }) + }) + // ---------------------------------------------------------------- + // Exterior Object Defaults + // ---------------------------------------------------------------- + it('Should default exterior into an object 1', () => { + const X = Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) + const R = Value.Default(X, undefined) + Assert.IsEqual(R, { x: 1 }) + }) + it('Should default exterior into an object 2', () => { + const X = Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) + const R = Value.Default(X, {}) + Assert.IsEqual(R, { x: 1 }) + }) + it('Should default exterior into an object 3', () => { + const X = Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) + const R = Value.Default(X, { y: 3 }) + Assert.IsEqual(R, { y: 3, x: 1 }) + }) + it('Should default exterior into an object 4', () => { + const X = Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) + const R = Value.Default(X, { y: 3, x: 7 }) + Assert.IsEqual(R, { y: 3, x: 7 }) + }) + it('Should default exterior into an object 5', () => { + const X = Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) + const R = Value.Default(X, { x: 2 }) + Assert.IsEqual(R, { x: 2 }) }) })