Inferred type of read-only objects isn't read-only #866
-
Hi all Just wondering why the following does not infer a read-only type.
I know I can add For now, I use |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
Hi, I'm having the same problem as you. |
Beta Was this translation helpful? Give feedback.
-
TypeBox doesn't apply the readOnly annotation by default. The reasons for this are largely historical and mostly due to how TypeBox internally handles inference and type differentiation, however you can apply the annotation in the following way. import { Type, TSchema, TReadonly } from '@sinclair/typebox'
function ReadonlyWithAnnotation<T extends TSchema>(schema: T): TReadonly<T> {
return Type.Readonly({ ...schema, readOnly: true }) as never
}
const T = Type.Object({
x: ReadonlyWithAnnotation(Type.Number())
}) I've been meaning to take another look into this, but wouldn't be able to make any changes until a minor semver revision (this due to the addition of the Hope this helps |
Beta Was this translation helpful? Give feedback.
-
@sdc395 Sorry, just to respond to your original comment...
The Type.Readonly modifier is representative of the ReadonlyDeepYou can however implement the TS utility type in the following way (TypeBox may provide this type in future (just need a reasonable name for it)) import * as Type from '@sinclair/typebox'
// ------------------------------------------------------------------
// ReadonlyProperties
// ------------------------------------------------------------------
export type TReadonlyProperties<T extends Type.TProperties> = Type.Evaluate<{
[K in keyof T]: TReadonlyDeep<T[K]>
}>
function ReadonlyProperties<T extends Type.TProperties>(properties: T) {
return globalThis.Object.getOwnPropertyNames(properties).reduce((Acc, K) => {
return { ...Acc, [K]: ReadonlyDeep(properties[K]) }
}, {})
}
// ------------------------------------------------------------------
// ReadonlyRest
// ------------------------------------------------------------------
export type TReadonlyRest<T extends Type.TSchema[], Acc extends Type.TSchema[] = []> = (
T extends [infer L extends Type.TSchema, ...infer R extends Type.TSchema[]]
? TReadonlyRest<R, [...Acc, TReadonlyDeep<L>]>
: Acc
)
function ReadonlyRest<T extends Type.TSchema[]>(schema: [...T]): TReadonlyRest<T> {
return schema.map(schema => ReadonlyDeep(schema)) as never
}
// ------------------------------------------------------------------
// ReadonlyDeep
// ------------------------------------------------------------------
export type TReadonlyDeep<T extends Type.TSchema> =
T extends Type.TIntersect<infer S extends Type.TSchema[]> ? Type.TReadonly<Type.TIntersect<TReadonlyRest<S>>> :
T extends Type.TUnion<infer S extends Type.TSchema[]> ? Type.TReadonly<Type.TUnion<TReadonlyRest<S>>> :
T extends Type.TTuple<infer S extends Type.TSchema[]> ? Type.TReadonly<Type.TUnion<TReadonlyRest<S>>> :
T extends Type.TObject<infer S extends Type.TProperties> ? Type.TReadonly<Type.TObject<TReadonlyProperties<S>>> :
T extends Type.TArray<infer S extends Type.TSchema> ? Type.TReadonly<Type.TArray<TReadonlyDeep<S>>> :
Type.TReadonly<T>
function ReadonlyDeep<T extends Type.TSchema>(schema: T): TReadonlyDeep<T> {
return (
Type.TypeGuard.IsIntersect(schema) ? Type.Readonly(Type.Intersect(ReadonlyRest(schema.allOf))) :
Type.TypeGuard.IsUnion(schema) ? Type.Readonly(Type.Union(ReadonlyRest(schema.anyOf))) :
Type.TypeGuard.IsObject(schema) ? Type.Readonly(Type.Object(ReadonlyProperties(schema.properties))) :
Type.TypeGuard.IsArray(schema) ? Type.Readonly(Type.Array(ReadonlyDeep(schema.items))) :
Type.Readonly(schema)
) as never
}
// ------------------------------------------------------------------
// Example
// ------------------------------------------------------------------
const T = ReadonlyDeep(Type.Object({
x: Type.Number(),
y: Type.Object({
z: Type.Number()
})
}))
type T = Type.Static<typeof T> In the nearer term, I may consider adding the above as a prototype for inclusion in later revisions. Hope this brings some insight |
Beta Was this translation helpful? Give feedback.
@sdc395 Sorry, just to respond to your original comment...
The Type.Readonly modifier is representative of the
readonly
syntax modifier, not representative of theReadonly
TS utility type (and it's a bit unfortunate about the naming conflict there). As of current, the Type.Readonly modifier only applies the top level type, it doesn't deeply traverse into the type.ReadonlyDeep
You can however implement the TS utility type in the following way (TypeBox may provide this type in future (just need a reasonable name for it))
TypeScript Link Here