zod icon indicating copy to clipboard operation
zod copied to clipboard

feat: Add `ZodReadonly` type

Open santosmarco-caribou opened this issue 2 years ago • 10 comments

Overview

  • Added support for the ZodReadonly type.
  • Added support for deep readonly as well.
  • Added option to toggle on/off the object freezing (Object.freeze) behavior

This type will transform the output and input of the following types to Readonly:

  • Array -> Adds the readonly modifier in front of it
  • Tuple -> Adds the readonly modifier in front of it
  • Map -> to the special ReadonlyMap type
  • Set -> to the special ReadonlySet type
  • Record -> Wraps it in Readonly<Record< ... >>
  • Object -> it adds the readonly modifier on each member
  • Native Enum -> doesn't make any difference because the parsing is not supposed to happen on the enum itself but in the enum values.

All other types, including Date and Function, which don't have support for Readonly were left unchanged. Interestingly enough, TS can make Promises readonly, but I've decided to skip them.


Deep readonly

The ZodReadonly type has a method called deep() that turns itself into a deep-readonly version. The behavior is basically the same, except that it recursively goes into Maps, Sets, objects, records, tuples and arrays and transforms everything to readonly. It's important to note that it preserves tuples too.


Additionally,

This is not just a type improvement, but the ZodReadonly can actually freeze the parsed data with Object.freeze to reflect real "readonliness" (if in the deep version, it will recursively freeze the data). This is optional and you can opt-in via an option available in the createParams.

The .readonly() property was added to the root Z class for convenience, but—again—only the aforementioned types get modified.

santosmarco-caribou avatar Sep 27 '22 15:09 santosmarco-caribou

Deploy Preview for guileless-rolypoly-866f8a ready!

Built without sensitive environment variables

Name Link
Latest commit 2733ebebeb1b539c390608a0debeb587c4b3b7da
Latest deploy log https://app.netlify.com/sites/guileless-rolypoly-866f8a/deploys/63940ab47dac250008f0e024
Deploy Preview https://deploy-preview-1432--guileless-rolypoly-866f8a.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

netlify[bot] avatar Sep 27 '22 15:09 netlify[bot]

I dig it. Is this still a draft or is it ready for review?

colinhacks avatar Oct 04 '22 16:10 colinhacks

I dig it. Is this still a draft or is it ready for review?

@colinhacks Yes, I believe it's now ready for review.

santosmarco-caribou avatar Oct 09 '22 08:10 santosmarco-caribou

Once we get this approved, I'm happy to add documentation around it in the README file prior to merging.

santosmarco-caribou avatar Oct 10 '22 21:10 santosmarco-caribou

I'm not a maintainer, but I've been testing this out with our custom Express validator, and it's working fantastically. I particularly appreciate the runtime freeze support. Thanks for doing this!

atomicmattie avatar Oct 11 '22 17:10 atomicmattie

I'm not a maintainer, but I've been testing this out with our custom Express validator, and it's working fantastically. I particularly appreciate the runtime freeze support. Thanks for doing this!

Thanks! We will get it merged hopefully soon.

Next step is a deep version.

santosmarco-caribou avatar Oct 12 '22 19:10 santosmarco-caribou

Would be great to see this merged. Hate having to turn all my ReadonlyArrays into Array.

izakfilmalter avatar Dec 02 '22 13:12 izakfilmalter

@colinhacks let me know if you need anything else from me.

Shall we move this forward?

santosmarco-caribou avatar Dec 06 '22 21:12 santosmarco-caribou

@colinhacks / @mattieb / @izakfilmalter

I've introduced deep readonly today. Feel free to review the PR one more time :)

Looking forward to helping deploy it.

santosmarco-caribou avatar Dec 07 '22 19:12 santosmarco-caribou

@santosmarco PR is working well for me. Can't wait for it to get merged.

izakfilmalter avatar Dec 15 '22 09:12 izakfilmalter

@colinhacks can it maybe receive some love for the next version?

santosmarco-caribou avatar Dec 23 '22 03:12 santosmarco-caribou

Was looking exactly for this! Looking forward to see it added!

einarpersson avatar Jan 17 '23 13:01 einarpersson

Looking forward to this as well.

Until then, there seem to be a few workarounds. Here's one using a type assertion:

import { z } from 'zod'

const thingsSchema = z.object({
  things: z.array(z.string()) as z.ZodType<readonly string[]>
})

type Things = z.infer<typeof thingsSchema>

TS Playground here

The type assertion can be codified into a polymorphic readonlyArray convenience function that also has no overhead beyond just z.array itself:

const readonlyArray: <T extends z.ZodTypeAny>(
  schema: T
) => z.ZodType<readonly z.infer<T>[], z.ZodTypeDef, readonly unknown[]> =
  z.array;

const thingsSchema = z.object({
  things: readonlyArray(z.string())
})

type Things = z.infer<typeof thingsSchema>

TS Playground here

My colleague @blackdynamo also proposed a runtime workaround using transform and as const:

import { z } from 'zod'

const thingsSchema = z.object({
  things: z.array(z.string()).transform(things => [...things] as const)
})

type Things = z.infer<typeof thingsSchema>

I hope those are helpful to folks.

briancavalier avatar Mar 09 '23 15:03 briancavalier

Any progress on this. Hate having to omit all the time to get readonly types.

izakfilmalter avatar Apr 08 '23 12:04 izakfilmalter

I have strong empathy for the thankless work of maintaining a popular project, so this isn’t judgment—just practicality.

To solve my immediate problem, I have settled for using a deep readonly utility type (e.g. from type-fest) and applying it at the point I call Zod’s parse method. It’s a better solution, in my view, than trying to maintain a local fork or similar.

atomicmattie avatar Apr 08 '23 13:04 atomicmattie

what exactly is blocking this?

francescosalvi avatar Apr 24 '23 11:04 francescosalvi

What is the progress of this? Is there something necessary to be done? :)

BleedingDev avatar Jun 12 '23 03:06 BleedingDev

oh so excited for this, i'm porting a big API from an in-house type assertion to zod, and having readonly is the sole blocking task.

clord avatar Jun 12 '23 22:06 clord

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Sep 10 '23 23:09 stale[bot]

So it seems this has been implemented? Did this PR's code get used, or a different implementation? https://github.com/colinhacks/zod#readonly

@santosmarco-caribou

atomicmattie avatar Sep 11 '23 13:09 atomicmattie

A modified version of this PR was merged in 3.22. Thanks @santosmarco-caribou!

colinhacks avatar Oct 04 '23 20:10 colinhacks