Tipe JSON

Kolom JSON native, fungsi pengolahnya, dan pertimbangan kapan JSON cocok dipakai.

Daftar isi

Tipe JSON menyimpan dokumen JSON sebagai tipe kolom native MySQL, lengkap dengan validasi saat penulisan dan fungsi pengolahan berbasis path. Topik ini membahas cara membuat kolom JSON, perilaku normalisasi yang terjadi saat penyimpanan, fungsi JSON_EXTRACT beserta operator pintasnya, pembaruan parsial dengan JSON_SET, dan pertimbangan kapan kolom JSON cocok dipakai. Demonstrasi memakai data pembacaan sensor suhu yang menyertakan metadata berbeda-beda pada setiap baris.

Kolom JSON dan normalisasi#

Kolom yang dideklarasikan JSON hanya menerima teks yang valid sebagai JSON, dan setiap nilai yang diterima langsung disimpan dalam format biner internal, bukan sebagai string. Demonstrasi berikut membuat tabel pembacaan sensor dengan kolom metadata bertipe JSON, lalu mengisinya dengan dua cara sekaligus, yaitu fungsi JSON_OBJECT dan teks literal JSON.

sql skema-json.sql
-- tidak-dijalankan
DROP DATABASE IF EXISTS latihan_tipe;
CREATE DATABASE latihan_tipe;
USE latihan_tipe;

CREATE TABLE pembacaan_sensor (
  id INT PRIMARY KEY,
  sensor VARCHAR(40) NOT NULL,
  metadata JSON NOT NULL
);

INSERT INTO pembacaan_sensor VALUES
  (1, 'SUHU-A1', JSON_OBJECT('lokasi', 'Ruang Baca', 'unit', '°C', 'terkalibrasi', true)),
  (2, 'SUHU-B2', '{"lokasi": "Gudang", "unit": "°C", "terkalibrasi": false}');
bash
# membuat ulang basis data latihan dan tabelnya
mysql -u root < skema-json.sql
bash
# menampilkan isi kolom JSON yang sudah dinormalkan
mysql -t -u root latihan_tipe -e "SELECT id, sensor, metadata FROM pembacaan_sensor;"
text Keluaran
+----+---------+---------------------------------------------------------------+
| id | sensor  | metadata                                                      |
+----+---------+---------------------------------------------------------------+
|  1 | SUHU-A1 | {"unit": "°C", "lokasi": "Ruang Baca", "terkalibrasi": true}  |
|  2 | SUHU-B2 | {"unit": "°C", "lokasi": "Gudang", "terkalibrasi": false}     |
+----+---------+---------------------------------------------------------------+

Baris pertama yang dibangun dengan JSON_OBJECT dan baris kedua yang ditulis sebagai teks literal menghasilkan bentuk tersimpan yang sepadan. Perhatikan urutan kuncinya, yaitu MySQL menormalkan objek saat penyimpanan dan menyusun ulang kunci menurut panjangnya, sehingga urutan kunci pada keluaran tidak menjamin urutan teks asli. Format biner internal inilah yang membuat pembacaan path tidak perlu membedah ulang teks setiap kali query berjalan.

Validasi berjalan pada saat penulisan, dan teks yang bukan JSON ditolak dengan error 3140, sehingga kolom JSON tidak pernah berisi string yang tidak dapat diurai.

bash
# mencoba memasukkan teks yang bukan JSON
mysql -u root latihan_tipe -e "INSERT INTO pembacaan_sensor VALUES (3, 'SUHU-C3', '{bukan: json}');"
text Keluaran
ERROR 3140 (22032) at line 1: Invalid JSON text: "Missing a name for object member." at position 1 in value for column 'pembacaan_sensor.metadata'.

Membaca nilai dengan path#

Fungsi JSON_EXTRACT(nilai, path) membaca bagian dokumen menurut path, misalnya $.lokasi untuk kunci teratas atau $.alat[0] untuk elemen pertama suatu larik (array). MySQL menyediakan operator pintas -> yang setara dengan JSON_EXTRACT, beserta operator ->> yang sekaligus menghapus tanda kutip pada hasil string. Demonstrasi berikut membandingkan keduanya pada kolom yang sama.

bash
# membandingkan hasil operator -> dan ->>
mysql -t -u root latihan_tipe -e "SELECT sensor, metadata->'$.lokasi' AS lokasi_kutip, metadata->>'$.lokasi' AS lokasi_polos FROM pembacaan_sensor;"
text Keluaran
+---------+--------------+--------------+
| sensor  | lokasi_kutip | lokasi_polos |
+---------+--------------+--------------+
| SUHU-A1 | "Ruang Baca" | Ruang Baca   |
| SUHU-B2 | "Gudang"     | Gudang       |
+---------+--------------+--------------+

Kolom lokasi_kutip mengembalikan nilai JSON bertipe string sehingga tanda kutipnya ikut tampil, sedangkan kolom lokasi_polos mengembalikan teks polos yang siap ditampilkan atau dibandingkan dengan kolom biasa. Untuk pemakaian di WHERE, bentuk ->> biasanya lebih mudah diprediksi karena perbandingannya berjalan pada teks, misalnya metadata->>'$.lokasi' = 'Gudang'. Tipe hasil JSON_EXTRACT selalu JSON, sehingga perbandingannya mengikuti aturan JSON dan bukan aturan string.

Path pada MySQL mengikuti spesifikasi evaluasi JSON sendiri, dan bagian $ selalu menunjuk dokumen utuh sebelum aksesornya. Kunci yang memuat spasi atau karakter khusus dibaca dengan bentuk $."nama kunci", sehingga nama kunci tidak perlu dibatasi konvensi penamaan kolom. Apabila path tidak menemukan kuncinya, hasilnya bernilai NULL dan bukan error, sehingga kunci yang belum terisi pada dokumen lama tidak menggagalkan query.

Memperbarui sebagian dokumen#

Fungsi JSON_SET memperbarui path tertentu tanpa menyentuh bagian lain dari dokumen, dan langsung membuat kunci yang belum ada. Pasangannya melengkapi kebutuhan yang lebih spesifik, yaitu JSON_INSERT yang hanya menambah kunci baru, JSON_REPLACE yang hanya memperbarui kunci yang ada, dan JSON_REMOVE yang menghapus path. Demonstrasi berikut mengubah status kalibrasi sekaligus menambahkan kunci alat berisi larik dalam satu pernyataan UPDATE.

bash
# memperbarui dua path sekaligus dengan JSON_SET
mysql -t -u root latihan_tipe <<'SQL'
UPDATE pembacaan_sensor
SET metadata = JSON_SET(metadata, '$.terkalibrasi', false,
                        '$.alat', JSON_ARRAY('termometer', 'pelindung'))
WHERE id = 1;
SELECT metadata FROM pembacaan_sensor WHERE id = 1;
SELECT JSON_EXTRACT(metadata, '$.alat[0]') AS alat_pertama,
       JSON_TYPE(metadata->'$.alat') AS tipe_alat,
       metadata->>'$.terkalibrasi' AS status
FROM pembacaan_sensor WHERE id = 1;
SQL
text Keluaran
+-----------------------------------------------------------------------------------------------------+
| metadata                                                                                            |
+-----------------------------------------------------------------------------------------------------+
| {"alat": ["termometer", "pelindung"], "unit": "°C", "lokasi": "Ruang Baca", "terkalibrasi": false}  |
+-----------------------------------------------------------------------------------------------------+
+--------------+-----------+--------+
| alat_pertama | tipe_alat | status |
+--------------+-----------+--------+
| "termometer" | ARRAY     | false  |
+--------------+-----------+--------+

Pembaruan hanya menyentuh dua path, sedangkan kunci lokasi dan unit tetap utuh pada dokumennya. Fungsi JSON_TYPE memastikan bahwa $.alat memang bertipe ARRAY, dan path $.alat[0] mengembalikan elemen pertamanya sebagai JSON. Pembaruan parsial seperti ini menghindari pola baca-ubah-tulis di aplikasi yang rentan menimpa perubahan dari sesi lain.

Fungsi pembaruan JSON bekerja pada tingkat path sehingga aplikasi tidak perlu menulis ulang seluruh dokumen untuk mengubah satu kunci. Meskipun demikian, dua JSON_SET yang berjalan berdekatan tetap berlomba pada tingkat baris seperti UPDATE biasa. Kunci yang benar-benar sering diperbarui dan selalu dibandingkan layak dipindahkan ke kolom biasa dengan tipe eksplisit.

Kapan JSON cocok dipakai#

Kolom JSON paling cocok untuk data yang strukturnya memang berbeda-beda antarbaris, seperti metadata perangkat yang modelnya beragam, parameter uji yang opsional, atau atribut yang hanya dimiliki sebagian baris. Pada tabel pembacaan sensor di atas, kunci alat hanya relevan untuk sebagian sensor, dan memaksanya menjadi kolom-kolom biasa berarti menambah banyak kolom yang sebagian besar kosong. JSON juga cocok sebagai struktur penampung yang selalu dibaca utuh, misalnya manifest arsip cadangan yang ditulis sekali dan dibaca sekali.

Kolom JSON bukan pengganti kolom biasa untuk data inti. Nilai di dalam JSON tidak dapat diberi foreign key, tidak memiliki tipe yang konsisten antarbaris, dan penyaringannya lebih mahal daripada penyaringan kolom biasa. Data yang selalu difilter, dijumlahkan, atau di-join sebaiknya tetap ditempatkan pada kolom dengan tipe eksplisit. Apabila sebagian kunci JSON kerap menjadi saringan, MySQL menyediakan kolom hasil turunan (generated column) yang mengambil nilai path tersebut dan dapat diindeks, sehingga biaya pemfilteran kembali mendekati kolom biasa.

Kelonggaran struktur JSON juga melemahkan jaminan data karena tanpa deklarasi tipe, kesalahan penulisan kunci tidak pernah terdeteksi oleh server. Kunci lokasi yang tertulis salah sekali saja langsung menghasilkan dokumen yang tampak sah tetapi tidak pernah cocok dengan pencarian. Konvensi penamaan kunci yang ditulis sebagai dokumen pendamping skema mengurangi risiko ini, dan pemeriksaan JSON_TYPE pada data penting menangkap pergeseran tipe lebih awal.

Kesalahan umum#

  1. Menyimpan JSON di kolom TEXT. Server tidak memvalidasi isinya, sehingga kesalahan sintaks baru ketahuan ketika aplikasi membaca data, dan fungsi JSON pun tidak berjalan tanpa konversi terlebih dahulu. Pola CHECK (JSON_VALID(kolom)) yang lazim dipakai sebelum MySQL 8.0 menjadi tidak perlu karena tipe JSON sudah memvalidasi setiap sisipan. Perbaikannya adalah memakai tipe JSON agar validasi, normalisasi, dan fungsi pengolahnya berjalan otomatis.
  2. Memindahkan seluruh kolom tabel ke dalam satu kolom JSON. Query kehilangan tipe yang eksplisit, indeks, dan foreign key sekaligus, sehingga keandalan data turun tanpa ada manfaat yang sepadan. Perbaikannya adalah mempertahankan kolom inti sebagai kolom relasional dan menyisakan JSON hanya untuk atribut opsional.
  3. Mengharapkan urutan kunci JSON tetap seperti teks yang dimasukkan. Normalisasi menyusun ulang kunci saat penyimpanan, sehingga perbandingan teks antara dua dokumen yang isinya sama dapat gagal. Perbaikannya adalah membandingkan nilai JSON dengan operator = antara dua nilai JSON, yang berjalan pada perbandingan kanonis dan bukan perbandingan teks.

Ringkasan#

  • Kolom JSON memvalidasi teks saat penulisan, menyimpan dokumen dalam format biner internal, dan menolak teks tidak sah dengan error 3140.
  • JSON_EXTRACT beserta operator -> mengembalikan nilai JSON, sedangkan ->> mengembalikan teks tanpa tanda kutip.
  • JSON_SET memperbarui path tertentu sekaligus membuat kunci yang belum ada tanpa menyentuh bagian lain dokumen.
  • JSON cocok untuk atribut opsional yang strukturnya beragam antarbaris, sedangkan data inti yang difilter dan di-join tetap ditempatkan pada kolom bertipe eksplisit.

Referensi#