Closed isaac-scarrott closed 1 year ago
@isaac-scarrott Hi!
Unfortunately, TypeBox utility types (such as Type.Partial(T)
) are built to align to the default TypeScript Utility Types only. Adding a TypeBox Type.DeepPartial()
or (or {deep: true}
) option would be considered "out of scope" from a TypeBox perspective (although I'm aware that quite a few other libraries implement this type)
It should be possible to construct a DeepPartial type using the following.
import { Type, Static, TObject, TProperties, TUnsafe } from '@sinclair/typebox'
// ---------------------------------------------------------------
// Deep Partial
// ---------------------------------------------------------------
type DeepPartial<T extends Record<any, any>> = {
[K in keyof T]?: T[K] extends Record<any, any> ?
DeepPartial<T[K]> :
T[K]
}
function DeepPartial<T extends TObject>(schema: T): TUnsafe<DeepPartial<Static<T>>> {
const properties = Object.keys(schema.properties).reduce((acc, key) => {
const property = schema.properties[key]
const mapped = (property.type === 'object') ? DeepPartial(property as TObject) : property
return { ...acc, [key]: Type.Optional(mapped)}
}, {}) as TProperties
return Type.Object({ ...properties })
}
// ---------------------------------------------------------------
// Example
// ---------------------------------------------------------------
const Example = Type.Object({
id: Type.String(),
testingOptions: Type.Object({
isTest: Type.Boolean(),
}),
});
const DeepPartialExample = DeepPartial(Example)
type DeepPartialExample = Static<typeof DeepPartialExample>
There is currently some work / research being done to try and introduce Mapped Typed functionality to TypeBox. As DeepPartial is typically implemented by way of a recursive mapped type, the following would be an example of how TypeBox hopes to see this functionality implemented one day.
// ----------------------------------------------
// TypeBox: Recursive Mapped Type
// ----------------------------------------------
// type DeepPartial<T extends Record<any, any>> = {
// [K in keyof T]?: T[K] extends Record<any, any> ?
// DeepPartial<T[K]> :
// T[K]
// }
const DeepPartial = <T extends TObject>(schema: T) => Type.Mapped(schema, (T, K) => {
return (TypeGuard.TObject(T[K])) ?
Type.Optional(DeepPartial(T[K])) :
Type.Optional(T[K])
})
// ----------------------------------------------
// Example
// ----------------------------------------------
const Example = Type.Object({
id: Type.String(),
testingOptions: Type.Object({
isTest: Type.Boolean(),
}),
});
const DeepPartialExample = DeepPartial(Example)
type DeepPartialExample = Static<typeof DeepPartialExample>
Hope this helps S
@sinclairzx81 Hi and thanks for the quick and detailed response!
Yep that seems like a good workaround for now and looking forward to the Mapped Type function.
Thanks again
Hi, I've implemented this however I'm getting an error when using in Type.Intersect
as follows:
const Example = Type.Object({
id: Type.String(),
testingOptions: Type.Object({
isTest: Type.Boolean(),
}),
});
const UpdateExampleSchema = Type.Intersect([
DeepPartial(Example),
Type.Object({ lastModified: TypeDate }),
]);
// error "...is missing the following properties from type 'TObject<TProperties>': type, propertiests"
Wondering if you have a good way to mitigate this @sinclairzx81 as we need lastModified
as required, however everything else optional?
@isaac-scarrott Try
export type DeepPartial<T extends Record<any, any>> = {
[K in keyof T]?: T[K] extends Record<any, any> ? DeepPartial<T[K]> : T[K]
}
// Specialized TObject type that can be passed to TIntersect
export interface TDeepPartial<T extends TObject> extends TObject {
static: DeepPartial<Static<T>>
}
export function DeepPartial<T extends TObject>(schema: T): TDeepPartial<T> {
const properties = Object.keys(schema.properties).reduce((acc, key) => {
const property = schema.properties[key]
const mapped = (property.type === 'object') ? DeepPartial(property as TObject) : property
return { ...acc, [key]: Type.Optional(mapped)}
}, {}) as TProperties
return Type.Object({ ...properties }) as TDeepPartial<T> // required
}
Yep perfect, thanks @sinclairzx81 !
I was wondering if it was possible to add a deep partial to the project. Potentially though a option on the
Partial
function or a separate function calledDeepPartial
as currently it will only make the top level of the object partial and sub objects remain as defined.For example
Could look something similar to: