JSON ke TypeScript

Menghasilkan antarmuka TypeScript dari JSON: elemen array digabung, kunci opsional, union tipe campuran, null terpisah — semua di browser Anda.

Masukan
TypeScript
export interface Root {
  id: number
  name: string
  active: boolean
  address: Address
  roles: string[]
  posts: Post[]
  tags: unknown[]
}

export interface Address {
  city: string
  zip: null
}

export interface Post {
  id: number
  title: string
  views: number | string
  pinned?: boolean
}

Antarmuka: 3

Tipe disimpulkan dari data, bukan ditebak

Sebuah dokumen JSON tidak membawa tipenya sendiri — ia membawa nilai, dan tipe harus dibaca kembali darinya. Itu mudah untuk satu objek dan mengejutkan halusnya untuk sebuah koleksi: bentuk yang Anda inginkan bukan bentuk satu rekaman tertentu, melainkan bentuk yang harus dipenuhi setiap rekaman. Alat ini menyimpulkan antarmuka TypeScript dari sampel JSON dengan melihat semua yang dikandung sampel, sehingga tipe yang dihasilkannya menggambarkan keseluruhan data Anda dan bukan baris pertama yang kebetulan berada di atas.

Konversinya hanya satu arah. Mengubah tipe kembali menjadi JSON berarti mengarang nilai, dan di sini tujuannya sebaliknya: dokumen yang ditempel adalah sumber kebenaran, dan setiap deklarasi diturunkan darinya. Tempel sebuah respons API, berkas konfigurasi, atau satu baris log, dan bacalah antarmuka yang jika tidak akan Anda tulis dengan tangan.

Bagaimana array digabungkan

Keputusan yang menarik semuanya terjadi pada array. Pengonversi naif melihat elemen pertama lalu berhenti, sehingga setiap bidang yang kebetulan dilihatnya tampak wajib dan setiap bidang yang tidak dilihatnya terlewat. Alat ini sebaliknya menggabungkan semua elemen menjadi satu tipe, dan tiga hal muncul darinya:

  • Sebuah kunci yang ada di sebagian elemen tetapi tidak ada di elemen lain menjadi opsional, ditulis dengan tanda tanya. Jika separuh rekaman Anda punya "middleName" dan separuh tidak, bidangnya adalah "middleName?", persis yang harus ditangani seorang konsumen.
  • Sebuah kunci yang nilainya berbeda jenis antar elemen menjadi sebuah union. Bidang yang berupa angka di satu rekaman dan string di rekaman lain diketik sebagai "number | string" — bukan demi kerapian, melainkan karena itulah yang benar-benar dikandung data dan yang harus diterima kode Anda.
  • Sebuah objek bersarang di dalam elemen digabungkan dengan cara yang sama, secara rekursif, dan diekstrak ke antarmukanya sendiri. Sepuluh elemen array yang masing-masing membawa "address" menghasilkan satu antarmuka Address yang menggambarkan kesepuluhnya.

Ketika dokumen itu sendiri berupa array di tingkat teratas, akar menjadi alias ke antarmuka item — misalnya "type Root = RootItem[]" — dengan tipe elemen yang digabung ditulis di bawahnya.

Null, opsional, dan mengapa keduanya berbeda

Menggoda untuk memperlakukan null sama seperti kunci yang tidak ada, dan itu keliru. Dalam TypeScript "name?: string" berarti propertinya boleh tidak ada; "name: string | null" berarti ia selalu ada tetapi boleh berisi null. Itu kontrak yang berbeda, dan seorang konsumen memeriksanya secara berbeda — "in" versus perbandingan nilai. Alat ini menjaga keduanya terpisah: null eksplisit dalam data menjadi anggota union "| null", ditaruh terakhir agar "string | null" terbaca seperti yang Anda harapkan, sementara kunci yang sekadar tidak ada di sebagian rekaman menjadi opsional. Sebuah bidang yang keduanya — null di satu rekaman, tidak ada di rekaman lain — keluar sebagai keduanya, "field?: T | null", karena kedua fakta itu benar tentang data Anda.

Objek bersarang menjadi antarmuka bernama

Alih-alih menyematkan sebuah bentuk bersarang di dalam induknya, setiap objek diekstrak ke antarmukanya sendiri yang namanya diturunkan dari kunci tempatnya berada. Objek "user" menjadi antarmuka User; sebuah "address" di dalamnya menjadi antarmuka Address yang dirujuk User. Tipe yang disematkan dalam-dalam sulit dibaca dan mustahil dipakai ulang, dan antarmuka bernama adalah apa yang akan Anda tulis sendiri. Elemen array dijadikan tunggal jika bisa — "users" memberi User, "categories" memberi Category — dan kunci yang tidak berbentuk jamak menerima akhiran Item agar elemen punya nama tersendiri.

Jika dua objek berbeda akan mengambil nama yang sama — dua "data" tak berkaitan, misalnya — yang kedua menerima akhiran alih-alih digabung, sehingga bentuk yang berbeda tetap berbeda. Objek akar dikeluarkan lebih dulu dan Anda bisa mengganti namanya; pilihan antara keluaran "interface" dan "type" adalah sakelar, karena sebagian basis kode lebih menyukai alias tipe di mana-mana.

Mundur ke unknown

Sebagian nilai tidak membawa informasi tipe sama sekali. Array kosong bisa berisi apa saja; objek kosong tidak punya kunci untuk digambarkan. Alih-alih memakai "any" — yang mematikan pemeriksaan tipe untuk semua yang menyusul — alat ini mundur ke "unknown": array kosong menjadi "unknown[]", objek kosong menjadi "Record<string, unknown>". Perbedaannya penting. "any" meloloskan bug diam-diam; "unknown" memaksa konsumen mempersempit nilai sebelum memakainya, sehingga tipe yang disimpulkan tetap jujur tentang apa yang dikatakan sampel dan apa yang tidak.

Ini adalah tipe yang akan Anda perketat dengan tangan begitu Anda tahu apa yang mesti ditampung koleksi kosong itu — tetapi sampai data mengatakannya, "unknown" adalah jawaban yang jujur, dan jawaban yang menjaga sisa tipe Anda tetap aman.

Berjalan di atas apa, dan di mana

Semuanya terjadi di browser Anda. JSON diurai dan tipe disimpulkan di perangkat Anda sendiri; tidak ada yang Anda tempel diunggah, disimpan, atau dicatat. Ini membuat alat aman dipakai pada respons API sungguhan atau berkas konfigurasi yang berisi rahasia: sampel tidak pernah meninggalkan halaman. Keluarannya adalah TypeScript biasa yang bisa Anda tempel langsung ke berkas "d.ts" atau modul, sesuaikan beberapa bidang "unknown" yang tak dapat digambarkan data, lalu pakai.

Pertanyaan yang sering diajukan

Apakah JSON saya dikirim ke server?
Tidak. Dokumen diurai dan tipe disimpulkan sepenuhnya di browser Anda, dan tidak ada yang Anda tempel diunggah atau dicatat. Aman dipakai pada respons API sungguhan atau berkas konfigurasi.
Mengapa sebuah bidang opsional padahal ada di sampel saya?
Karena ia tidak ada di setidaknya satu elemen dari array yang digabung alat. Inferensi membaca setiap elemen, bukan hanya yang pertama, jadi kunci yang dihilangkan sebagian rekaman menjadi opsional — itulah tipe yang benar-benar didukung data Anda, meski rekaman yang Anda lihat kebetulan memuatnya.
Mengapa sebuah bidang menjadi union seperti string | number?
Karena nilainya berjenis berbeda di elemen array yang berbeda — string di satu rekaman dan angka di rekaman lain. Tipe yang digabung harus menerima keduanya, jadi ditulis sebagai union. Jika itu mengejutkan Anda, biasanya artinya data kurang seragam dari yang diduga, yang layak diketahui.
Mengapa alat memakai unknown alih-alih any?
Untuk nilai yang tak dapat digambarkannya — array kosong, objek kosong — "unknown" menjaga hasil tetap aman tipe, memaksa konsumen mempersempit nilai sebelum memakainya, sedangkan "any" akan mematikan pemeriksaan tipe. Anda bisa memperketat bidang-bidang itu dengan tangan begitu tahu apa yang ditampung koleksi kosong.
Apa beda keluaran interface dan type?
Tidak ada dalam tipe yang digambarkan — keduanya menghasilkan bentuk yang sama. "interface" adalah idiom umum untuk tipe objek dan bisa diperluas serta digabung; alias "type" adalah yang lebih disukai sebagian basis kode untuk dipakai di mana-mana. Sakelar itu ada agar keluaran cocok dengan gaya proyek Anda.
Bisakah ia mengubah TypeScript kembali menjadi JSON?
Tidak, dan itu disengaja. Konversinya satu arah: JSON masuk, tipe keluar. Menuju arah sebaliknya berarti mengarang nilai yang tak pernah ada di data Anda, dan inti keseluruhannya adalah bahwa setiap deklarasi diturunkan dari apa yang benar-benar Anda tempel.
Bagaimana objek bersarang diberi nama?
Dari kunci tempatnya berada: objek "user" menjadi User, sebuah "address" di dalamnya menjadi Address. Elemen array dijadikan tunggal jika bisa — "categories" memberi Category — dan kunci yang tidak berbentuk jamak menerima akhiran Item. Dua bentuk berbeda yang akan bertabrakan pada sebuah nama menerima akhiran alih-alih digabung, sehingga tetap berbeda.