Giter VIP home page Giter VIP logo

Comments (3)

sinclairzx81 avatar sinclairzx81 commented on June 19, 2024

@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.

sinclairzx81 avatar sinclairzx81 commented on June 19, 2024

@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.

oshox avatar oshox commented on June 19, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.