fabian-hiller / valibot

The modular and type safe schema library for validating structural data 🤖

Home Page:https://valibot.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add passthrough object schema

vladshcherbin opened this issue · comments

Hey 👋

I'm porting my projects to use valibot instead of zod and would love to have some kind of passthrough object schema.

My use case:

I'm validating API requests with lots of nested objects and want to keep unknown props while validating the ones in schema.

With rest object param I can do it this way:

object({ location: string() }, unknown())

It works great.

However with nested objects it becomes hard to track this objects, for example:

Nested object example
const schema = object({
  adverts: array(
    object({
      id: number(),
      locationName: string(),
      metadata: object({
        brandId: number(),
        modelId: number()
      }, unknown()),
      photos: array(
        object({
          big: object({
            height: number(),
            url: string([url()]),
            width: number()
          }, unknown()),
          main: boolean(),
          mimeType: picklist(['image/jpeg', 'image/png'])
        }, unknown())
      ),
      price: object({
        usd: object({
          amountFiat: number(),
          currency: literal('usd')
        }, unknown())
      }, unknown()),
      properties: array(
        object({
          name: string(),
          value: union([boolean(), number(), string()])
        }, unknown()),
        [minLength(1)]
      ),
      publicUrl: string([url()]),
      year: number()
    }, unknown())
  ),
  currentSorting: object({
    label: literal('новые объявления')
  }),
  pageCount: number()
}, unknown())

For me it's quite hard to track such objects, check any missing unknown() rest types, add new objects 😢

zod has passthrough() method with pending feature request to have global method.

Feature request

Imagine having a simple type for this in valibot. Just compare:

Proposed api example
const schema = passthroughObject({
  adverts: array(
    passthroughObject({
      id: number(),
      locationName: string(),
      metadata: passthroughObject({
        brandId: number(),
        modelId: number()
      }),
      photos: array(
        passthroughObject({
          big: passthroughObject({
            height: number(),
            url: string([url()]),
            width: number()
          }),
          main: boolean(),
          mimeType: picklist(['image/jpeg', 'image/png'])
        })
      ),
      price: passthroughObject({
        usd: passthroughObject({
          amountFiat: number(),
          currency: literal('usd')
        })
      }),
      properties: array(
        passthroughObject({
          name: string(),
          value: union([boolean(), number(), string()])
        }),
        [minLength(1)]
      ),
      publicUrl: string([url()]),
      year: number()
    })
  ),
  currentSorting: object({
    label: literal('новые объявления')
  }),
  pageCount: number()
})

It's so clean, easy to follow, add, change, etc. It can also have a different name.

Please help reduce the pain of validating such nested objects, I pray for this feature for years 🙏

You could add passthroughObject on your own by just wrapping object:

export function passthroughObject<TEntries extends v.ObjectEntries>(
  entries: TEntries
): v.ObjectSchema<TEntries, v.UnknownSchema> {
  return v.object(entries, v.unknown());
}

@fabian-hiller thank you a lot for the example, I'll use it as for now ♥️

Hopefully it can be added to core and help other developers too. I actually need it in almost every project with external API requests.

I will think about it. At the moment I prefer strong primitives over specialized functions. Also, I don't want there to be too many ways to achieve the same things within a library, because that makes usage confusing in my eyes.

The latest version now comes with a looseObject schema 🧩

@fabian-hiller perfect, thank you! 🚀