vrlspace logo
vrlspace

Memahami dan Mengimplementasikan Compact Object dalam TypeScript


Varrel Al Jabaar K

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.

typescript
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:

typescript
const compactObject = Object.entries(obj).filter(([_, value]) => Boolean(value));
 
// Output:
// [["name", "Rel"], ["scores", [10, 0, null, 20]]]

Hasilnya:

  • name dan scores tetap ada.
  • age , isActive , dan address hilang (karena 0 , false, dan null ).

Tapi...

  • scores masih ada 0 dan null 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).

typescript
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 dari 0 dan null .

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:

typescript
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:

typescript
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)

typescript
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:

typescript
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 dibanding reduce .
  • Handle Edge Cases explicit null check dan nested object handling.
# Typescript# Javascript# Programming