JSON ke Zod

Menghasilkan skema Zod dari JSON, masing-masing dengan tipe z.infer-nya: elemen array digabung, kunci opsional dan nullable ditandai, tanpa menebak.

Masukan
Skema Zod
import * as z from "zod"

export const Customer = z.object({
  name: z.string(),
  email: z.string(),
  phone: z.null(),
})
export type Customer = z.infer<typeof Customer>

export const LineItem = z.object({
  sku: z.string(),
  quantity: z.number(),
  price: z.number(),
  note: z.string().nullable(),
  backordered: z.boolean().optional(),
})
export type LineItem = z.infer<typeof LineItem>

export const Root = z.object({
  id: z.string(),
  customer: Customer,
  lineItems: z.array(LineItem),
  tags: z.array(z.unknown()),
})
export type Root = z.infer<typeof Root>

Sintaks Zod 4 yang juga valid di Zod 3. z.object membuang setiap kunci yang tidak dicantumkannya, jadi kolom yang tidak ada di sampel Anda hilang tanpa peringatan dari data yang diurainya.

  • Di setiap posisi di bawah, sampel Anda hanya pernah berisi null, jadi skema tidak menerima apa pun selain itu di sana.

    Posisi: 1

    • Customer.phone
  • Di setiap posisi di bawah, setiap angka adalah bilangan bulat. z.number() juga menerima 7.5, dan .int() akan mewajibkan bilangan bulat, jadi tambahkan sendiri hanya di tempat yang Anda tahu nilainya harus bulat.

    Posisi: 1

    • LineItem.quantity
  • Di setiap posisi di bawah, tidak ada yang bisa disimpulkan, jadi skema tidak memeriksa apa yang ada di sana: z.unknown() menerima nilai apa pun, dan z.record(z.string(), z.unknown()) objek apa pun.

    Posisi: 1

    • Root.tags[]

Pemeriksaan yang berjalan setiap kali data tiba

Tipe TypeScript diperiksa saat kode Anda dikompilasi dan sudah tidak ada lagi ketika kode itu berjalan. Skema Zod adalah bagian yang tetap tinggal: ia berjalan di dalam program Anda dan memeriksa setiap payload saat payload itu masuk — respons API, badan webhook, pesan dari antrean — sebelum kode Anda mengandalkannya. Tempel sampel JSON itu dan halaman ini menuliskan skemanya untuk Anda, siap ditempel ke dalam modul: setiap objek yang punya kunci menjadi "z.object" bernama, elemen array digabung menjadi satu, dan setiap skema diikuti tipe TypeScript yang dihasilkannya.

Ini adalah jawaban yang diberikan halaman JSON ke TypeScript untuk JSON yang sama, dieja sebagai validator alih-alih sebagai tipe. Kedua halaman membaca satu inferensi, jadi kunci mana yang opsional, nilai mana yang menjadi union, di mana null dipisahkan dari kunci yang tidak ada, dan apa nama setiap objek bersarang diputuskan sekali dan hanya dituliskan dua kali. Aturan penggabungan itu adalah pokok bahasan panduan di halaman JSON ke TypeScript dan tidak dijelaskan lagi di sini. Panduan ini membahas apa yang dilakukan skema terhadap data sungguhan begitu ia berjalan, dan apa yang disisakannya untuk Anda putuskan.

Apa yang lolos, dan apa yang ditolak

Skema dari halaman ini menerima sampel yang menjadi dasar penulisannya, baik di Zod 3 maupun di Zod 4, kecuali dalam satu kasus yang punya pemberitahuan sendiri, yaitu angka tak hingga di Zod 4. Di luar sampel itu, ia menerima apa pun yang tetap berada di dalam apa yang dikatakan skema, dan untuk data yang benar-benar akan Anda terima, hasilnya seperti ini:

  • Sebelum batas kedalaman tercapai, kunci yang ditulis tanpa ".optional()" bersifat wajib. Payload yang tidak memuatnya ditolak, dan begitu pula payload yang menaruh di sana jenis nilai yang tidak disebut skema — string di tempat skema berbunyi "z.number()", objek di tempat skema berbunyi "z.string()".
  • Kunci yang ditulis dengan ".optional()" boleh tidak ada, dan kunci yang ditulis dengan ".nullable()" boleh berisi null. Tidak satu pun dari keduanya meloloskan jenis nilai lain apa pun, jadi kunci opsional yang ada tetap harus berisi apa yang disebut skema.
  • "z.union" menerima anggotanya yang mana pun dan tidak menerima yang lain. "z.array" menerima berapa pun jumlah elemen, termasuk tanpa elemen sama sekali, asalkan masing-masing cocok dengan skema yang ditulis untuk elemennya.
  • Di tempat sampel tidak menunjukkan apa pun — elemen array kosong, objek tanpa kunci, nilai yang bersarang melewati batas kedalaman — skema tidak memeriksa apa yang ada di sana: ia menerima nilai apa pun, atau objek apa pun di tempat objek dalam sampel tidak punya kunci, dan pemberitahuan menyebutkan di mana.

Kunci yang tidak dicantumkan skema diloloskan lalu dibuang dari hasilnya; itulah pembuangan kunci, dan ia punya bagiannya sendiri. Satu kunci berada di luar semua ini. Kunci yang dieja "__proto__" ditulis sebagai kunci terkomputasi, di dalam kurung siku, karena jika ditulis apa adanya ia akan menetapkan prototipe objek tempatnya berada alih-alih menamai kunci — namun tidak satu pun dari kedua versi Zod mengembalikan kunci itu dalam hasil yang diberikan penguraian, dan Zod 4 sama sekali tidak memeriksa nilainya.

Mengapa skema tidak pernah memperketat dirinya sendiri

Setiap sampel punya kesamaan yang tidak bisa dibuktikannya: setiap id berupa bilangan bulat, setiap alamat surel berbentuk alamat surel, peran yang hanya pernah berisi admin. Itu keteraturan, dan keteraturan bukanlah pemeriksaan. Generator tergoda untuk tetap menuliskannya — ".int()" pada id, "z.email()" pada alamat, literal pada peran — dan generator yang ini tidak pernah melakukannya, karena sampel menunjukkan apa yang bisa dimuat data Anda dan tidak pernah apa yang harus dimuatnya. Tipe yang lebih ketat daripada data Anda dibayar dengan galat kompilasi di mesin Anda sendiri. Skema yang lebih ketat daripada data Anda dibayar dengan permintaan yang ditolak di produksi: peran pertama yang bukan admin, id pertama yang bernilai 7.5, alamat pertama yang tidak diantisipasi polanya.

Pemeriksaan itu juga akan bergeser di bawah kaki Anda. "z.uuid()" milik Zod 4 menolak string berbentuk UUID yang diterima ".uuid()" milik Zod 3, karena ia memeriksa bit varian, dan ".int()" milik Zod 4 menolak bilangan bulat di luar rentang aman yang diloloskan padanannya di Zod 3 — jadi pemeriksaan yang ditebak dari sampel hari ini akan menjadi pemeriksaan yang berbeda di Zod mana pun yang Anda pasang. Dengan demikian, tidak ada yang sekadar diisyaratkan sampel yang dituliskan ke dalam skema. Di tempat hal itu penting saat data tiba, halaman ini memberi tahu Anda lewat pemberitahuan sebagai gantinya, dan suntingannya diserahkan kepada Anda.

Apa yang tidak akan dikatakan skema dengan sendirinya

Di bawah skema, halaman ini mencantumkan apa yang diamatinya dan tidak dituliskannya ke dalam skema: satu pemberitahuan untuk setiap jenis di bawah yang berlaku, diberikan sekali beserta setiap posisi tempat pemberitahuan itu berlaku. Posisi ditulis sebagaimana keluaran menamai sesuatu — "Customer.phone" untuk kunci, "Root.tags[]" untuk elemen array, kuncinya di dalam tanda kutip dan kurung siku jika ia bukan pengenal dan untuk "__proto__" — sehingga posisi itu dapat ditemukan sekilas di skema. Contoh yang dimuat bersama halaman menunjukkan setiap jenis kecuali yang tentang Infinity.

  • Hanya null. Di posisi itu sampel tidak pernah berisi apa pun selain null, jadi skema berbunyi "z.null()" dan menolak nilai sungguhan yang pertama. Putuskan apa yang dimuat kolom itu ketika terisi dan tuliskan itu sendiri — "z.string().nullable()", misalnya — atau tempel sampel yang di dalamnya kolom itu punya nilai.
  • Infinity. Angka yang melampaui apa yang dapat ditampung angka JavaScript, seperti 1e999, menjadi Infinity atau -Infinity saat JSON diurai. "z.number()" milik Zod 4 menolak angka tak hingga dan milik Zod 3 menerimanya, jadi di Zod 4 inilah satu-satunya kasus ketika skema menolak justru sampel yang menjadi dasar pembuatannya. Pertanyaan yang dimunculkannya menyangkut data, bukan skema: digitnya sudah hilang sebelum validator mana pun melihatnya, jadi apakah nilai itu memang seharusnya berupa angka adalah pertanyaan bagi apa pun yang menuliskannya.
  • Bilangan bulat. Setiap angka di posisi itu adalah bilangan bulat, dan "z.number()" juga menerima 7.5. Di tempat nilai harus bulat — id, hitungan, kuantitas — tambahkan ".int()" sendiri; di tempat nilai itu harga yang kebetulan bulat, biarkan saja. Pemberitahuan ini tidak diberikan untuk posisi mana pun yang memuat bilangan bulat di luar rentang aman, yaitu dari "Number.MIN_SAFE_INTEGER" sampai "Number.MAX_SAFE_INTEGER", karena ".int()" milik Zod 4 menolaknya, dan saran yang akan membuat skema menolak sampelnya sendiri adalah satu-satunya jenis saran yang tidak akan diberikan halaman ini.
  • Tidak ada yang bisa disimpulkan. Elemen array kosong, objek tanpa kunci, dan nilai yang bersarang lebih dari 100 tingkat tidak memberi inferensi pegangan apa pun, jadi skema tidak memeriksa apa yang ada di sana: "z.unknown()" menerima nilai apa pun tanpa kecuali, dan "z.record(z.string(), z.unknown())" objek apa pun. Tempel sampel yang di dalamnya array itu punya elemen dan objek itu punya kunci, atau tulis bagian skema itu dengan tangan.

Pemberitahuan tidak pernah mengubah satu bita pun dari skema. Ia adalah kalimat di sampingnya, dalam bahasa halaman, dan suntingan yang ditunjuknya terserah Anda untuk dikerjakan atau dilewati. Tidak ada pemberitahuan tentang format string, literal, atau enum: masing-masing akan menjadi tebakan atas aturan yang tidak dapat ditunjukkan sampel.

Kunci yang tidak dicantumkan skema dibuang

"z.object" meloloskan objek yang membawa kunci yang tidak dicantumkannya dan tidak menyertakan kunci itu dalam apa yang dikembalikannya. Itu bawaan Zod di kedua versi, dan hal paling senyap yang dilakukan skema: kolom yang kebetulan tidak ada di sampel Anda lenyap dari data yang diterima kode Anda, tanpa galat yang mengatakannya. Itulah sebabnya kalimat di bawah setiap skema di halaman ini menyebutkannya.

Halaman ini tidak memilih ketat atau longgar untuk Anda, karena masing-masing mengklaim lebih dari yang dapat ditunjukkan sampel. Objek ketat menolak kunci apa pun yang tidak dicantumkannya — tidak ada kunci selain ini — dan tidak ada sampel yang dapat membuktikan hal itu tentang payload berikutnya. Objek longgar mempertahankan kunci tambahan, dan tipe yang disimpulkannya mendapat index signature untuk kunci-kunci itu, sehingga ia tidak akan lagi menjadi jawaban halaman TypeScript. Pembuangan kunci adalah satu-satunya perilaku yang menerima apa yang diterima tipe TypeScript dan tetap menyimpulkan tipe itu — nilai dengan properti tambahan juga memenuhi antarmuka. Untuk memilih yang lain, ubah skema dengan tangan:

  • Untuk menolak kunci yang tidak dicantumkan skema, tulis "z.strictObject" di tempat keluaran menulis "z.object" di Zod 4, atau rangkaikan ".strict()" pada "z.object" di Zod 3.
  • Untuk mempertahankannya, tulis "z.looseObject" di Zod 4, atau rangkaikan ".passthrough()" di Zod 3. Zod 4 masih menjalankan kedua metode Zod 3 itu, dan menyebut keduanya sebagai warisan.

Setiap objek dalam keluaran adalah skema tersendiri, jadi pilihan itu dibuat satu objek demi satu objek: membuat akar menjadi ketat tidak mengubah apa pun pada objek yang bersarang di dalamnya, yang sering kali merupakan hal yang Anda inginkan ketika hanya pembungkus luarnya yang berhak Anda tuntut.

Satu nama untuk setiap skema dan tipenya

Setiap skema diikuti tipenya — "export type Customer = z.infer<typeof Customer>" tepat setelah "export const Customer" — yang merupakan cara zod.dev menulis contohnya sendiri: satu nama untuk nilai yang memeriksa data dan untuk tipe yang dihasilkannya, karena TypeScript menyimpan nilai dan tipe dalam ruang-nama yang terpisah. Tipe itu adalah tipe yang dicetak halaman JSON ke TypeScript untuk JSON yang sama, nama demi nama dan kunci demi kunci, dengan kunci opsional yang sama, union yang sama, dan null di tempat yang sama — dengan pengecualian yang disebut di bawah.

Repositori ini memeriksa janji itu alih-alih memercayainya: korpus sampel melewati kedua halaman lalu melewati kompilator TypeScript bersama setiap versi Zod, dan kompilator itu ditanya, untuk setiap nama, apakah kedua tipe identik dan apakah masing-masing dapat ditetapkan ke yang lain. Di tempat kompilator menjawab lain, tempat itu berada jauh di dalam dokumen. Setelah batas kedalaman terlampaui, di tempat kunci berisi "z.unknown()", Zod 3 menyimpulkan kunci itu sebagai opsional sementara halaman TypeScript menjadikannya wajib. Dan di Zod 4 kompilator menyerah pada tipe array yang bersarang puluhan tingkat, dengan melaporkan galat TS2589 pada baris tipe itu sendiri; skema di atas baris itu tetap berjalan dan tetap menerima sampelnya, dan hanya tipe yang disimpulkan yang hilang.

Kedua perbandingan membaca kunci opsional seperti yang dilakukan TypeScript secara bawaan. Dengan "exactOptionalPropertyTypes", yang nonaktif kecuali proyek menyalakannya, "z.infer" dari kunci opsional juga mengizinkan undefined yang eksplisit, di versi Zod mana pun, sedangkan tipe halaman TypeScript tidak.

Ditulis untuk Zod 4, dan tetap valid di Zod 3

Keluarannya berpegang pada apa yang dimiliki kedua versi mayor — "z.object", "z.array", "z.union" atas dua anggota atau lebih, "z.string()", "z.number()", "z.boolean()", "z.null()", "z.unknown()", "z.record(z.string(), z.unknown())", ".optional()", ".nullable()", dan "z.infer" — di bawah baris impor yang menjadi pembuka contoh-contoh zod.dev sendiri. Tidak ada di dalamnya yang diperkenalkan di Zod 4 dan tidak ada di dalamnya yang usang di sana, jadi proyek yang belum beralih dari Zod 3 dapat menempelnya apa adanya.

Namun, teks yang sama tidak berperilaku identik di keduanya, dan setiap perbedaan disebutkan dalam panduan ini di tempat perbedaan itu penting. "z.number()" milik Zod 4 menolak angka tak hingga sedangkan milik Zod 3 menerimanya, yang merupakan pemberitahuan Infinity. Kunci yang nilainya "z.unknown()" bersifat opsional dalam tipe yang disimpulkan Zod 3 dan wajib dalam tipe milik Zod 4 — dan wajib juga saat data diurai, mulai Zod 4.4 — yang hanya pernah dituliskan keluaran ini setelah batas kedalaman terlampaui. Zod 3 memeriksa kunci yang dieja "__proto__" dan Zod 4 tidak. Dan kompilator menyerah pada tipe milik Zod 4 untuk array yang bersarang puluhan tingkat, sedangkan tipe milik Zod 3 diselesaikannya.

Keluaran ini ditulis untuk Zod biasa, dengan metode: "z.string().nullable().optional()". Zod Mini mengeja skema yang sama dengan fungsi sebagai gantinya, "z.optional(z.nullable(z.string()))", jadi Zod Mini tidak dapat menjalankan keluaran ini apa adanya.

Mengapa akar berada paling akhir

Halaman TypeScript mencetak akar lebih dulu dan objek yang dipakainya sesudahnya, karena tipe boleh dipakai sebelum baris yang mendeklarasikannya. Skema tidak boleh: ia adalah nilai, dan "const" yang dibaca di atas deklarasinya sendiri melempar ReferenceError selagi modulnya masih dimuat. Jadi setiap skema di sini berada setelah setiap skema yang dipakainya, dan akar berada paling akhir — Customer dan LineItem lebih dulu dalam contoh, lalu Root yang memuat keduanya — itulah sebabnya akar yang membuka halaman TypeScript menutup halaman ini.

Urutan seperti itu selalu ada. Inferensi ini berupa pohon, setiap objek yang diekstraknya dipakai dari tepat satu tempat, jadi tidak ada skema yang perlu merujuk ke dirinya sendiri atau ke skema yang dicetak sesudahnya, dan keluaran tidak pernah membutuhkan "z.lazy".

Pertanyaan yang sering diajukan

Mengapa id berupa "z.number()" dan bukan "z.number().int()"?
Karena sampel bisa menunjukkan bahwa setiap id sejauh ini bulat, tetapi tidak bahwa yang berikutnya akan bulat. Halaman ini mengatakannya sebagai gantinya: pemberitahuan bilangan bulat mencantumkan setiap posisi tempat setiap angka adalah bilangan bulat, dan di tempat Anda tahu nilai harus tetap bulat, menambahkan ".int()" adalah suntingan satu kata. Pemberitahuan itu tidak diberikan di tempat angka merupakan bilangan bulat di luar rentang aman, karena ".int()" milik Zod 4 akan menolak sampel itu sendiri.
Mengapa alamat surel keluar sebagai "z.string()" biasa?
Karena string yang tampak seperti alamat surel di sampel Anda tidak mengatakan apa pun tentang string berikutnya, dan pemeriksaan format yang benar harus mengikuti pola milik Zod sendiri, yang berubah antarversi — "z.uuid()" milik Zod 4 sudah menolak string yang diloloskan ".uuid()" milik Zod 3. Tanggal, URL, dan UUID tetap berupa string dengan alasan yang sama, dan kolom yang hanya pernah berisi beberapa nilai tidak pernah menjadi enum atau literal. Jika Anda tahu aturannya, tuliskan ke dalam skema; skema itu kode biasa milik Anda.
Mengapa sampel saya sendiri gagal di Zod 4?
Sampel itu memuat angka yang melampaui apa yang dapat ditampung angka JavaScript — misalnya 1e999 — yang menjadi Infinity atau -Infinity saat JSON diurai, dan "z.number()" milik Zod 4 menolak angka tak hingga sedangkan milik Zod 3 menerimanya. Pemberitahuan Infinity menyebut setiap posisi yang terlibat. Di luar itu, skema selalu menerima sampel asalnya, karena setiap nilai dalam sampel masuk ke dalamnya, dan repositori ini memeriksa hal itu di kedua versi atas korpus sampel.
Mengapa kolom yang saya kirim hilang dari hasil penguraian?
Karena skema tidak mencantumkannya. "z.object" menerima objek dengan kunci tambahan dan mengembalikannya tanpa kunci-kunci itu, di versi mana pun, dan kunci yang tidak ada di sampel Anda adalah kunci yang tidak pernah dipelajari skema. Tambahkan kunci itu ke skema, atau jadikan objek yang satu itu longgar — "z.looseObject" di Zod 4, ".passthrough()" di Zod 3 — jika kunci yang tidak dikenal seharusnya lewat tanpa tersentuh.
Saya memindahkan satu skema ke bawah skema lain dan mendapat ReferenceError. Mengapa?
Karena skema adalah nilai, dan JavaScript tidak mengizinkan nilai dibaca di atas baris yang mendefinisikannya. Halaman ini mencetak setiap skema setelah setiap skema yang dipakainya, dengan akar paling akhir, tepat karena alasan itu; tempatkan skema di atas semua yang merujuk kepadanya dan galatnya hilang.
Mengapa skemanya bernama Customer dan bukan CustomerSchema?
Itu konvensi zod.dev sendiri: skema dan tipe yang disimpulkannya berbagi satu nama, yang diizinkan TypeScript karena nilai dan tipe tinggal di ruang-nama yang terpisah. Namanya sendiri adalah nama yang diberikan halaman JSON ke TypeScript kepada objek yang sama, jadi katanya sama di kedua halaman, baik untuk skema maupun untuk tipenya.
Untuk versi Zod yang mana keluaran ini ditulis?
Zod 4, tanpa memakai apa pun yang tidak dimiliki Zod 3, jadi keluaran ini berjalan tanpa perubahan di versi mana pun. Keduanya berbeda di beberapa tempat — angka tak hingga, kunci yang melewati batas kedalaman, kunci yang dieja "__proto__", dan tipe array yang bersarang sangat dalam — dan masing-masing dijelaskan di atas. Keluaran ini Zod biasa dengan metode berantai; Zod Mini mengeja skema yang sama dengan fungsi dan tidak dapat menjalankannya apa adanya.
Bolehkah saya menempel data sungguhan, lengkap dengan kredensialnya?
Boleh. Skema dibuat di peramban Anda: apa yang Anda tempel dibaca di mesin Anda sendiri dan tidak dikirim ke server, disimpan, atau dicatat. Skema itu juga tidak memuat satu pun nilai Anda, hanya kunci Anda dan jenis nilai di bawah masing-masing kunci, jadi token di dalam sampel keluar sebagai "z.string()" dan tidak lebih dari itu.

Alat terkait

  • JSON ke TypeScript

    Skema dari halaman ini berjalan sebagai bagian dari kode Anda dan memeriksa data setiap kali data itu tiba. Halaman itu memberi jawaban yang sama untuk JSON yang sama dalam bentuk tipe TypeScript biasa, dengan nama yang sama, yang diperiksa saat kode Anda dikompilasi dan tidak menambahkan apa pun saat program berjalan.

  • JSON ke Go

    Halaman ini menyerahkan kepada Anda untuk mewajibkan bilangan bulat di tempat setiap angka dalam sampel Anda bulat. Halaman itu menyusun bentuk yang sama dengan nama tipe yang sama, tetapi Go tidak punya tipe yang sekadar angka JSON, jadi di tempat setiap angka ditulis sebagai bilangan bulat, halaman itu memberi bidang itu tipe bilangan bulat, dan nilai yang ditulis dengan titik desimal tidak akan dapat didekode ke dalamnya.

  • Penguji JSONPath

    Uji kueri JSONPath (RFC 9535) terhadap JSON.

  • Pembuat tabel Markdown

    Buat dan ratakan tabel Markdown dari CSV, TSV, atau JSON.