Comments (3)
@oshox Hi,
Is this behavior expected and the result of me doing something wrong?
This mostly comes down to TypeScript not having adequate information to derive the correct types from the runtime implementation of schema<...>()
. Given the complexity involved mapping from typeof schemasObject
into the target structures, you're going to need to go a bit "deeper" on type programmability (which is largely unavoidable)
I've had go reimplementing the type and runtime logic using similar techniques TypeBox uses internally. Generally the implementation of these kinds of mappings require you to define the source structures (Source Types) and the associated runtime / static logic that gradually remap the type at varying levels. Given the requirement to rename property names, and generate mappings of custom schematics, things can get a bit complex.
The following implementation has been tested locally as should work ok. Scroll down for the Usage
.
TypeScript Inference Example Here
import * as TB from '@sinclair/typebox'
// ------------------------------------------------------------------
// Source Types
// ------------------------------------------------------------------
type Descriptor = Readonly<{ type: 'number' | 'text', display: string }>
type Schema = Readonly<{ [key: string]: Descriptor }>
type Schemas = Readonly<{ [key: string]: Schema }>
// ------------------------------------------------------------------
// TFromDescriptor
// ------------------------------------------------------------------
type TFromDescriptor<T extends Schema, K extends PropertyKey> = K extends keyof T
? TB.Evaluate<Record<`${TB.Assert<K, string>}_filter`,
T[K]['type'] extends 'number' ? TB.TOptional<TB.TNumber> :
T[K]['type'] extends 'text' ? TB.TOptional<TB.TString> :
TB.TNever>>
: {}
function FromDescriptor<T extends Schema, K extends keyof T>(schema: T, key: K): TFromDescriptor<T, K> {
return {
[`${key as string}_filter`]:
schema[key]['type'] === 'number' ? TB.Optional(TB.Number(schema[key])) :
schema[key]['type'] === 'text' ? TB.Optional(TB.String(schema[key])) :
TB.Never()
} as never
}
// ------------------------------------------------------------------
// FromSchemaReduce
// ------------------------------------------------------------------
type TFromSchemaReduce<T extends Schema, K extends PropertyKey[], Acc extends Record<PropertyKey, TB.TSchema> = {}> = (
K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]]
? TFromSchemaReduce<T, R, Acc & TFromDescriptor<T, L>>
: TB.TObject<TB.Evaluate<Acc>>
)
function FromSchemaReduce<T extends Schema, K extends PropertyKey[]>(schema: T, keys: K): TFromSchemaReduce<T, K> {
const properties = keys.reduce((Acc, L) => {
return { ...Acc, ...FromDescriptor(schema, L as keyof T) }
}, {} as TB.TProperties)
return TB.Type.Object(properties, { additionalProperties: false }) as never
}
// ------------------------------------------------------------------
// FromSchema
// ------------------------------------------------------------------
type TFromSchema<T extends Schema, K extends PropertyKey[] = TB.UnionToTuple<keyof T>> = TFromSchemaReduce<T, K>
function FromSchema<T extends Schema>(schema: T): TFromSchema<T> {
return FromSchemaReduce(schema, Object.keys(schema)) as never
}
// ------------------------------------------------------------------
// TFromSchemas
// ------------------------------------------------------------------
type TFromSchemas<T extends Schemas> = TB.Evaluate<{
-readonly [K in keyof T]: TFromSchema<T[K]>
}>
function FromSchemas<T extends Schemas>(schemas: T): TFromSchemas<T> {
return Object.keys(schemas).reduce((Acc, K) => {
return { ...Acc, [K]: FromSchema(schemas[K]) }
}, {}) as never
}
// ------------------------------------------------------------------
// Usage
// ------------------------------------------------------------------
import { Static } from '@sinclair/typebox'
import { TypeCompiler } from '@sinclair/typebox/compiler'
const Mapped = FromSchemas({
foo: { text1: { type: "text", display: "Text 1" }, number1: { type: "number", display: "Number 1" } },
bar: { text2: { type: "text", display: "Text 2" }, number2: { type: "number", display: "Number 2" } }
} as const)
const fooSchema = Mapped["foo"]
type FooSchema = Static<typeof fooSchema>
const barSchema = Mapped["bar"]
type BarSchema = Static<typeof barSchema>
const fooValidator = TypeCompiler.Compile(fooSchema)
const barValidator = TypeCompiler.Compile(barSchema)
const fooObject = {
number1_filter: 123,
text1_filter: 'example'
}
const barObject = {
number2_filter: 13,
text2_filter: 'example2'
}
console.log('Valid object is valid:', fooValidator.Check(fooObject))
// console.log('Errors:', ...fooValidator.Errors(fooObject))
console.log('Invalid object is valid:', fooValidator.Check(barObject))
// console.log('Errors', ...fooValidator.Errors(barObject))
console.log('Valid object is valid:', barValidator.Check(barObject))
/// console.log('Errors:', ...barValidator.Errors(barObject))
console.log('Invalid object is valid:', barValidator.Check(fooObject))
// console.log('Errors', ...barValidator.Errors(fooObject))
Hope this helps
S
from typebox.
@oshox Hiya,
Might close off this issue as the above example should provide a good reference as to how to approach advanced type mapping in TypeBox. As noted, the inference isn't working here (as per original snippet) due to TypeScript being unable to derive the exact types from the function implementation. In cases like this you will need to help it along by writing explicit mappings (on the return type) that computes the anticipated output type.
If you have any other questions, feel free to ping on this thread.
Cheers!
S
from typebox.
Thank you for the response. You explanation solves my issue, and I will be able to implement those concepts in my other projects.
I appreciate your time. Please close the issue.
from typebox.
Related Issues (20)
- Encoding does not ignore undefined values HOT 2
- Incorrect symbol escapement in record keys HOT 2
- Value.Hash doesn't work with Unicode strings HOT 3
- Given a type, convert it to Typebox HOT 2
- `TypeCheck<any>` results in `TS2589: Type instantiation is excessively deep and possibly infinite.` HOT 2
- `TypeSystem.Type` returning `any` after updating to `0.32.16` HOT 1
- Type.Schema HOT 2
- Preflight validation check failed to guard for the given schema when using `structuredClone` HOT 2
- `Value.Convert` to a `Union` no longer checks destination type validity HOT 2
- Error when trying to use TSchema as a generic HOT 2
- How to use `Value.Check` with raw json schemas? HOT 4
- Regression bug 0.32.19 HOT 2
- TypeGuard.IsEnum HOT 2
- Better pre-validation errors for invalid TypeBox schema HOT 2
- tsconfig "strict": true - ignore modifiers HOT 2
- `Static` types cannot be used to enforce types from a generic parameter HOT 3
- Applying refinements + general advice on my implementation HOT 11
- Unable to create value of custom class type HOT 2
- Migrate to regexp vs string type in pattern HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from typebox.