Tidak semua data cocok dipaksa ke kolom tetap: satu produk punya warna dan ukuran, produk lain punya bahan dan kapasitas. JSON mengatasi data semi-terstruktur seperti itu — kita menyimpan objek fleksibel tanpa mengubah skema tabel setiap ada atribut baru. MySQL mendukung JSON sebagai tipe kolom asli sejak versi 5.7, dan pada MySQL 8.4 dukungannya makin matang. Pada Chapter ini kita akan membuktikan bahwa kolom JSON menolak teks yang bukan JSON sah, memeriksa tipe nilai dengan JSON_VALID dan JSON_TYPE, membaca isi dengan JSON_EXTRACT, dan mengintip fakta bahwa isinya tidak disimpan sebagai teks biasa.

Kolom JSON dan Validasi Otomatis #

Nilai utama tipe JSON ada di gerbangnya: setiap nilai yang masuk diperiksa strukturnya dulu oleh server. Teks yang bukan JSON sah ditolak sebelum sempat tersimpan. Kita buat tabel produk dengan kolom atribut:

sql
DROP DATABASE IF EXISTS belajar_tipe;
CREATE DATABASE belajar_tipe;
USE belajar_tipe;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed
sql
CREATE TABLE produk (
  id SMALLINT UNSIGNED,
  nama VARCHAR(40),
  atribut JSON
);

INSERT INTO produk VALUES
  (1, 'Kaos Polos', '{"warna": "merah", "ukuran": "L", "stok": 12}'),
  (2, 'Topi Belajar', '{"warna": "hitam", "bahan": "katun"}');

SELECT id, nama, atribut FROM produk ORDER BY id;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

+------+--------------+-----------------------------------------------+
| id   | nama         | atribut                                       |
+------+--------------+-----------------------------------------------+
|    1 | Kaos Polos   | {"stok": 12, "warna": "merah", "ukuran": "L"} |
|    2 | Topi Belajar | {"bahan": "katun", "warna": "hitam"}          |
+------+--------------+-----------------------------------------------+
2 rows in set (0.00 sec)

Mari kita bedah hasilnya. Kedua baris tersimpan, tetapi cermati isi kolom atribut pada baris pertama: yang tampil adalah {"stok": 12, "warna": "merah", "ukuran": "L"} padahal kita menulis {"warna": "merah", "ukuran": "L", "stok": 12}. Urutan anggota objek berubah dan spasi ditambahkan setelah titik dua — MySQL tidak menyimpan teks JSON kita apa adanya, melainkan mengubahnya ke format biner internal lalu menyusun ulang tampilannya saat dibaca. Anggota tetap lengkap dan nilainya tetap sama; hanya “kemasan” teksnya yang dinormalisasi. Konsekuensi praktisnya: jangan mengandalkan urutan kunci maupun format spasi JSON sebagai data.

Sekarang kita buktikan gerbang validasinya dengan teks yang rusak — kunci warna tanpa tanda kutip:

sql
-- @error
INSERT INTO produk VALUES (3, 'Item Rusak', '{warna: merah}');

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 3140 (22032): Invalid JSON text: "Missing a name for object member." at position 1 in value for column 'produk.atribut'.
Bye

Server menolak statement dengan galat Invalid JSON text lengkap dengan posisi karakter yang bermasalah — pesan yang menunjuk tepat ke awal teks rusaknya. Bandingkan dengan kolom VARCHAR: teks {warna: merah} akan tersimpan tanpa keluhan di sana, lalu baru meledak di aplikasi saat diparse. Validasi di gerbang inilah alasan utama memilih tipe JSON daripada menyimpan teks JSON di VARCHAR atau TEXT.

Memeriksa Nilai dengan JSON_VALID dan JSON_TYPE #

Dua fungsi utilitas membantu kita memeriksa JSON tanpa menyimpannya. JSON_VALID(teks) mengembalikan 1 bila teks adalah JSON sah dan 0 bila tidak, sedangkan JSON_TYPE(nilai) melaporkan jenis nilai JSON yang diberikan:

sql
SELECT JSON_VALID('{"a": 1}') AS objek_sah,
       JSON_VALID('{a: 1}') AS teks_rusak;

SELECT JSON_TYPE('{"a": 1}') AS tipe_objek,
       JSON_TYPE('[1, 2, 3]') AS tipe_array,
       JSON_TYPE('"teks"') AS tipe_string,
       JSON_TYPE('123') AS tipe_integer,
       JSON_TYPE('true') AS tipe_boolean,
       JSON_TYPE('null') AS tipe_null;

Output yang Dihasilkan:

text
+-----------+------------+
| objek_sah | teks_rusak |
+-----------+------------+
|         1 |          0 |
+-----------+------------+
1 row in set (0.00 sec)

+------------+------------+-------------+--------------+--------------+-----------+
| tipe_objek | tipe_array | tipe_string | tipe_integer | tipe_boolean | tipe_null |
+------------+------------+-------------+--------------+--------------+-----------+
| OBJECT     | ARRAY      | STRING      | INTEGER      | BOOLEAN      | NULL      |
+------------+------------+-------------+--------------+--------------+-----------+
1 row in set (0.00 sec)

Fungsi JSON_VALID memastikan teks rusak {a: 1} dinilai 0 — pola yang sama yang ditolak server pada INSERT sebelumnya. Fungsi JSON_TYPE membedakan enam jenis nilai JSON: objek, array, string, integer, boolean, dan null; daftar ini menegaskan bahwa JSON punya sistem tipe kecil sendiri di dalam nilainya. JSON_VALID berguna saat kita menerima data dari luar (API, unggahan pengguna) dan ingin memeriksa kelayakannya sebelum INSERT, tanpa memancing galat.

Membaca Isi dengan JSON_EXTRACT #

Data di dalam kolom JSON kita baca memakai path expression — pola $.nama_kunci yang menunjuk anggota objek. Fungsi JSON_EXTRACT(kolom, path) mengambil nilainya, dan operator ->> merupakan singkatan yang sekaligus melepas tanda kutip pada hasil string:

sql
SELECT nama,
       atribut->>'$.warna' AS warna,
       JSON_EXTRACT(atribut, '$.stok') AS stok
FROM produk
ORDER BY id;

Output yang Dihasilkan:

text
+--------------+-------+------+
| nama         | warna | stok |
+--------------+-------+------+
| Kaos Polos   | merah | 12   |
| Topi Belajar | hitam | NULL |
+--------------+-------+------+
2 rows in set (0.00 sec)

Kolom warna memakai operator ->> sehingga hasilnya tampil sebagai teks bersih merah dan hitam tanpa tanda kutip JSON. Kolom stok memakai JSON_EXTRACT: baris pertama menampilkan 12 sesuai isinya, sedangkan baris kedua menghasilkan NULL — objek Topi memang tidak memiliki anggota stok. Perilaku “kunci yang tidak ada menghasilkan NULL” ini berbeda dari kolom biasa yang galat bila kolomnya sendiri tidak ada; JSON memilih jalur lembut karena struktur tiap baris memang boleh berbeda. Kombinasi path expression, JSON_EXTRACT, dan ->> inilah pintu masuk utama bekerja dengan data JSON — keranjang fungsi lengkapnya (penelusuran, modifikasi, agregasi) akan dibedah tuntas pada Section JSON dan Data Semi-Terstruktur.

Penyimpanan Biner di Balik Layar #

Kita sudah melihat bukti normalisasi pada output SELECT: urutan kunci berubah dan spasi distandarkan. Penyebabnya, kolom JSON menyimpan nilai dalam format biner internal — bukan salinan teks yang kita INSERT. Format biner ini memungkinkan server membaca anggota tertentu tanpa memparse seluruh dokumen, sehingga atribut->>'$.warna' tidak perlu memindai teks dari awal. Efek samping format biner yang perlu kita ingat ada tiga: format tampilan selalu dinormalisasi (spasi dan urutan kunci bisa berubah), duplikasi kunci di input hanya yang terakhir yang disimpan, dan ukuran penyimpanan bisa berbeda dari panjang teks aslinya.

Terakhir, kita bereskan database latihan:

sql
DROP DATABASE belajar_tipe;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

Referensi #

Dari angka, teks, waktu, logika, sampai semi-terstruktur — satu keluarga tipe masih tersisa untuk data mentah byte demi byte: gambar, hash, dan file. Chapter berikutnya membedah BINARY, VARBINARY, dan keluarga BLOB beserta trik HEX()/UNHEX() untuk melihat isinya.