Giter Site home page Giter Site logo

Enum validation support? about zod HOT 8 CLOSED

colinhacks avatar colinhacks commented on August 25, 2024 3
Enum validation support?

from zod.

Comments (8)

agustif avatar agustif commented on August 25, 2024 129

z.nativeEnum(MyEnum)

Worked for my with a prisma db schema generated enum

from zod.

agustif avatar agustif commented on August 25, 2024 90

when you find your answer looking for the problem in google and can't remember how you knew the answer a year back.

Back to the future debugging LOL

from zod.

Strajk avatar Strajk commented on August 25, 2024 4

for future googlers – it's z.nativeEnum, not z.NativeEnum
https://zod.dev/?id=native-enums

from zod.

silasdavis avatar silasdavis commented on August 25, 2024 1

FWIW the lack of support for enums is what sent me into the arms of io-ts having tried zod first, although not a hugely satisfying fix there: gcanti/io-ts#216 (comment). Requires an additional helper needs to be invoked like:

fromEnum<EnumType>('EnumType', EnumType)

However, once it's done you are allowed to forget about it forever.

I will definitely consider using a syntax like the one you recommended

It sounds like you are seeing this as syntactic sugar. What I'd like is the inferred type to be exactly the enum type I pass in.

The problem with defining the enum as you describe is that the inferred string union is not assignable to the existing enum. So you still need to convert the union to the enum via reverse lookup EnumType[value]. This gets ugly if you have functions taking deeply nested types with enum values present.

Simple support for this seems on-brand for your develop-experience focus and a differentiator with io-ts :)

from zod.

colinhacks avatar colinhacks commented on August 25, 2024

=== UPDATE: the syntax described below is no longer accurate ===

I implemented a way to declare enums, but it's a bit different from the API you suggested. I will definitely consider using a syntax like the one you recommended at some point though.

Current usage:

const MyEnum = z.enum([
  z.literal('Bar'), 
  z.literal('Baz'), 
  z.literal('Foo')
]);

MyEnum.parse('Bar'); // => Bar
MyEnum.parse('LOL'); // => throws

Type inference works as expected:

type MyEnum = z.TypeOf<typeof MyEnum>
// => "Bar" | "Baz" | "Foo"

And for good measure you can get autocomplete on your enum values with the .Values variable like so:

const val = MyEnum.Values.Bar;

type MyEnumValues = MyEnum.Values;
// => { Bar: "Bar", Baz: "Baz", Foo: "Foo" }

I'm about to go out of town, but I will eventually implement a syntax more similar to yours, probably called .fromEnum():

enum Foo {
  BAR = 'BAR',
  BAZ = 'BAZ'
}
let e = z.fromEnum(Foo);

e.parse("LOL"); // throws

Closing this in the meantime.

from zod.

colinhacks avatar colinhacks commented on August 25, 2024

@silasdavis I appreciate the feedback. 👍

In my opinion Zod's current approach is much more developer-friendly, though I understand that it's annoying to rewrite a bunch of enums in a non-language construct.

For starters, I hate normal "numeric" enums with a passion. Defining a mapping from string keys to numeric values that hides the actual values and is determined exclusively by key ordering is a recipe for disaster. gcanti agrees with me on this: gcanti/io-ts#216 (comment)

Since I expect many people will use Zod to validate "real data" before writing it to their database, etc, I wanted to help users avoid that category of bugs.

The io-ts approach is doubly duplicative. You have redundancy in your "real" enum definition

enum MyEnum {
  Tuna = "tuna",
  Trout = "trout
}

and triple-redundancy in your io-ts schema definition

fromEnum<Thing>("Thing", Thing);

compared to

z.enum(['Tuna','Trout']);

from zod.

colinhacks avatar colinhacks commented on August 25, 2024

Moreover there appears to be a bug in the @geekflyer fromEnum implementation where it will incorrectly accept one of the enum's keys as a valid value. I haven't tested this so apologies if I'm wrong about this.

enum Thing {
  FOO = "foo",
  BAR = "bar"
}

const ThingCodec = fromEnum<Thing>("Thing", Thing);

ThingCodec.decode('foo')
// passes validation correctly

ThingCodec.decode('FOO')
// passes validation incorrectly

So this solution doesn't really give you the behavior you want anyway.

from zod.

silasdavis avatar silasdavis commented on August 25, 2024

tl;dr using enums is not my choice to make, the inferred type for a schema has values (where an enum value should be) that are not assignable to enum values, meaning they cannot be passed to functions that expect enums.

For starters, I hate normal "numeric" enums with a passion.

I am not a great fan of enums either, the subtleties of const and non-const sprint to mind. The point is you often do not control the definition of types on which you depend. Either they are owned by code you do not control or are generated by some tooling that is hard to change (e.g. OpenAPI spec). Despite their short-comings they are part of a language with at the very least highly suggestive semantics, and outside of typescript code where const literals stand in for enums very well I'm not sure I would agree with an outright ban of enums. Moreover I'd question if a schema library should be calling the shots here.

annoying to rewrite a bunch of enums in a non-language construct

The io-ts approach is doubly duplicative.

It's not that it is annoying (it is, but I can deal with that), indeed as you point out the option I am currently using is not pretty. What is a deal-breaker for me is that if I have a structure containing enums like:

type Foo = {
  Inner: {
    Bar: EnumType
  }
  Baz: EnumType
}

The schema inferred type will be something like:

type InferredFoo = {
  Inner: {
    Bar: 'ValueA' | 'ValueB'
  }
  Baz: ValueA' | 'ValueB'
}

I cannot pass InferredFoo to a function that expects Foo, but I mean to express Foo with my schema.

I could define a function realFoo(foo: InferredFoo): Foo but that is excessively burdensome. I need to be able to describe the shape of the data I actually have with schema within the confines of Typescripts Javascripty enums.

You have redundancy in your "real" enum definition

As above, let's assume that someone else has inflicted that on me.

So this solution doesn't really give you the behavior you want anyway.

I actually stopped paying attention when it compiled :) but I've just written a test case and it does not exhibit the behaviour you describe. It's acting over the values of the enum.

from zod.

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.