colinhacks / zod

TypeScript-first schema validation with static type inference
https://zod.dev
MIT License
33.12k stars 1.15k forks source link

z.ObjectID() ? #318

Closed alexnix closed 3 years ago

alexnix commented 3 years ago

I am trying to integrate zod with Mongoose but I have an issue.

One of the fields is ObjectID... how should I define this in the schema? I tried z.string() but I does not work (even if I add the ObjectID as a string on my object before saving it, I guess Mongoose internally converts it back to ObjectID before calling the pre-validate hook so in the end I am getting an ObjectID again).

Any workaround? Maybe something like https://github.com/jquense/yup/issues/467

davidmdm commented 3 years ago

What exactly is your use case? Is it mapping user input to a mongo objectID? The way myzod does this is via the map method.

const objectIdSchema = z.string().map(value => new ObjectId(value))

Maybe zod might consider doing something like this? Otherwise you can try myzod which is similar to zod.

alexnix commented 3 years ago

Yeah, kind of that use case... I found a workaround to use:

z.union([z.any(), userSchema])

with userSchema being the schema of the collection the field would point to. Mongoose will do verify that the input is indeed a correct ObjectID so I am covered from the validation point of view.

I guess the only thing I could ask for now would be for a way to make it infer the type of the field to string | z.infer<typeof userSchema> (string cause when the object is sent from the the API as JSON, ObjectID becomes a string, and because I might call populate on the field before returning the object).

colinhacks commented 3 years ago

Hi @alexnix. Sounds like ObjectId is a class? In that case you should use z.instanceof:

const User = z.object({
  id: z.instanceof(ObjectId)
})

This simply checks that the value passed into the id property is an intance of the ObjectID class. Docs are at https://github.com/colinhacks/zod#instanceof

learndeviomartin commented 1 year ago

Hi @alexnix. Sounds like ObjectId is a class? In that case you should use z.instanceof:

const User = z.object({
  id: z.instanceof(ObjectId)
})

This simply checks that the value passed into the id property is an intance of the ObjectID class. Docs are at https://github.com/colinhacks/zod#instanceof

This works as long as you do the check on the server. I had the same problem with my next.js app when I tried to access the schema in my components.

It was an easy fix tho.

const User = z.object({ id: z.instanceof(Object).transform((id) => id.toString()), })

adelowo commented 1 year ago

Hi @alexnix. Sounds like ObjectId is a class? In that case you should use z.instanceof:

const User = z.object({
  id: z.instanceof(ObjectId)
})

This simply checks that the value passed into the id property is an intance of the ObjectID class. Docs are at https://github.com/colinhacks/zod#instanceof

This doesn't work if It is coming from a HTTP request. are there ways to do that?

andrinheusser commented 1 year ago

This doesn't work if It is coming from a HTTP request. are there ways to do that?

You should be able to use preprocess

bbar commented 1 year ago

Will this work?

import type { ObjectId } from 'bson';

const _id = z.custom<ObjectId>();
catnug commented 1 year ago

Hi, when i try to guess your implementation, i think you probably want to achieve this 😎.

import z from 'zod'
import mongoose from 'mongoose'

const paramsSchema = z.object({
  id: z.string().refine((val) => {
    return mongoose.Types.ObjectId.isValid(val)
  }),
})

//-------
paramsSchema.parse(req.params.id)  //pass the validation
w3llcod3 commented 1 year ago

While the solution of catnug works, I have found that the solution of bbar works better. So here's it refactored:

    import mongoose from 'mongoose'

    const schema = z
      .object({
        _id: z.custom<mongoose.Types.ObjectId>(),
      })

The above solutions using instanceof does not really work if data is coming from an HTTP request.

orimdominic commented 1 year ago

In my case (data is coming from an HTTP request,) I used

const mongoIdSchema = z.string().regex(/^[0-9a-f]{24}$/);

from joi-objectid

iarlen-reis commented 1 year ago

I don't know if it was the best way, but for https requests, the simplest way I found was like this:

// create a schema with zod:

export const getSignSchema = z.object({
  id: z.string(),
})

// using schema:

const { id } = getSignSchema.parse(request.params)

const sign = await Sign.findById(new ObjectId(id))
reslear commented 9 months ago

or simple ready solution using https://github.com/validatorjs/validator.js

import isMongoId from 'validator/es/lib/isMongoId'

const schema = z.object({
  id: z.string().refine(isMongoId),
})

Update: but be careful sometimes it breaks the scheme better to use regexp solution or rewrite to transform

thanhtutzaw commented 6 months ago

Why I can't use ObjectId for _id in zod shared types. It is using Turbo Repo to use in backend and frontend.

bhrvarsani22 commented 1 week ago

Hi, when i try to guess your implementation, i think you probably want to achieve this 😎.

import z from 'zod'
import mongoose from 'mongoose'

const paramsSchema = z.object({
  id: z.string().refine((val) => {
    return mongoose.Types.ObjectId.isValid(val)
  }),
})

//-------
paramsSchema.parse(req.params.id)  //pass the validation

This one working perfact..

laneme commented 1 week ago

Update: but be careful sometimes it breaks the scheme ...

What you mean?