Dokumen produk telah tersimpan rapi di kolom JSON, namun mengambil seluruh dokumen untuk sekadar membaca satu warna tentu boros — apalagi aplikasi biasanya hanya butuh satu potongan kecil dari dokumen yang besar. MySQL menyediakan dua operator path berpanah, -> dan ->>, yang menjangkau nilai di kedalaman dokumen langsung dari daftar SELECT. Keduanya tampak mirip padahal hasilnya berbeda secara fundamental: satu mengembalikan nilai JSON, satu mengembalikan teks polos. Pada chapter ini kita membedah kedua operator tersebut berdampingan, memperkenalkan fungsi JSON_EXTRACT sebagai bentuk lengkapnya, lalu menjangkau nilai bersarang, elemen array, dan menyimak apa yang terjadi saat path yang diminta tidak ada.
Menyiapkan Tabel Latihan #
Kita pakai kembali skema produk(id, nama, atribut) pada database latihan belajar_json. Empat produk sengaja dibuat tidak seragam: dua produk pertama lengkap dengan warna, dimensi, dan tag; Teh Melati tidak punya warna; Gula Aren bahkan tidak punya tag — ketimpangan inilah bahan uji untuk melihat perilaku path yang hilang:
DROP DATABASE IF EXISTS belajar_json;
CREATE DATABASE belajar_json;
USE belajar_json;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE produk (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
atribut JSON NOT NULL
);
INSERT INTO produk (nama, atribut) VALUES
('Kopi Gayo 250g', '{"warna": "coklat", "berat": 250, "tag": ["arabika", "toraja"], "dimensi": {"tinggi": 12, "diameter": 8}}'),
('Mug Keramik', '{"warna": "putih", "berat": 350, "tag": ["keramik", "microwave"], "dimensi": {"tinggi": 10, "diameter": 8}, "spesifikasi": {"bahan": "keramik", "kapasitas": "350 ml"}}'),
('Teh Melati 100g', '{"berat": 100, "tag": ["teh", "melati"]}'),
('Gula Aren 500g', '{"berat": 500}');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0Operator -> dan -» Berdampingan #
Kedua operator dibaca sama: “ambil nilai pada path ini dari kolom JSON tersebut”. Path selalu diawali tanda dolar ($) yang melambangkan akar dokumen, lalu diikuti nama kunci yang dituju. Mari kita bandingkan keduanya pada kolom warna:
SELECT
nama,
atribut->'$.warna' AS panah_json,
atribut->>'$.warna' AS panah_teks
FROM produk
ORDER BY id;Output yang Dihasilkan:
+-----------------+------------+------------+
| nama | panah_json | panah_teks |
+-----------------+------------+------------+
| Kopi Gayo 250g | "coklat" | coklat |
| Mug Keramik | "putih" | putih |
| Teh Melati 100g | NULL | NULL |
| Gula Aren 500g | NULL | NULL |
+-----------------+------------+------------+
4 rows in set (0.00 sec)Perbedaannya langsung terlihat pada dua kolom pertama: -> menghasilkan "coklat" lengkap dengan tanda kutip ganda, sedangkan ->> menghasilkan coklat tanpa kutip. Operator -> mengembalikan nilai sebagai JSON — string tetap dibungkus kutip karena begitulah string ditulis dalam JSON. Operator ->> (dibaca “double arrow”) mengembalikan nilai string yang sudah di-unquote, alias teks polos siap pakai. Baris Teh Melati dan Gula Aren menampilkan NULL pada kedua kolom karena kedua dokumen memang tidak memiliki kunci warna.
Kapan memakai yang mana? Aturan praktisnya sederhana: -> saat kita butuh hasil JSON untuk diproses fungsi JSON lain, ->> saat hasilnya ingin diperlakukan sebagai teks. Mari kita uji kedua operator di posisi WHERE mencari produk berwarna putih:
SELECT nama FROM produk WHERE atribut->'$.warna' = 'putih';Output yang Dihasilkan:
+-------------+
| nama |
+-------------+
| Mug Keramik |
+-------------+
1 row in set (0.00 sec)SELECT nama FROM produk WHERE atribut->>'$.warna' = 'putih';Output yang Dihasilkan:
+-------------+
| nama |
+-------------+
| Mug Keramik |
+-------------+
1 row in set (0.00 sec)Kedua query ternyata sama-sama menemukan Mug Keramik. MySQL menerapkan koersi saat membandingkan nilai JSON dengan nilai SQL biasa: operand 'putih' dikonversi lebih dulu menjadi string JSON "putih", sehingga perbandingan tetap adil meski satu sisi berkutip dan sisi lain tidak. Tanda kutip baru benar-benar terasa ketika nilai dipakai sebagai teks, bukan sekadar dibandingkan. Perhatikan penggabungan string berikut:
SELECT
CONCAT('Warna: ', atribut->'$.warna') AS dengan_kutip,
CONCAT('Warna: ', atribut->>'$.warna') AS tanpa_kutip
FROM produk
WHERE id = 2;Output yang Dihasilkan:
+----------------+--------------+
| dengan_kutip | tanpa_kutip |
+----------------+--------------+
| Warna: "putih" | Warna: putih |
+----------------+--------------+
1 row in set (0.00 sec)Kolom pertama menghasilkan Warna: "putih" — tanda kutip JSON ikut tergabung karena operator -> menyerahkan string JSON mentah. Kolom kedua menghasilkan Warna: putih yang bersih karena ->> sudah melepas kutipnya. Konteks string seperti CONCAT, pengukuran CHAR_LENGTH, atau pencarian LIKE adalah tempat pemilihan operator menentukan kebenaran hasil.
JSON_EXTRACT sebagai Bentuk Lengkap #
Operator panah sebenarnya singkatan dari fungsi bernama JSON_EXTRACT. Bentuk fungsinya menerima kolom JSON dan path, lalu mengembalikan nilai JSON — perilaku yang persis sama dengan ->. Mari kita buktikan keduanya identik pada baris yang sama:
SELECT
JSON_EXTRACT(atribut, '$.warna') AS bentuk_fungsi,
atribut->'$.warna' AS bentuk_operator
FROM produk
WHERE id = 1;Output yang Dihasilkan:
+---------------+-----------------+
| bentuk_fungsi | bentuk_operator |
+---------------+-----------------+
| "coklat" | "coklat" |
+---------------+-----------------+
1 row in set (0.00 sec)Kedua kolom menampilkan "coklat" — hasil yang sama persis. Sementara itu, ->> setara dengan JSON_EXTRACT yang lalu dibungkus JSON_UNQUOTE:
SELECT
atribut->>'$.warna' AS operator_panah,
JSON_UNQUOTE(JSON_EXTRACT(atribut, '$.warna')) AS fungsi_lengkap
FROM produk
WHERE id = 1;Output yang Dihasilkan:
+----------------+----------------+
| operator_panah | fungsi_lengkap |
+----------------+----------------+
| coklat | coklat |
+----------------+----------------+
1 row in set (0.00 sec)Kembali keduanya identik: teks coklat tanpa kutip. Pengetahuan padanan ini penting karena dokumentasi dan kode lama sering memakai bentuk fungsi, sementara kode modern condong ke operator yang lebih ringkas. Kita bebas memilih selama mengingat padanan tepatnya: -> untuk JSON_EXTRACT, ->> untuk JSON_UNQUOTE(JSON_EXTRACT(...)).
Menjangkau Nilai Bersarang #
Dokumen nyata jarang datar. Kolom dimensi pada produk kita adalah object di dalam object, dan path menulusurinya cukup dengan menambah titik diikuti nama kunci berikutnya:
SELECT
nama,
atribut->'$.dimensi.tinggi' AS tinggi,
atribut->'$.dimensi.diameter' AS diameter
FROM produk
ORDER BY id;Output yang Dihasilkan:
+-----------------+--------+----------+
| nama | tinggi | diameter |
+-----------------+--------+----------+
| Kopi Gayo 250g | 12 | 8 |
| Mug Keramik | 10 | 8 |
| Teh Melati 100g | NULL | NULL |
| Gula Aren 500g | NULL | NULL |
+-----------------+--------+----------+
4 rows in set (0.00 sec)Path $.dimensi.tinggi dibaca dari kiri: mulai dari akar, masuk ke anggota dimensi, lalu anggota tinggi di dalamnya. Nilai yang ditemukan berupa angka JSON sehingga kolom hasil tampil polos tanpa kutip — angka tidak pernah dibungkus kutip dalam JSON. Mug Keramik punya kedalaman lebih lagi lewat spesifikasi:
SELECT
nama,
atribut->>'$.spesifikasi.bahan' AS bahan,
atribut->>'$.spesifikasi.kapasitas' AS kapasitas
FROM produk
ORDER BY id;Output yang Dihasilkan:
+-----------------+---------+-----------+
| nama | bahan | kapasitas |
+-----------------+---------+-----------+
| Kopi Gayo 250g | NULL | NULL |
| Mug Keramik | keramik | 350 ml |
| Teh Melati 100g | NULL | NULL |
| Gula Aren 500g | NULL | NULL |
+-----------------+---------+-----------+
4 rows in set (0.00 sec)Hanya Mug Keramik yang memiliki spesifikasi, jadi tiga produk lain menghasilkan NULL — termasuk Kopi Gayo yang memang tidak punya kunci tersebut. Perhatikan juga bahwa di sini kita memakai ->> karena bahan dan kapasitas adalah string yang ingin ditampilkan sebagai teks bersih.
Membaca Elemen Array dengan Indeks #
Anggota bertipe array ditulusuri dengan tanda kurung siku berisi nomor indeks, dihitung dari nol. Kolom tag pada produk kita adalah array of string, sehingga $.tag[0] mengambil elemen pertama dan $.tag[1] elemen kedua:
SELECT
nama,
atribut->'$.tag[0]' AS tag_pertama,
atribut->'$.tag[1]' AS tag_kedua
FROM produk
ORDER BY id;Output yang Dihasilkan:
+-----------------+-------------+-------------+
| nama | tag_pertama | tag_kedua |
+-----------------+-------------+-------------+
| Kopi Gayo 250g | "arabika" | "toraja" |
| Mug Keramik | "keramik" | "microwave" |
| Teh Melati 100g | "teh" | "melati" |
| Gula Aren 500g | NULL | NULL |
+-----------------+-------------+-------------+
4 rows in set (0.00 sec)Teh Melati dan dua produk ber-tag tampil normal, sementara Gula Aren menghasilkan NULL karena dokumennya tidak punya array tag sama sekali. Elemen array berupa string tetap ber-kutip saat diambil dengan ->, sama perilakunya dengan anggota object. Indeks berhitung dari 0 hingga jumlah elemen dikurangi satu — mengakses indeks di luar rentang juga berakhir NULL, bukan galat. MySQL 8.4 juga mengenal kata last sebagai indeks elemen terakhir, sehingga $.tag[last] praktis untuk array yang panjangnya tidak pasti:
SELECT
nama,
atribut->>'$.tag[0]' AS tag_pertama,
atribut->>'$.tag[last]' AS tag_terakhir
FROM produk
WHERE id <= 3
ORDER BY id;Output yang Dihasilkan:
+-----------------+-------------+--------------+
| nama | tag_pertama | tag_terakhir |
+-----------------+-------------+--------------+
| Kopi Gayo 250g | arabika | toraja |
| Mug Keramik | keramik | microwave |
| Teh Melati 100g | teh | melati |
+-----------------+-------------+--------------+
3 rows in set (0.00 sec)Path yang Tidak Ada Menghasilkan NULL #
Kita sudah melihat polanya berulang kali, dan patut dinyatakan tegas sebagai aturan: path yang menunjuk anggota yang tidak ada tidak pernah memicu galat — hasilnya selalu NULL. Perilaku ini masuk akal untuk data semi terstruktur yang tiap baris boleh beda struktur; satu produk tanpa warna tidak seharusnya meruntuhkan seluruh query. Konsekuensinya, NULL pada hasil JSON adalah sinyal “tidak ada” yang harus ditangani, bukan diabaikan:
SELECT
nama,
IFNULL(atribut->>'$.warna', 'tanpa warna') AS warna
FROM produk
ORDER BY id;Output yang Dihasilkan:
+-----------------+-------------+
| nama | warna |
+-----------------+-------------+
| Kopi Gayo 250g | coklat |
| Mug Keramik | putih |
| Teh Melati 100g | tanpa warna |
| Gula Aren 500g | tanpa warna |
+-----------------+-------------+
4 rows in set (0.00 sec)Fungsi IFNULL menggantikan NULL dengan label tanpa warna supaya laporan lebih enak dibaca. Pola yang sama berguna untuk ORDER BY agar baris tanpa atribut tidak menumpuk di urutan paling depan secara diam-diam.
Karakter Pengganti [*] untuk Seluruh Elemen #
Path dapat pula memakai karakter pengganti [*] di posisi indeks, yang berarti “semua elemen array ini”. Hasilnya dikembalikan sebagai satu array JSON berisi elemen-elemen tersebut:
SELECT
atribut->'$.tag' AS seluruh_anggota,
atribut->'$.tag[*]' AS wildcard
FROM produk
WHERE id = 1;Output yang Dihasilkan:
+-----------------------+-----------------------+
| seluruh_anggota | wildcard |
+-----------------------+-----------------------+
| ["arabika", "toraja"] | ["arabika", "toraja"] |
+-----------------------+-----------------------+
1 row in set (0.00 sec)Kedua kolom tampak sama karena $.tag dan $.tag[*] memang menghasilkan array yang sama pada kasus ini — bedanya, $.tag mengambil anggota tag apa adanya, sedangkan $.tag[*] mengumpulkan semua elemennya menjadi array baru. Perbedaan keduanya baru terasa saat path menggabungkan wildcard dengan penulusuran lebih dalam, misalnya mengambil satu anggota dari setiap elemen array bersarang — bahasan lengkapnya ada di chapter JSON Path berikutnya.
Membersihkan Database Latihan #
Latihan membaca nilai selesai dan database latihan kita jatuhkan:
DROP DATABASE belajar_json;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Referensi #
- JSON Search Functions — MySQL 8.4 Reference Manual
- The JSON Data Type — MySQL 8.4 Reference Manual
- JSON Function Reference — MySQL 8.4 Reference Manual
Operator -> dan ->> kini menjadi pintu kita masuk ke dokumen: nilai JSON berkutip, teks polos, penulusuran bersarang, indeks array, dan NULL untuk path yang hilang — semuanya sudah teruji. Path yang kita pakai sejauh ini masih bentuk sederhana $, ., dan [n]. Chapter berikutnya membedah bahasa JSON Path secara utuh: wildcard $.*, array bersarang [[]], dan fungsi-fungsi pencarian yang beroperasi langsung pada path.