Closed piyush-nudge closed 3 months ago
@piyush-nudge Hi, thanks for letting me know.
See below which fixes for the Enum case (updates to PartialDeep
and TPartialDeep
respectively). The issue here is that TypeBox treats enums as TUnion<TLiteral[]>
and where the previous infer logic was getting hung on the defacto Union check. I've added a TEnum<...>
clause first in the inference and runtime expression which short circuts the check specifically for enums. This should solve the issue.
import { TypeGuard, Type, TSchema, TIntersect, TEnum, TUnion, TObject, TPartial, TProperties, Evaluate } from '@sinclair/typebox'
// -------------------------------------------------------------------------------------
// TPartialDeepProperties
// -------------------------------------------------------------------------------------
export type TPartialDeepProperties<T extends TProperties> = {
[K in keyof T]: TPartialDeep<T[K]>
}
function PartialDeepProperties<T extends TProperties>(properties: T): TPartialDeepProperties<T> {
return Object.getOwnPropertyNames(properties).reduce((acc, key) => {
return {...acc, [key]: PartialDeep(properties[key])}
}, {}) as never
}
// -------------------------------------------------------------------------------------
// TPartialDeepRest
// -------------------------------------------------------------------------------------
export type TPartialDeepRest<T extends TSchema[], Acc extends TSchema[] = []> = (
T extends [infer L extends TSchema, ...infer R extends TSchema[]]
? TPartialDeepRest<R, [...Acc, TPartialDeep<L>]>
: Acc
)
function PartialDeepRest<T extends TSchema[]>(rest: [...T]): TPartialDeepRest<T> {
return rest.map(schema => PartialDeep(schema)) as never
}
// -------------------------------------------------------------------------------------
// TPartialDeep
// -------------------------------------------------------------------------------------
export type TPartialDeep<T extends TSchema> =
T extends TEnum<infer S> ? TEnum<S> : // added - enum values are opaque to the type system, we just infer for S
T extends TIntersect<infer S> ? TIntersect<TPartialDeepRest<S>> :
T extends TUnion<infer S> ? TUnion<TPartialDeepRest<S>> :
T extends TObject<infer S> ? TPartial<TObject<Evaluate<TPartialDeepProperties<S>>>> :
T
export function PartialDeep<T extends TSchema>(schema: T): TPartialDeep<T> {
return (
TypeGuard.IsUnionLiteral(schema) ? schema : // added - enum representations are expressed as TUnion<TLiteral[]>
TypeGuard.IsIntersect(schema) ? Type.Intersect(PartialDeepRest(schema.allOf)) :
TypeGuard.IsUnion(schema) ? Type.Union(PartialDeepRest(schema.anyOf)) :
TypeGuard.IsObject(schema) ? Type.Partial(Type.Object(PartialDeepProperties(schema.properties))) :
schema
) as never
}
// -------------------------------------------------------------
// Example
// -------------------------------------------------------------
import { type Static } from '@sinclair/typebox'
export enum PagesStatus {
ACTIVE = "active",
INACTIVE = "inactive",
DELETED = "deleted"
}
export const pagesSchema = PartialDeep(Type.Object({
id: Type.Optional(Type.String()),
name: Type.String(),
page_id: Type.String(),
status: Type.Enum(PagesStatus),
properties: Type.Object({}),
components: Type.Array(Type.Unknown()), // missing schema
image_key: Type.String(),
image_url: Type.Optional(Type.String()),
created_by: Type.String(),
app_version: Type.String(),
tag: Type.String(),
used_in: Type.Optional(Type.Array(Type.String())),
created_at: Type.Optional(Type.Date()),
updated_at: Type.Optional(Type.Date()),
}))
type T = Static<typeof pagesSchema> // type T = { ..., status?: PageStatus | undefined }
I'll update the prototype later tonight, did you want to give this a test run your side? Keep me posted S
Hi @sinclairzx81 , thank you for the quick response. The modified implementation you provided above works correctly for our case and gives the intended types. It is interesting that typebox treats Enums as Union of Literals. So I just wanted to ask is there actually any difference while defining a schema using Enums and Union Literals or they both have the same implementation under-the-hood, and also it would be nice to get some insights into performance when using either of those.
Hi @sinclairzx81 ,
While using the partial deep implementation on a schema, if there are any enums involved in the schema, their values are becoming null. For example, if the schema looks like this:
and PageStatus is:
the compiled value after using the partial deep implementation mentioned here, the schema's type becomes this:
But if we use
Type.Union([ Type.Literal("..."), Type.Literal("..."), Type.Literal("...") ])
instead of Enums, the type of Tpages becomes what we intend. Why is this occuring, is it because of the implementation of DeepPartial for enums or theStatic
when we are using for the type of TPages?