REST API modern hampir semuanya berbicara dalam satu bahasa: JSON. Format ini ringkas, bisa dibaca manusia, dan didukung setiap bahasa pemrograman — tetapi pustaka standar Java sengaja tidak menyertakan parser JSON, karena API JSON sebagai bagian dari platform pernah diskusikan bertahun-tahun tanpa kesepakatan final. Bagi kita, ada dua jalur praktis: menyusun dan membedah JSON secara manual untuk kasus sederhana, atau memakai pustaka produksi seperti Jackson dan Gson lewat build tool. Chapter ini menempuh jalur pertama secara menyeluruh — escape, susun, kirim lewat POST, lalu baca balasan — sehingga kita memahami apa yang sebenarnya dikerjakan pustaka di belakang layar, sebelum menutup dengan pola konfigurasi jalur kedua.
Aturan Escape pada JSON #
JSON membungkus teks dalam tanda kutip ganda, sehingga tanda kutip di dalam nilai wajib di-escape: karakter " menjadi \", backslash menjadi \\, baris baru menjadi \n, dan tab menjadi \t. Karakter kendali lain ditulis \uXXXX dengan empat digit heksadesimal. Tanpa escape yang benar, JSON yang kita susun jadi tidak valid — parser akan menolaknya atau, lebih buruk, membaca struktur yang salah.
Mari kita buktikan aturannya dengan method escape sederhana yang memetakan karakter berisiko menjadi bentuk amannya:
public class EscapeJson {
static String escape(String teks) {
StringBuilder hasil = new StringBuilder();
for (int i = 0; i < teks.length(); i++) {
char c = teks.charAt(i);
switch (c) {
case '"' -> hasil.append("\\\"");
case '\\' -> hasil.append("\\\\");
case '\n' -> hasil.append("\\n");
case '\t' -> hasil.append("\\t");
default -> hasil.append(c);
}
}
return hasil.toString();
}
public static void main(String[] args) {
String nama = "Budi \"Si Cepat\" Santoso";
String alamat = "Jl. Kenanga 7\tBandung";
System.out.println("{\"nama\":\"" + escape(nama) + "\"}");
System.out.println("{\"alamat\":\"" + escape(alamat) + "\"}");
}
}Output yang Dihasilkan:
{"nama":"Budi \"Si Cepat\" Santoso"}
{"alamat":"Jl. Kenanga 7\tBandung"}Method escape memeriksa setiap karakter dengan switch expression: tanda kutip dan backslash diawali backslash, karakter kendali diganti bentuk pendeknya, dan karakter biasa dibiarkan apa adanya. Perhatikan bahwa string Java "\\" di sini berarti satu backslash — kita sedang menulis dua lapisan sekaligus: string Java yang berisi teks JSON. Kebiasaan mental memilah lapisan ini adalah keterampilan inti saat bekerja dengan JSON manual.
Percakapan JSON Lengkap lewat HTTP #
Sekarang kita rangkai semuanya menjadi satu percakapan nyata: klien mengirim POST berisi JSON ke server lokal, server membaca nilai nama dari body, lalu membalas JSON baru. Permintaan menyertakan header Content-Type: application/json — etiket yang memberi tahu server format body; tanpanya server harus menebak. Untuk sisi baca, kita pakai pembaca mini ambilNilai yang mencari kunci dan membaca nilai stringnya dengan penanganan escape dasar:
import com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class TugasJson {
// Menyusun objek JSON kecil secara manual dengan escape yang benar
static String bangunJson(String nama, String prioritas) {
return "{\"nama\":\"" + EscapeJson.escape(nama)
+ "\",\"prioritas\":\"" + EscapeJson.escape(prioritas) + "\"}";
}
// Pembaca nilai string sederhana untuk JSON yang kita susun sendiri
static String ambilNilai(String json, String kunci) {
String penanda = "\"" + kunci + "\":";
int posisi = json.indexOf(penanda);
if (posisi < 0) {
return null;
}
int i = posisi + penanda.length();
while (i < json.length() && json.charAt(i) != '"') {
i++;
}
i++; // lewati tanda kutip pembuka nilai
StringBuilder nilai = new StringBuilder();
while (i < json.length()) {
char c = json.charAt(i);
if (c == '\\' && i + 1 < json.length()) {
char berikutnya = json.charAt(i + 1);
nilai.append(switch (berikutnya) {
case '"' -> '"';
case '\\' -> '\\';
case 'n' -> '\n';
case 't' -> '\t';
default -> berikutnya;
});
i += 2;
} else if (c == '"') {
break;
} else {
nilai.append(c);
i++;
}
}
return nilai.toString();
}
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/tugas", exchange -> {
String bodyMasuk = new String(exchange.getRequestBody().readAllBytes(),
StandardCharsets.UTF_8);
String nama = ambilNilai(bodyMasuk, "nama");
String jawaban = "{\"status\":\"diterima\",\"nama\":\""
+ EscapeJson.escape(nama) + "\",\"id\":42}";
byte[] body = jawaban.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set("Content-Type", "application/json");
exchange.sendResponseHeaders(200, body.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(body);
}
});
server.start();
try {
String permintaan = bangunJson("Budi Santoso", "tinggi");
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/tugas"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(permintaan))
.build();
HttpClient klien = HttpClient.newHttpClient();
HttpResponse<String> res = klien.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println("kirim : " + permintaan);
System.out.println("terima : " + res.body());
System.out.println("status : " + ambilNilai(res.body(), "status"));
System.out.println("nama : " + ambilNilai(res.body(), "nama"));
} finally {
server.stop(0);
}
}
}Output yang Dihasilkan:
kirim : {"nama":"Budi Santoso","prioritas":"tinggi"}
terima : {"status":"diterima","nama":"Budi Santoso","id":42}
status : diterima
nama : Budi SantosoMari kita telaah bagian-bagian pentingnya:
- Baris 13-16 (
bangunJson): menyusun objek JSON denganStringBuilderimplisit dari penggabungan string — tanda kurung kurawal, kunci dalam kutip, dan nilai yang selalu melewatiEscapeJson.escape. Susunan ini valid karena kita yang mengendalikan seluruh struktur. - Baris 20-55 (
ambilNilai): pembaca mini yang bekerja untuk JSON berbentuk datar. Method mencari posisi"kunci":, melompat ke kutip pembuka nilai, lalu menyalin karakter demi karakter — karakter setelah backslash diterjemahkan kembali (\"menjadi") dan kutip penutup menghentikan pembacaan. - Baris 65-78 (handler
/tugas): server membaca seluruh body masuk, mengambil nilainama, lalu merangkai balasan JSON lengkap denganidnumerik. HeaderContent-Type: application/jsonikut dikirim agar klien tahu format balasan. - Baris 88-91 (permintaan
POST):BodyPublishers.ofString(permintaan)membawa JSON sebagai body, danheader("Content-Type", "application/json")menandai formatnya. Nilai"tinggi"pada prioritas menunjukkan bahwa field non-kritis boleh tetap dikirim meski server tidak membacanya. - Baris 96-99: baris
terimamenampilkan balasan mentah, dua baris berikutnya membuktikanambilNilaiberhasil membedah balasan itu kembali menjadi nilai.
Jalur Produksi dengan Jackson atau Gson #
Pada proyek nyata, pekerjaan di atas diserahkan kepada pustaka JSON. Jackson dan Gson memetakan objek Java ke JSON dan sebaliknya, menangani escape, angka, array, dan objek bersarang dengan benar. Keduanya adalah dependensi eksternal yang ditambahkan lewat Maven atau Gradle:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>Pemakaiannya berpusat pada satu class mapper yang mengubah objek menjadi string dan sebaliknya:
ObjectMapper mapper = new ObjectMapper();
// Objek Java -> JSON
PesanTugas tugas = new PesanTugas("Budi Santoso", "tinggi");
String json = mapper.writeValueAsString(tugas);
// JSON -> objek Java
PesanTugas balik = mapper.readValue(json, PesanTugas.class);Dengan Jackson di tangan, class PesanTugas berupa record biasa — Jackson membaca nama komponen record sebagai kunci JSON. Gson menawarkan pola serupa lewat Gson gson = new Gson(); gson.toJson(...) dan gson.fromJson(...). Pilihan di antara keduanya lebih soal selera tim dan ekosistem framework: Spring Boot memakai Jackson secara bawaan, sedangkan Gson umum pada proyek Android.
Materi Build Tools dan Dependency Management membahas cara menambahkan dependensi ini secara rinci. Sebelum sampai ke sana, ada dua disiplin operasional yang menentukan sehat-tidaknya klien HTTP di produksi: pembatasan waktu dan kebijakan percobaan ulang — pokok bahasan Chapter berikutnya.
Referensi #
- Oracle: HttpRequest.BodyPublishers API
- Oracle: HttpResponse.BodyHandlers API
- Maven: Panduan Dependensi
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
EscapeJson.java
Unduh berkaspublic class EscapeJson {
static String escape(String teks) {
StringBuilder hasil = new StringBuilder();
for (int i = 0; i < teks.length(); i++) {
char c = teks.charAt(i);
switch (c) {
case '"' -> hasil.append("\\\"");
case '\\' -> hasil.append("\\\\");
case '\n' -> hasil.append("\\n");
case '\t' -> hasil.append("\\t");
default -> hasil.append(c);
}
}
return hasil.toString();
}
public static void main(String[] args) {
String nama = "Budi \"Si Cepat\" Santoso";
String alamat = "Jl. Kenanga 7\tBandung";
System.out.println("{\"nama\":\"" + escape(nama) + "\"}");
System.out.println("{\"alamat\":\"" + escape(alamat) + "\"}");
}
}
TugasJson.java
Unduh berkasimport com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class TugasJson {
// Menyusun objek JSON kecil secara manual dengan escape yang benar
static String bangunJson(String nama, String prioritas) {
return "{\"nama\":\"" + EscapeJson.escape(nama)
+ "\",\"prioritas\":\"" + EscapeJson.escape(prioritas) + "\"}";
}
// Pembaca nilai string sederhana untuk JSON yang kita susun sendiri
static String ambilNilai(String json, String kunci) {
String penanda = "\"" + kunci + "\":";
int posisi = json.indexOf(penanda);
if (posisi < 0) {
return null;
}
int i = posisi + penanda.length();
while (i < json.length() && json.charAt(i) != '"') {
i++;
}
i++; // lewati tanda kutip pembuka nilai
StringBuilder nilai = new StringBuilder();
while (i < json.length()) {
char c = json.charAt(i);
if (c == '\\' && i + 1 < json.length()) {
char berikutnya = json.charAt(i + 1);
nilai.append(switch (berikutnya) {
case '"' -> '"';
case '\\' -> '\\';
case 'n' -> '\n';
case 't' -> '\t';
default -> berikutnya;
});
i += 2;
} else if (c == '"') {
break;
} else {
nilai.append(c);
i++;
}
}
return nilai.toString();
}
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/tugas", exchange -> {
String bodyMasuk = new String(exchange.getRequestBody().readAllBytes(),
StandardCharsets.UTF_8);
String nama = ambilNilai(bodyMasuk, "nama");
String jawaban = "{\"status\":\"diterima\",\"nama\":\""
+ EscapeJson.escape(nama) + "\",\"id\":42}";
byte[] body = jawaban.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set("Content-Type", "application/json");
exchange.sendResponseHeaders(200, body.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(body);
}
});
server.start();
try {
String permintaan = bangunJson("Budi Santoso", "tinggi");
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/tugas"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(permintaan))
.build();
HttpClient klien = HttpClient.newHttpClient();
HttpResponse<String> res = klien.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println("kirim : " + permintaan);
System.out.println("terima : " + res.body());
System.out.println("status : " + ambilNilai(res.body(), "status"));
System.out.println("nama : " + ambilNilai(res.body(), "nama"));
} finally {
server.stop(0);
}
}
}