diff --git a/src/value/cast/cast.ts b/src/value/cast/cast.ts index 999e9379..c3313f7b 100644 --- a/src/value/cast/cast.ts +++ b/src/value/cast/cast.ts @@ -80,8 +80,9 @@ function ScoreUnion(schema: TSchema, references: TSchema[], value: any): number } } function SelectUnion(union: TUnion, references: TSchema[], value: any): TSchema { - let [select, best] = [union.anyOf[0], 0] - for (const schema of union.anyOf) { + const schemas = union.anyOf.map((schema) => Deref(schema, references)) + let [select, best] = [schemas[0], 0] + for (const schema of schemas) { const score = ScoreUnion(schema, references, value) if (score > best) { select = schema diff --git a/src/value/deref/deref.ts b/src/value/deref/deref.ts index c51fc23f..91a77915 100644 --- a/src/value/deref/deref.ts +++ b/src/value/deref/deref.ts @@ -30,15 +30,22 @@ import type { TSchema } from '../../type/schema/index' import type { TRef } from '../../type/ref/index' import type { TThis } from '../../type/recursive/index' import { TypeBoxError } from '../../type/error/index' +import { Kind } from '../../type/symbols/index' export class TypeDereferenceError extends TypeBoxError { constructor(public readonly schema: TRef | TThis) { super(`Unable to dereference schema with $id '${schema.$id}'`) } } +function Resolve(schema: TThis | TRef, references: TSchema[]): TSchema { + const target = references.find((target) => target.$id === schema.$ref) + if (target === undefined) throw new TypeDereferenceError(schema) + return Deref(target, references) +} /** Dereferences a schema from the references array or throws if not found */ -export function Deref(schema: TRef | TThis, references: TSchema[]): TSchema { - const index = references.findIndex((target) => target.$id === schema.$ref) - if (index === -1) throw new TypeDereferenceError(schema) - return references[index] +export function Deref(schema: TSchema, references: TSchema[]): TSchema { + // prettier-ignore + return (schema[Kind] === 'This' || schema[Kind] === 'Ref') + ? Resolve(schema as never, references) + : schema } diff --git a/test/runtime/value/cast/union.ts b/test/runtime/value/cast/union.ts index 33d77b41..d9ae7302 100644 --- a/test/runtime/value/cast/union.ts +++ b/test/runtime/value/cast/union.ts @@ -145,4 +145,20 @@ describe('value/cast/Union', () => { value: 'B', }) }) + // ---------------------------------------------------------------- + // https://github.com/sinclairzx81/typebox/issues/880 + // ---------------------------------------------------------------- + // prettier-ignore + it('Should dereference union variants', () => { + const A = Type.Object({ type: Type.Literal('A') }, { $id: 'A' }) + const B = Type.Object({ type: Type.Literal('B'), value: Type.Number() }, { $id: 'B' }) + const RA = Type.Union([A, B]) + const RB = Type.Union([Type.Ref(A), Type.Ref(B)]) + // variant 0 + Assert.IsEqual(Value.Cast(RA, [A, B], { type: 'B' }), { type: 'B', value: 0 }) + Assert.IsEqual(Value.Cast(RB, [A, B], { type: 'B' }), { type: 'B', value: 0 }) + // variant 1 + Assert.IsEqual(Value.Cast(RA, [A, B], { type: 'A' }), { type: 'A' }) + Assert.IsEqual(Value.Cast(RB, [A, B], { type: 'A' }), { type: 'A' }) + }) })