ネストしたオブジェクトの操作

実際のアプリケーションでは、オブジェクトがネスト(入れ子)していることがほとんどです。ネストしたオブジェクトを安全に読み書きする方法を理解しておくことが重要です。

ネストしたプロパティへのアクセス

ドット記法やブラケット記法を連鎖させることで、ネストしたプロパティにアクセスできます。

const user = {
  name: "田中",
  address: {
    city: "東京",
    zip: "100-0001"
  }
};

console.log(user.address.city);    // "東京"
console.log(user["address"]["zip"]); // "100-0001"

存在しないプロパティへのアクセス

途中のプロパティが存在しない場合、エラーが発生します。

const user = { name: "田中" };

// user.address は undefined
console.log(user.address.city); // TypeError: Cannot read property 'city' of undefined

オプショナルチェイニング(?.)

ES2020 で導入されたオプショナルチェイニングを使うと、途中が undefined や null でもエラーにならず、undefined を返します。

const user = { name: "田中" };

console.log(user.address?.city);           // undefined(エラーにならない)
console.log(user.address?.city ?? "不明");  // "不明"(デフォルト値)

深くネストした構造でも安全にアクセスできます。

const data = {
  users: [{
    profile: { avatar: { url: "https://example.com/img.png" } }
  }]
};

console.log(data.users?.[0]?.profile?.avatar?.url);
// "https://example.com/img.png"

console.log(data.users?.[1]?.profile?.avatar?.url);
// undefined

ネストしたプロパティの更新

ネストしたプロパティを更新する場合、元のオブジェクトを変更する方法と、イミュータブルに新しいオブジェクトを作る方法があります。

ミュータブル(直接変更)

元のオブジェクトを直接変更する。シンプルだが副作用がある

イミュータブル(新規作成)

新しいオブジェクトを作成する。React などで推奨される

const user = {
  name: "田中",
  address: { city: "東京" }
};

// ミュータブルな更新
user.address.city = "大阪";

// イミュータブルな更新(スプレッド構文)
const updated = {
  ...user,
  address: {
    ...user.address,
    city: "大阪"
  }
};

動的なパスでのアクセス

プロパティのパスが動的に決まる場合、パス文字列を分割してアクセスする関数が便利です。

function getNestedValue(obj, path) {
  return path.split('.').reduce((current, key) => current?.[key], obj);
}

const data = {
  user: {
    profile: {
      name: "田中"
    }
  }
};

console.log(getNestedValue(data, "user.profile.name")); // "田中"
console.log(getNestedValue(data, "user.settings.theme")); // undefined

ネストしたプロパティの設定

動的なパスで値を設定する関数も作れます。

function setNestedValue(obj, path, value) {
  const keys = path.split('.');
  const lastKey = keys.pop();
  
  const target = keys.reduce((current, key) => {
    if (!(key in current)) {
      current[key] = {};
    }
    return current[key];
  }, obj);
  
  target[lastKey] = value;
  return obj;
}

const data = {};
setNestedValue(data, "user.profile.name", "田中");

console.log(data);
// { user: { profile: { name: "田中" } } }

フラット化

ネストしたオブジェクトをフラットなキーと値のペアに変換することもできます。

function flatten(obj, prefix = "") {
  return Object.entries(obj).reduce((acc, [key, value]) => {
    const newKey = prefix ? `${prefix}.${key}` : key;
    
    if (typeof value === "object" && value !== null && !Array.isArray(value)) {
      Object.assign(acc, flatten(value, newKey));
    } else {
      acc[newKey] = value;
    }
    return acc;
  }, {});
}

const nested = { a: { b: { c: 1 } }, d: 2 };
console.log(flatten(nested));
// { "a.b.c": 1, "d": 2 }

ネストしたオブジェクトの操作は、オプショナルチェイニングとスプレッド構文を活用することで、安全かつ簡潔に書けるようになります。