JSON #
JSON (JavaScript Object Notation) adalah format pertukaran data yang paling dominan di web saat ini — hampir setiap REST API menggunakannya. Di Java, dua library yang paling banyak dipakai untuk bekerja dengan JSON adalah Jackson dan Gson. Jackson adalah pilihan default Spring Boot dan ekosistem enterprise, sementara Gson lebih ringan dan sering dipakai di Android atau proyek yang tidak butuh fitur penuh Jackson. Artikel ini membahas cara serialisasi objek Java menjadi string JSON, deserialisasi JSON kembali menjadi objek, kustomisasi penamaan field, penanganan field opsional, tipe generik, tanggal, hingga cara mem-parsing JSON secara dinamis tanpa kelas yang sudah diketahui strukturnya.
Gambaran Umum #
Dua operasi utama dalam bekerja dengan JSON:
| Operasi | Arah | Jackson | Gson |
|---|---|---|---|
| Serialisasi | Objek Java → String JSON | objectMapper.writeValueAsString(obj) | gson.toJson(obj) |
| Deserialisasi | String JSON → Objek Java | objectMapper.readValue(json, Kelas.class) | gson.fromJson(json, Kelas.class) |
flowchart LR
A["Objek Java\n(POJO / Record)"] -->|"Serialisasi"| B["String JSON<br/>{"nama":"Laptop"}"]
B -->|"Deserialisasi"| A
C["File / HTTP Response\nInputStream"] -->|"readValue()"| A
A -->|"writeValue()"| D["File / HTTP Body\nOutputStream"]Jackson #
Jackson adalah library JSON paling populer di ekosistem Java. Spring Boot menggunakannya secara default. Kelas utamanya adalah ObjectMapper yang thread-safe dan mahal untuk dibuat — selalu jadikan singleton atau biarkan Spring mengelolanya.
Dependensi #
<!-- Maven -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.1</version>
</dependency>
<!-- Untuk dukungan Java 8 date/time (LocalDate, LocalDateTime, dll.) -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.1</version>
</dependency>
// Gradle
implementation 'com.fasterxml.jackson.databind:jackson-databind:2.17.1'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1'
Di proyek Spring Boot, Jackson sudah tersedia otomatis lewat spring-boot-starter-web — tidak perlu tambahkan dependensi secara manual.
Serialisasi — Objek ke JSON #
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
// ObjectMapper adalah thread-safe — buat sekali, pakai berulang
ObjectMapper mapper = new ObjectMapper();
// Model sederhana (bisa POJO atau record)
record Produk(Long id, String nama, double harga, int stok) {}
Produk laptop = new Produk(1L, "Laptop", 12_000_000, 5);
// Serialisasi ke String
String json = mapper.writeValueAsString(laptop);
// {"id":1,"nama":"Laptop","harga":12000000.0,"stok":5}
// Serialisasi dengan pretty print (indentasi, lebih mudah dibaca)
String jsonRapi = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(laptop);
/*
{
"id" : 1,
"nama" : "Laptop",
"harga" : 12000000.0,
"stok" : 5
}
*/
// Serialisasi ke file
mapper.writeValue(new java.io.File("produk.json"), laptop);
// Serialisasi ke byte array (untuk HTTP response body)
byte[] bytes = mapper.writeValueAsBytes(laptop);
// Serialisasi List
List<Produk> daftar = List.of(laptop, new Produk(2L, "Mouse", 150_000, 20));
String jsonDaftar = mapper.writeValueAsString(daftar);
// [{"id":1,"nama":"Laptop",...},{"id":2,"nama":"Mouse",...}]
Deserialisasi — JSON ke Objek #
String json = """
{
"id": 1,
"nama": "Laptop",
"harga": 12000000.0,
"stok": 5
}
""";
// Deserialisasi ke kelas tertentu
Produk produk = mapper.readValue(json, Produk.class);
System.out.println(produk.nama()); // Laptop
// Deserialisasi dari file
Produk dariFile = mapper.readValue(new java.io.File("produk.json"), Produk.class);
// Deserialisasi dari URL (langsung fetch dan parse)
Produk dariURL = mapper.readValue(new java.net.URL("https://api.example.com/produk/1"), Produk.class);
// Deserialisasi List
String jsonDaftar = "[{\"id\":1,\"nama\":\"Laptop\"},{\"id\":2,\"nama\":\"Mouse\"}]";
List<Produk> daftar = mapper.readValue(jsonDaftar,
mapper.getTypeFactory().constructCollectionType(List.class, Produk.class));
// Atau dengan TypeReference (lebih bersih)
import com.fasterxml.jackson.core.type.TypeReference;
List<Produk> daftar2 = mapper.readValue(jsonDaftar, new TypeReference<List<Produk>>() {});
Penanganan Field yang Tidak Dikenal #
// ANTI-PATTERN: JSON punya field yang tidak ada di kelas → UnrecognizedPropertyException
String jsonDenganFieldBaru = """
{"id": 1, "nama": "Laptop", "harga": 12000000.0, "stok": 5, "kategori": "elektronik"}
""";
// Jika Produk tidak punya field "kategori" → error!
// BENAR: abaikan field yang tidak dikenal
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Atau dengan anotasi di kelas
@JsonIgnoreProperties(ignoreUnknown = true)
public class Produk { /* ... */ }
Anotasi Jackson #
Anotasi Jackson mengontrol bagaimana objek diserialisasi dan dideserialisasi tanpa mengubah struktur kelas.
@JsonProperty — Penamaan Field #
import com.fasterxml.jackson.annotation.*;
public class Pengguna {
private Long id;
// Nama field di JSON berbeda dari nama field Java
@JsonProperty("full_name") // JSON: "full_name", Java: namaLengkap
private String namaLengkap;
@JsonProperty("email_address")
private String email;
@JsonProperty("created_at")
private LocalDateTime dibuatPada;
// Getter dan setter...
}
// Hasil serialisasi:
// {"id":1,"full_name":"Budi Santoso","email_address":"[email protected]","created_at":"2025-08-17T10:00:00"}
@JsonIgnore dan @JsonIgnoreProperties #
public class AkunBank {
private Long id;
private String nomorRekening;
@JsonIgnore // field ini tidak pernah masuk ke JSON (output maupun input)
private String pinRahasia;
@JsonIgnore
private String passwordHash;
}
// Anotasi di level kelas — abaikan field tertentu berdasarkan nama
@JsonIgnoreProperties({"pinRahasia", "passwordHash", "internalId"})
public class AkunBankV2 {
private Long id;
private String nomorRekening;
private String pinRahasia; // akan diabaikan
}
@JsonInclude — Atur Field yang Disertakan #
import com.fasterxml.jackson.annotation.JsonInclude;
// Jangan sertakan field yang null dalam output JSON
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseDTO {
private String data;
private String error; // null saat sukses → tidak muncul di JSON
private Integer total; // null saat tidak relevan → tidak muncul
}
// Opsi lain:
// NON_NULL : lewati field null
// NON_EMPTY : lewati null dan string/collection kosong
// NON_DEFAULT : lewati nilai default (0, false, null, "")
// ALWAYS : selalu sertakan (default)
// Atau konfigurasi global di ObjectMapper
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
@JsonAlias — Banyak Nama Diterima #
public class Produk {
// Menerima "product_name", "productName", atau "nama" dari JSON input
@JsonAlias({"product_name", "productName"})
@JsonProperty("nama") // tapi output selalu menggunakan "nama"
private String nama;
}
@JsonCreator dan @JsonValue #
// @JsonValue: gunakan satu field sebagai representasi JSON seluruh objek
public enum StatusOrder {
DIBUAT("dibuat"),
DIPROSES("diproses"),
DIKIRIM("dikirim"),
SELESAI("selesai");
private final String kode;
StatusOrder(String kode) { this.kode = kode; }
@JsonValue // serialisasi enum sebagai string kode, bukan nama enum
public String getKode() { return kode; }
@JsonCreator // deserialisasi string kode kembali menjadi enum
public static StatusOrder dariKode(String kode) {
for (StatusOrder s : values()) {
if (s.kode.equals(kode)) return s;
}
throw new IllegalArgumentException("Kode tidak valid: " + kode);
}
}
// Hasil: {"status":"dikirim"} bukan {"status":"DIKIRIM"}
@JsonSerialize dan @JsonDeserialize #
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
// Serializer kustom untuk format harga
public class HargaSerializer extends StdSerializer<Double> {
public HargaSerializer() { super(Double.class); }
@Override
public void serialize(Double value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeString("Rp" + String.format("%,.0f", value));
}
}
public class Produk {
private String nama;
@JsonSerialize(using = HargaSerializer.class)
private Double harga;
}
// Hasil: {"nama":"Laptop","harga":"Rp12.000.000"}
Tipe Generik #
Deserialisasi ke tipe generik seperti List<T> atau Map<String, T> membutuhkan informasi tipe runtime yang tidak bisa diperoleh hanya dari Kelas.class karena Java menghapus informasi generik saat runtime (type erasure).
TypeReference untuk Tipe Kompleks #
import com.fasterxml.jackson.core.type.TypeReference;
String jsonList = "[{\"id\":1,\"nama\":\"Laptop\"},{\"id\":2,\"nama\":\"Mouse\"}]";
String jsonMap = "{\"laptop\":{\"id\":1,\"harga\":12000000},\"mouse\":{\"id\":2,\"harga\":150000}}";
// List<Produk> — pakai TypeReference
List<Produk> daftar = mapper.readValue(jsonList, new TypeReference<List<Produk>>() {});
// Map<String, Produk>
Map<String, Produk> byNama = mapper.readValue(jsonMap, new TypeReference<Map<String, Produk>>() {});
// JavaType — alternatif programatik
JavaType listType = mapper.getTypeFactory()
.constructCollectionType(List.class, Produk.class);
List<Produk> daftar2 = mapper.readValue(jsonList, listType);
// Tipe nested: List<Map<String, Produk>>
JavaType innerMap = mapper.getTypeFactory().constructMapType(Map.class, String.class, Produk.class);
JavaType outerList = mapper.getTypeFactory().constructCollectionType(List.class, innerMap);
// TypeReference bersarang
Map<String, List<Produk>> grouped = mapper.readValue(json,
new TypeReference<Map<String, List<Produk>>>() {});
Penanganan Tanggal dan Waktu #
Jackson secara default menserialisasi LocalDate dan LocalDateTime sebagai array angka [2025, 8, 17] — bukan format string yang mudah dibaca. Modul JSR-310 memperbaiki ini.
Konfigurasi ObjectMapper untuk java.time #
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.SerializationFeature;
ObjectMapper mapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
// Nonaktifkan serialisasi sebagai timestamp — pakai string ISO-8601
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
record Event(String nama, LocalDate tanggal, LocalDateTime waktu) {}
Event acara = new Event("Konferensi Java", LocalDate.of(2025, 8, 17),
LocalDateTime.of(2025, 8, 17, 9, 0));
String json = mapper.writeValueAsString(acara);
// {"nama":"Konferensi Java","tanggal":"2025-08-17","waktu":"2025-08-17T09:00:00"}
Format Tanggal Kustom #
import com.fasterxml.jackson.annotation.JsonFormat;
public class Transaksi {
private Long id;
@JsonFormat(pattern = "dd/MM/yyyy")
private LocalDate tanggal;
@JsonFormat(pattern = "dd/MM/yyyy HH:mm:ss")
private LocalDateTime waktu;
@JsonFormat(shape = JsonFormat.Shape.NUMBER)
private Instant timestamp; // serialisasi sebagai epoch milli
}
Parsing JSON Dinamis dengan JsonNode #
Terkadang kamu tidak tahu struktur JSON sebelumnya — misalnya saat menangani webhook atau response API yang bervariasi. Gunakan JsonNode sebagai pohon yang bisa dinavigasi.
Membaca dan Menavigasi JsonNode #
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
String json = """
{
"id": 1,
"nama": "Laptop",
"harga": 12000000,
"tersedia": true,
"spesifikasi": {
"ram": "16GB",
"storage": "512GB SSD"
},
"tag": ["elektronik", "komputer", "kerja"]
}
""";
JsonNode root = mapper.readTree(json);
// Akses field
long id = root.get("id").asLong(); // 1
String nama = root.get("nama").asText(); // "Laptop"
double harga = root.get("harga").asDouble(); // 12000000.0
boolean tersedia = root.get("tersedia").asBoolean(); // true
// Nested object
JsonNode spek = root.get("spesifikasi");
String ram = spek.get("ram").asText(); // "16GB"
String storage = spek.get("storage").asText(); // "512GB SSD"
// Array
JsonNode tags = root.get("tag");
for (JsonNode tag : tags) {
System.out.println(tag.asText()); // elektronik, komputer, kerja
}
// Cek apakah field ada
boolean adaDiskon = root.has("diskon"); // false
boolean isNull = root.get("id").isNull(); // false
// Optional path — tidak throw jika field tidak ada
String opsional = root.path("tidakAda").asText("default"); // "default"
// Navigasi path bertingkat aman
String ram2 = root.at("/spesifikasi/ram").asText(); // "16GB"
String tidakAda = root.at("/spesifikasi/warna").asText("hitam"); // "hitam" (default)
Membuat dan Memodifikasi JsonNode #
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
// Buat JSON dari awal secara programatik
ObjectNode root = mapper.createObjectNode();
root.put("id", 1);
root.put("nama", "Laptop");
root.put("harga", 12_000_000.0);
root.put("tersedia", true);
// Nested object
ObjectNode spek = root.putObject("spesifikasi");
spek.put("ram", "16GB");
spek.put("storage", "512GB SSD");
// Array
ArrayNode tags = root.putArray("tag");
tags.add("elektronik");
tags.add("komputer");
String hasilJson = mapper.writeValueAsString(root);
// Ubah field yang sudah ada
((ObjectNode) root).put("harga", 11_000_000.0); // timpa nilai
((ObjectNode) root).remove("tersedia"); // hapus field
// Merge dua JsonNode
ObjectNode tambahan = mapper.createObjectNode();
tambahan.put("diskon", 10);
root.setAll(tambahan); // tambahkan semua field dari tambahan ke root
Gson #
Gson adalah library Google yang lebih ringan dari Jackson. Tidak butuh anotasi atau konfigurasi untuk kasus sederhana, tapi fiturnya tidak selengkap Jackson.
Dependensi #
<!-- Maven -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
// Gradle
implementation 'com.google.code.gson:gson:2.10.1'
Serialisasi dan Deserialisasi Dasar #
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
// Gson adalah thread-safe dan ringan — bisa dibuat sebagai konstanta
Gson gson = new Gson();
// Model — Gson tidak butuh anotasi untuk kasus sederhana
public class Produk {
private Long id;
private String nama;
private double harga;
// Gson bekerja dengan field langsung, tidak butuh getter/setter
public Produk(Long id, String nama, double harga) {
this.id = id; this.nama = nama; this.harga = harga;
}
}
Produk laptop = new Produk(1L, "Laptop", 12_000_000);
// Serialisasi
String json = gson.toJson(laptop);
// {"id":1,"nama":"Laptop","harga":1.2E7}
// Dengan GsonBuilder untuk konfigurasi
Gson gsonRapi = new GsonBuilder()
.setPrettyPrinting() // pretty print
.serializeNulls() // sertakan field null
.disableHtmlEscaping() // jangan escape karakter HTML
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss") // format tanggal kustom
.create();
// Deserialisasi
Produk dariJson = gson.fromJson(json, Produk.class);
System.out.println(dariJson.nama); // Laptop
// List — butuh TypeToken
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
String jsonList = "[{\"id\":1,\"nama\":\"Laptop\"},{\"id\":2,\"nama\":\"Mouse\"}]";
Type listType = new TypeToken<List<Produk>>(){}.getType();
List<Produk> daftar = gson.fromJson(jsonList, listType);
// Map
Type mapType = new TypeToken<Map<String, Produk>>(){}.getType();
Map<String, Produk> peta = gson.fromJson(jsonMap, mapType);
Kustomisasi Gson #
import com.google.gson.*;
// Serializer kustom — ubah LocalDate menjadi string
Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDate.class, (JsonSerializer<LocalDate>)
(src, typeOfSrc, ctx) -> new JsonPrimitive(src.toString()))
.registerTypeAdapter(LocalDate.class, (JsonDeserializer<LocalDate>)
(json, typeOfT, ctx) -> LocalDate.parse(json.getAsString()))
.create();
// @SerializedName: setara @JsonProperty di Jackson
public class Pengguna {
@com.google.gson.annotations.SerializedName("full_name")
private String namaLengkap;
@com.google.gson.annotations.Expose // hanya sertakan field yang @Expose
private String email;
private transient String passwordHash; // 'transient' mengecualikan field dari Gson
}
Jackson vs Gson — Perbandingan #
| Aspek | Jackson | Gson |
|---|---|---|
| Performa | Lebih cepat | Sedikit lebih lambat |
| Fitur | Sangat lengkap | Lebih terbatas |
| Konfigurasi | Lebih banyak opsi | Lebih sederhana |
| Anotasi | @JsonProperty, @JsonIgnore, dll. | @SerializedName, transient |
| Spring Boot | Default, terintegrasi penuh | Butuh konfigurasi manual |
| Tipe generik | TypeReference | TypeToken |
| Ukuran JAR | Lebih besar | Lebih kecil |
| Dukungan java.time | Modul JSR-310 | Butuh adapter manual |
| Popularitas | Lebih tinggi di enterprise | Lebih tinggi di Android |
Integrasi dengan Spring Boot #
Spring Boot menggunakan Jackson secara otomatis untuk semua response @RestController. Kamu bisa mengkonfigurasinya lewat application.properties atau bean ObjectMapper.
Konfigurasi via application.properties #
# Format tanggal default
spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss
spring.jackson.time-zone=Asia/Jakarta
# Jangan tulis tanggal sebagai timestamp
spring.jackson.serialization.write-dates-as-timestamps=false
# Abaikan field unknown saat deserialisasi
spring.jackson.deserialization.fail-on-unknown-properties=false
# Pretty print (jangan aktifkan di produksi — boros bandwidth)
spring.jackson.serialization.indent-output=false
# Jangan sertakan field null
spring.jackson.default-property-inclusion=NON_NULL
# Naming strategy: snake_case untuk semua field
spring.jackson.property-naming-strategy=SNAKE_CASE
Konfigurasi via Bean #
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
}
}
Request dan Response di Controller #
@RestController
@RequestMapping("/api/produk")
public class ProdukController {
// Spring otomatis deserialisasi JSON body ke Produk
@PostMapping
public ResponseEntity<Produk> buat(@RequestBody Produk produk) {
// produk sudah di-deserialize dari JSON request body
Produk disimpan = service.simpan(produk);
return ResponseEntity.status(201).body(disimpan);
// Spring otomatis serialisasi disimpan ke JSON response body
}
// Response bisa berupa Map untuk JSON ad-hoc
@GetMapping("/info")
public Map<String, Object> info() {
return Map.of(
"versi", "1.0.0",
"waktu", LocalDateTime.now(),
"status", "aktif"
);
}
}
Kasus Nyata #
Memanggil REST API Eksternal #
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
HttpClient client = HttpClient.newHttpClient();
// GET — terima JSON dan parse
HttpRequest getReq = HttpRequest.newBuilder()
.uri(java.net.URI.create("https://api.example.com/produk/1"))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> res = client.send(getReq, HttpResponse.BodyHandlers.ofString());
Produk produk = mapper.readValue(res.body(), Produk.class);
// POST — kirim objek sebagai JSON
Produk baru = new Produk(null, "Monitor", 3_500_000, 10);
String requestBody = mapper.writeValueAsString(baru);
HttpRequest postReq = HttpRequest.newBuilder()
.uri(java.net.URI.create("https://api.example.com/produk"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> postRes = client.send(postReq, HttpResponse.BodyHandlers.ofString());
Produk tersimpan = mapper.readValue(postRes.body(), Produk.class);
Membaca dan Menulis File JSON #
// Baca dari file
List<Produk> katalog = mapper.readValue(
new java.io.File("katalog.json"),
new TypeReference<List<Produk>>() {}
);
// Tulis ke file dengan pretty print
mapper.writerWithDefaultPrettyPrinter()
.writeValue(new java.io.File("katalog-baru.json"), katalog);
// Baca dari classpath (resource file dalam JAR)
InputStream stream = getClass().getResourceAsStream("/data/produk.json");
Produk dariResource = mapper.readValue(stream, Produk.class);
Transformasi JSON tanpa Model #
// Ubah semua nama field menjadi snake_case tanpa mendefinisikan kelas tujuan
String jsonCamel = "{\"namaLengkap\":\"Budi\",\"tanggalLahir\":\"1995-01-15\"}";
JsonNode node = mapper.readTree(jsonCamel);
// proses dan transformasi node sesuai kebutuhan
ObjectNode transformed = mapper.createObjectNode();
node.fields().forEachRemaining(e -> {
// konversi camelCase ke snake_case
String snakeKey = e.getKey().replaceAll("([A-Z])", "_$1").toLowerCase();
transformed.set(snakeKey, e.getValue());
});
String jsonSnake = mapper.writeValueAsString(transformed);
// {"nama_lengkap":"Budi","tanggal_lahir":"1995-01-15"}
Kapan Menggunakan Jackson vs Gson #
Gunakan JACKSON jika:
✓ Proyek Spring Boot (sudah ada, tidak perlu tambah dependensi)
✓ Butuh fitur lengkap: custom serializer, JsonNode, streaming API
✓ Butuh dukungan java.time yang mulus (dengan modul JSR-310)
✓ Perlu naming strategy global (snake_case, kebab-case)
✓ Aplikasi enterprise dengan kebutuhan JSON yang kompleks
Gunakan GSON jika:
✓ Proyek Android atau proyek non-Spring yang ringan
✓ Tidak butuh fitur canggih — serialisasi/deserialisasi sederhana
✓ Ingin setup minimal tanpa konfigurasi
✓ Tim sudah familiar dengan Gson
Anti-pattern yang perlu dihindari:
✗ Buat ObjectMapper baru setiap request — sangat mahal, jadikan singleton
✗ Abaikan unknown properties tanpa alasan — bisa sembunyikan bug
✗ Simpan password atau data sensitif dalam JSON response tanpa @JsonIgnore
✗ Gunakan Jackson dan Gson sekaligus dalam satu proyek tanpa alasan jelas
Ringkasan #
ObjectMapperadalah singleton — ia thread-safe dan mahal dibuat. Di Spring Boot, biarkan framework mengelolanya. Di kode biasa, buat sekali sebagai konstanta static.writeValueAsString()untuk serialisasi,readValue()untuk deserialisasi — dua metode utama yang paling sering dipakai. TambahkanwriterWithDefaultPrettyPrinter()untuk output yang rapi.TypeReferenceuntuk tipe generik —mapper.readValue(json, new TypeReference<List<Produk>>(){})karena Java menghapus informasi generik saat runtime.@JsonPropertyuntuk penamaan kustom,@JsonIgnoreuntuk field yang dikecualikan,@JsonInclude(NON_NULL)untuk melewati null — tiga anotasi yang paling sering dibutuhkan.- Daftarkan
JavaTimeModuleuntuk java.time — tanpa ini,LocalDatedanLocalDateTimeakan diserialisasi sebagai array angka, bukan string ISO-8601 yang mudah dibaca.JsonNodeuntuk JSON dengan struktur dinamis — saat struktur JSON tidak diketahui atau bervariasi, navigasi denganroot.get("field"),root.at("/path/dalam"), danroot.has("field").@JsonIgnoreProperties(ignoreUnknown = true)atauFAIL_ON_UNKNOWN_PROPERTIES = false— penting untuk forward compatibility saat API menambah field baru yang belum ada di model kamu.- Gson lebih ringan, Jackson lebih lengkap — untuk proyek Spring Boot pilih Jackson; untuk Android atau proyek ringan tanpa Spring, Gson adalah pilihan yang baik.