Memahami dan Mengimplementasikan Compact Object dalam TypeScript

Varrel Al Jabaar K
Apa Itu Compact Object?
Compact Object adalah proses menghilangkan semua nilai falsy dari sebuah objek atau array. Ini berguna ketika kita ingin memastikan data yang diproses hanya berisi nilai-nilai yang valid sebelum disimpan atau ditampilkan.
const obj = {
name: "Rel",
age: 0, // falsy (0)
isActive: false, // falsy
address: null, // falsy
scores: [10, 0, null, 20], // index ke 1[0] dan 2[null] falsy
};
const compacted = compactObject(obj);
/* Hasil:
{
name: "Rel",
scores: [10, 20]
}
*/
Implementasi Compact Object dalam TypeScript
Percobaan Pertama: Filter Sederhana
Pertama aku coba pake filter
biasa:
const compactObject = Object.entries(obj).filter(([_, value]) => Boolean(value));
// Output:
// [["name", "Rel"], ["scores", [10, 0, null, 20]]]
Hasilnya:
name
danscores
tetap ada.age
,isActive
, danaddress
hilang (karena0
,false
, dannull
).
Tapi...
scores
masih ada0
dannull
di dalam array.
Problem: Hanya ngefilter di level terluar doang.
Percobaan Kedua: Recursive Funtion untuk Nested Object
Setelah googling, ketemu konsep Recursive Funtion (fungsi yang manggil dirinya sendiri).
function compactObject(obj: any): any {
// 1. Handle jika input adalah array
if (Array.isArray(obj)) {
// Filter nilai falsy dari array, lalu rekursif bersihkan tiap elemen
return obj
.filter(Boolean) // Hapus nilai falsy (false, 0, "", null, undefined)
.map(compactObject); // Bersihkan tiap elemen yang tersisa secara rekursif
}
// 2. Handle jika input adalah object (bukan array dan bukan null)
if (typeof obj === "object" && obj !== null) {
const result: any = {}; // Buat object baru untuk hasil
// Loop melalui semua key-value pair dalam object
for (const [key, value] of Object.entries(obj)) {
// Jika nilai tidak falsy, masukkan ke object hasil
if (value) {
// Rekursif bersihkan nilai jika berupa object/array
result[key] = compactObject(value);
}
// Nilai falsy diabaikan (tidak dimasukkan ke result)
}
return result; // Kembalikan object yang sudah dibersihkan
}
// 3. Handle nilai primitif (number, string, boolean, dll)
// Langsung kembalikan nilai aslinya karena tidak bisa difilter lebih lanjut
return obj;
}
Hasilnya:
scores
sekarang bersih dari0
dannull
.
Tapi...
- ⚠️ TypeScript marah karena pake
any
. - ⚠️ Kalau ada
null
di nested object, masih kurang optimal.
Percobaan Ketiga: Bikin Lebih Type-Safe
Bikin type khusus buat data JSON:
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
function compactObject(obj: JSONValue): JSONValue {
if (obj === null) return null;
if (Array.isArray(obj)) {
return obj.filter(Boolean).map(compactObject);
}
if (typeof obj === "object") {
const result: { [key: string]: JSONValue } = {};
for (const [key, value] of Object.entries(obj)) {
if (value) {
result[key] = compactObject(value);
}
}
return result;
}
return obj;
}
Perbaikan:
- Type lebih aman.
- Handle
null
secara eksplisit.
Masalah Baru: Object Asli Ikut Berubah!
Ternyata, kode di atas bisa bikin object asli berubah kalau kita modify langsung. Harusnya bikin object baru.
Solusi:
function compactObject(obj: JSONValue): JSONValue {
if (obj === null) return null;
if (Array.isArray(obj)) {
return obj.filter(Boolean).map(compactObject);
}
if (typeof obj === "object") {
// Gunakan reduce karena bisa membantu akumulasi ke objek baru
return Object.entries(obj).reduce(
(acc, [key, value]) => {
if (value) {
acc[key] = compactObject(value);
}
return acc;
},
// Inisialisasi OBJEK BARU {}
{} as { [key: string]: JSONValue },
);
}
return obj;
}
Sekarang sudah immutable nggak mengubah data asli.
3. Versi Hybrid (Type-Safe + Immutable + Cleaner Code)
function compactObject(obj: JSONValue): JSONValue {
if (obj === null) return null;
if (Array.isArray(obj)) {
return obj.filter(Boolean).map(compactObject);
}
if (typeof obj === "object") {
return Object.entries(obj).reduce<Record<string, JSONValue>>(
(acc, [key, value]) => {
if (value) {
acc[key] = compactObject(value);
}
return acc;
},
{}, // Tidak perlu type assertion jika generic `reduce<T>` dipakai
);
}
// Handle primitive values (string, number, boolean)
return obj;
}
Kelebihan:
- Type-safe pakai generic
reduce<Record<string, JSONValue>>
. - Immutable selalu buat objek/array baru.
- Cleaner tidak perlu
as
type assertion. - Explicit null handling.
Final Recommendation:
function compactObject(obj: JSONValue): JSONValue {
if (obj === null) return null;
if (Array.isArray(obj)) {
return (
obj
// Langkah 1: Filter semua nilai falsy dari array
.filter(Boolean)
// Langkah 2: Bersihkan tiap elemen yang tersisa dengan map
.map(
(item) =>
typeof item === "object" && item !== null
? compactObject(item) // Rekursif jika elemen adalah object/array
: item, // Kalau bukan biarkan nilai primitif tetap apa adanya
)
);
}
// Handle jika input adalah object biasa
else {
// Buat object baru untuk menyimpan hasil (pendekatan immutable)
const result: Record<string, JSONValue> = {};
// Loop melalui semua key-value pair dalam object
for (const [key, value] of Object.entries(obj)) {
// Hanya proses nilai yang truthy
if (value) {
// Jika nilai adalah object, bersihkan secara rekursif
result[key] =
typeof value === "object"
? compactObject(value) // Rekursif untuk nested object/array
: value; // kalau bukan biarkan nilai primitif
}
// Nilai falsy di-skip (tidak dimasukkan ke result)
}
return result; // Kembalikan object baru yang sudah dibersihkan
}
}
Kenapa Kode Ini Bagus?
- Immutability tidak pernah modifikasi input asli, selalu buat data baru.
- Type Safety menggunakan type
JSONValue
. - Efisien
for
loop lebih cepat untuk object dibandingreduce
. - Handle Edge Cases explicit null check dan nested object handling.