Membaca JSON
Enam tipe JSON dan nilai-nilai yang menjebak tes.
Sebagian besar API yang akan Anda uji mengirim dan menerima JSON. Ini format teks untuk data terstruktur, dan cukup kecil untuk dipelajari dalam satu sore. Sintaksnya jarang jadi masalah. Masalahnya, JSON membiarkan beberapa hal tidak terdefinisi, dan program yang berbeda mengisi celah itu dengan cara berbeda. Di celah itulah banyak bug berada, jadi pelajaran ini lebih banyak membahasnya daripada sintaksnya.
Enam jenis nilai
Nilai JSON selalu salah satu dari ini:
| Tipe | Contoh | Catatan |
|---|---|---|
| Objek | { "key": "value" } | Field bernama di dalam kurung kurawal. Key selalu string dalam tanda kutip ganda. |
| Array | [1, 2, 3] | Daftar berurutan dalam kurung siku. Item bisa bertipe apa saja, bercampur. |
| String | "hello" | Teks dalam tanda kutip ganda. Tanda kutip tunggal tidak diizinkan. |
| Angka | 42, -7, 3.14159, 6.022e23 | Satu tipe untuk bilangan bulat dan desimal. Tanpa nol di depan, tanpa NaN, tanpa Infinity. |
| Boolean | true, false | Huruf kecil, tanpa tanda kutip. |
| Null | null | Huruf kecil, tanpa tanda kutip. Berarti “tidak ada nilai”. |
Itulah seluruh sistem tipenya. Tidak ada tipe tanggal, tidak ada tipe bilangan bulat yang terpisah dari desimal, dan tidak ada cara menambahkan komentar. API ini punya endpoint yang mengembalikan masing-masing satu:
/api/edge-cases/types{
"string": "hello",
"emptyString": "",
"integer": 42,
"negativeInteger": -7,
"float": 3.14159,
"exponent": 6.022e23,
"booleanTrue": true,
"booleanFalse": false,
"nullValue": null,
"array": [1, 2, 3],
"emptyArray": [],
"object": { "key": "value" },
"emptyObject": {},
"arrayOfObjects": [ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" } ],
"nestedArrays": [[1, 2], [3, [4, [5]]]],
"stringThatLooksLikeNumber": "123",
"stringThatLooksLikeBoolean": "true",
"stringThatLooksLikeNull": "null"
}Lihat tiga field terakhir. "123", "true", dan "null" semuanya string, karena berada dalam tanda kutip. Orang yang membaca response melihat angka, boolean, dan null. Program melihat tiga string. Saat memeriksa response, periksa tipenya juga, bukan hanya nilainya. API yang suatu hari mengembalikan "price": "249.99" dan keesokan harinya "price": 249.99 sudah mengubah kontraknya, meskipun angkanya terlihat sama.
Menunjuk sebuah nilai
Saat menulis tes atau laporan bug, Anda butuh cara untuk menyebut nilai mana yang Anda maksud. Ini sebagian produk dari pelajaran pertama:
{
"id": 1,
"title": "Wireless Noise-Cancelling Headphones",
"price": 249.99,
"category": "electronics",
"rating": { "rate": 4.3, "count": 127 },
"inStock": true,
"createdAt": "2025-06-12T10:30:00Z"
}Notasi informal yang umum memakai titik untuk masuk ke objek, dan kurung siku berisi angka untuk memilih item dari array. Hitungan dimulai dari nol.
| Path | Nilai |
|---|---|
title | "Wireless Noise-Cancelling Headphones" |
rating.rate | 4.3 |
rating.count | 127 |
Saat seluruh response berupa array, seperti pada /api/Products?limit=2, ID produk pertama adalah [0].id dan rating produk kedua adalah [1].rating.rate. Setiap alat menulisnya sedikit berbeda. JSONPath, yang akan Anda lihat di Postman, REST Assured, dan banyak library tes, diawali tanda dolar untuk root: $.rating.rate, $[0].id. Ia juga punya wildcard, jadi $[*].priceberarti "harga setiap item". JMESPath, yang dipakai alat baris perintah AWS, menulis hal yang sama sebagai [*].price. Anda belum perlu mempelajari keduanya secara mendalam. Mengetahui bahwa $[0].idberarti "id item pertama" sudah cukup untuk membaca sebagian besar kode tes.
Null, hilang, dan kosong
Ketiganya terlihat mirip tetapi berarti berbeda. Misalkan seorang pengguna tidak punya nama tengah. API bisa menyatakannya setidaknya dengan lima cara:
"middleName": null, field-nya ada dan tidak bernilai- tidak ada key
middleNamesama sekali "middleName": "", string kosong"middleName": "null", kata null empat huruf, biasanya bug saat sesuatu memanggil toString pada null"middleName": "None"atau"undefined", bug yang sama dari Python atau JavaScript
Endpoint ini mengembalikan semuanya berdampingan:
/api/edge-cases/nulls{
"nullValue": null,
"nestedNull": { "a": null, "b": { "c": null } },
"arrayOfNulls": [null, null, null],
"arrayWithNull": [1, null, 3],
"nullAsString": "null",
"undefinedAsString": "undefined",
"noneAsString": "None",
"emptyString": "",
"emptyArray": [],
"emptyObject": {},
"zero": 0,
"false": false
}Mana yang benar tergantung dokumentasi API, dan dokumentasi sering tidak menyebutkannya. Yang bisa Anda uji adalah konsistensi. Jika satu produk punya "discount": null dan produk lain tidak punya key discount, klien yang membaca product.discount.amount akan crash pada salah satunya dan tidak pada yang lain. Waspadai juga 0 dan falseyang dipakai untuk berarti "tidak diketahui". Stok 0 dan stok yang tidak diketahui siapa pun adalah fakta yang berbeda.
Angka
JSON tidak membatasi ukuran atau presisi angka. Program yang membaca JSON-lah yang membatasinya. JavaScript menyimpan setiap angka sebagai float 64-bit, yang hanya bisa menyimpan bilangan bulat secara persis sampai 9.007.199.254.740.991. Di atas itu, ia membulatkan diam-diam.
/api/edge-cases/numbers{
"int64Max": 9223372036854775807,
"int64MaxAsString": "9223372036854775807",
"maxSafeInteger": 9007199254740991,
"beyondSafeInteger": 9007199254740993,
"beyondSafeIntegerAsString": "9007199254740993",
"pointOnePlusPointTwo": 0.30000000000000004,
"money": 19.99,
"moneyAsString": "19.99",
"trailingZeros": 1.50,
"negativeZero": -0,
"numberAsString": "42",
"nanAsString": "NaN"
}Parse response itu di JavaScript dan di Python, lalu bandingkan:
JSON.parse(body).beyondSafeInteger // 9007199254740992
JSON.parse(body).int64Max // 9223372036854776000json.loads(body)["beyondSafeInteger"] # 9007199254740993
json.loads(body)["int64Max"] # 9223372036854775807JavaScript mengubah 9007199254740993 menjadi 9007199254740992 tanpa error apa pun. Python mempertahankannya persis. Jika sebuah API memakai ID bilangan bulat 64-bit (ID Twitter dulu begitu, dan banyak database juga), klien JavaScript bisa berakhir meminta data yang salah. Itulah sebabnya API yang cermat mengirim ID besar sebagai string, seperti int64MaxAsString di atas. Saat menguji API dengan ID numerik besar, periksa apa yang benar-benar diterima klien di browser.
Desimal punya masalah serupa. 0.1 + 0.2 hasilnya 0.30000000000000004 di hampir semua bahasa, karena float biner tidak bisa merepresentasikan 0.1 secara persis. Untuk uang, ini penting. Ada API yang mengirim harga sebagai angka (19.99), ada yang sebagai string ("19.99"), dan ada yang sebagai sen bulat (1999). Semuanya bisa berfungsi. Tes sebaiknya memeriksa bahwa total cocok sampai ke sen, bukan bahwa hasilnya "cukup dekat".
Dua hal kecil dari response yang sama. 1.50 dan 1.5 adalah angka yang sama setelah di-parse, jadi tes yang membandingkan teks mentah akan gagal di tempat tes yang membandingkan nilai lulus. Dan -0 adalah JSON yang valid, tetapi JSON.stringify(-0) di JavaScript menulis 0, jadi nilai itu tidak bertahan dalam perjalanan bolak-balik.
Tanggal adalah string
JSON tidak punya tipe tanggal, jadi tanggal dikirim sebagai string atau angka, dan setiap API memilih formatnya sendiri. Ini satu momen yang sama, ditulis dengan beberapa cara:
/api/edge-cases/dates{
"iso8601Utc": "2025-07-15T09:30:00Z",
"iso8601PositiveOffset": "2025-07-15T15:00:00+05:30",
"iso8601NoZone": "2025-07-15T09:30:00",
"dateOnly": "2025-07-15",
"unixSeconds": 1752571800,
"unixMilliseconds": 1752571800000,
"usFormat": "07/15/2025",
"euFormat": "15/07/2025",
"invalidLeapDay": "2025-02-29T00:00:00Z",
"nullDate": null,
"emptyDate": ""
}Format yang paling sering Anda lihat adalah ISO 8601 dengan zona waktu, seperti 2025-07-15T09:30:00Z. Z berarti UTC. Hal yang perlu diperiksa saat API mengembalikan tanggal:
- Zona waktunya ada.
2025-07-15T09:30:00tanpa zona berarti pukul 9.30 di suatu tempat, dan setiap klien akan menebak tempat yang berbeda. - Unix timestamp menyebutkan apakah dalam detik atau milidetik.
1752571800dan1752571800000adalah momen yang sama, dan tertukar keduanya membawa Anda ke tahun 1970 atau tahun 57500. 07/15/2025dan15/07/2025tidak ambigu hanya karena 15 tidak mungkin bulan.03/04/2025bisa Maret atau April, tergantung siapa yang membacanya.- Tanggal mustahil seperti
2025-02-29diterima sebagian parser dan ditolak yang lain. Coba kirim ke API yang menerima tanggal sebagai input.
Urutan key dan key duplikat
Key pada objek tidak punya urutan. {"a": 1, "b": 2} dan {"b": 2, "a": 1} adalah objek yang sama. Sebagian besar server kebetulan selalu mengirim key dalam urutan yang sama, jadi ada godaan untuk membandingkan seluruh response sebagai string. Tes itu akan rusak pada hari pembaruan library mengubah urutan key, padahal tidak ada yang salah. Bandingkan nilai yang sudah di-parse. Urutan array, sebaliknya, penting: [1, 2] dan [2, 1] adalah array yang berbeda.
Key duplikat adalah kasus yang lebih aneh. Standar JSON mengatakan key "sebaiknya" unik, bukan harus, jadi ini secara teknis diizinkan:
/api/edge-cases/duplicate-keys{
"id": 1,
"name": "first value",
"name": "second value",
"nested": { "flag": true, "flag": false },
"id": 2
}JSON.parse di JavaScript dan json.loads di Python sama-sama menyimpan nilai terakhir, menghasilkan {"id": 2, "name": "second value", "nested": {"flag": false}}. Parser lain menyimpan nilai pertama atau menolak dokumennya. Jika gateway memeriksa key role yang pertama dan backend membaca yang kedua, perbedaan itu menjadi celah keamanan. Anda tidak akan sering bertemu key duplikat di response, tetapi layak dikirim dalam request saat Anda menguji penanganan input.
Saat JSON-nya rusak
Kadang server mengirim sesuatu yang sama sekali bukan JSON valid: body yang terpotong, halaman error HTML, stack trace. Ini response yang mengaku JSON padahal bukan:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id": 1, "name": "Alice", "tags": ["a", "b",], "active": truAda koma berlebih setelah "b", dan body-nya berhenti di tengah true. Statusnya 200 dan header Content-Type berbunyi application/json, jadi klien akan mencoba mem-parse-nya lalu gagal. Node.js melaporkan Unexpected token ']'. Python melaporkan Illegal trailing comma before end of array. Di API sungguhan, ini biasanya berarti server crash di tengah penulisan response, atau proxy memotongnya. Pelajarannya untuk tes: parse body di setiap pemeriksaan. 200 dengan body yang tidak bisa di-parse adalah kegagalan, dan tes yang hanya melihat kode status akan melewatkannya.
Membaca response yang besar
Response sungguhan sering berisi ratusan baris. Sebelum memeriksa detail, pahami bentuknya: apakah level teratas berupa objek atau array, berapa jumlah item-nya, field apa saja yang dimiliki satu item. Lalu baca satu item secara lengkap. Sebagian besar bug JSON terlihat dari bentuknya saja, seperti daftar yang kembali sebagai satu objek saat hasilnya hanya satu, atau objek yang dibungkus key data tambahan di satu endpoint tetapi tidak di endpoint lain.
Latihan
Kirim GET /api/Countries/code/INmasuk untuk melihat dan jawab pertanyaan ini dari response-nya, dengan menulis setiap path memakai notasi titik dari pelajaran ini:
- Apa tipe
population, dan apa tipecallingCode? Mengapa keduanya bisa berbeda? - Apa path ke simbol mata uang?
- Apa path ke bahasa kedua?
- Field
flagberupa emoji. Lihat response mentahnya dengan curl dan bandingkan dengan yang ditampilkan browser Anda. Apa yang berbeda, dan apakah itu penting bagi klien?
Lalu buka /api/edge-cases/stringsmasuk untuk melihat dan temukan setidaknya dua nilai yang terlihat baik-baik saja di browser tetapi bisa merusak program yang menyimpan atau menampilkannya.