sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.77k stars 152 forks source link

Doubt on Record #927

Closed jordi-petit closed 2 months ago

jordi-petit commented 2 months ago

I would like to define a type that represents a dictionary of, say, users indexed by their keys (strings). The Record type looks like the proper candidate and, indeed, does the job:

import { Type, type Static } from '@sinclair/typebox'

const TUser = Type.Object({
    name: Type.String(),
    email: Type.String(),
})

const TUserDict = Type.Record(Type.String(), TUser)

type TUser = Static<typeof TUser>

type TUserDict = Static<typeof TUserDict>

const alice: TUser = {
    name: 'Alice',
    email: 'alice@a.com',
}

const bob: TUser = {
    name: 'Bob',
    email: 'bob@a.com',
}

const users: TUserDict = {
    'alice': alice,
    'bob': bob,
}

console.log(JSON.stringify(TUserDict, null, 2))

However, the resulting schema is the following:

{
  "type": "object",
  "patternProperties": {
    "^(.*)$": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "email": {
          "type": "string"
        }
      },
      "required": [ "name",  "email" ]
    }
  }
}

where the use of patternProperties seems odd, because:

Could you please clarify how to express the concept of dictionary (or associative array) with TypeBox in a way that is interoperable with other tools?

PS. I have seen there was a Dict type that is now deprecated.

Thanks for your work!

sinclairzx81 commented 2 months ago

@jordi-petit Hi,

TypeBox schematics are based on the Json Schema specification. The patternProperties keyword is a formal keyword in that specification, but not necessarily in OpenAPI (which historically has been at odds with the Json Schema specification). I believe this has improved in OpenAPI 3.1 which suggests support for Json Schema Draft 2020-12.

Information on patternProperties can be found at the following link.

https://json-schema.org/understanding-json-schema/reference/object#patternProperties

Could you please clarify how to express the concept of dictionary (or associative array) with TypeBox in a way that is interoperable with other tools?

It is possible to construct custom schematics using the Unsafe type. This type provides an escape hatch and allows you to specify arbitrary schematics with custom inference.

import { Type, Static } from '@sinclair/typebox'

type TUser = Static<typeof TUser>
const TUser = Type.Object({
  name: Type.String(),
  email: Type.String(),
})

type TUserDict = Static<typeof TUserDict>
const TUserDict = Type.Unsafe<Record<string, TUser>>({
  type: 'object',
  additionalProperties: TUser
})

TypeBox has partial validation support for Records created in this way, but does require a slightly different definition.

import { Type, Static } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'

type TUser = Static<typeof TUser>
const TUser = Type.Object({
  name: Type.String(),
  email: Type.String(),
})

type TUserDict = Static<typeof TUserDict>
const TUserDict = Type.Unsafe<Record<string, TUser>>(Type.Object({}, {
  additionalProperties: TUser
}))

// works only with the above definition of TUserDict
const R = Value.Check(TUserDict, {
  user1: { name: 'user1', email: 'user1@domain.com' },
  user2: { name: 'user2', email: 'user2@domain.com' }
})

console.log(R) // true

Hope this helps S

jordi-petit commented 2 months ago

Thanks for your help, the distinction between openapi and its different versions and json schema is important, indeed.