ネストしたオブジェクト型とは何か(まずはイメージから)
ネストしたオブジェクト型は、「オブジェクトの中に、さらにオブジェクトが入っている形を、そのまま型で表現したもの」です。
現実のデータって、ユーザーの中に住所があったり、住所の中に国や市があったりと、階層構造になりがちですよね。
TypeScript では、その階層構造をそのまま「型」として書けます。
const user = {
name: "Taro",
address: {
country: "Japan",
city: "Tokyo",
},
};
TypeScriptこの { ... } の形を、きちんと型として表現しておくと、
「country を書き忘れた」「city に number を入れた」みたいなミスをコンパイル時に止められるようになります。
一番基本の書き方:中にそのままオブジェクト型を書く
interface でネストしたオブジェクト型を書く
interface Person {
name: string;
address: {
country: string;
city: string;
};
}
const person: Person = {
name: "Taro",
address: {
country: "Japan",
city: "Tokyo",
},
};
TypeScriptここでやっていることはシンプルで、address プロパティの型として、
さらに { country: string; city: string } というオブジェクト型を書いているだけです。
TypeScript はこう理解します。
nameは stringaddressは「country: stringとcity: stringを持つオブジェクト」
だから、次のようなコードはエラーになります。
const badPerson: Person = {
name: "Taro",
address: {
country: "Japan",
// city がない → エラー
},
};
TypeScript「ネストしていても、結局は“プロパティ名: 型”の組み合わせが入れ子になっているだけ」
この感覚を持てると、一気に怖くなくなります。
type エイリアスでもまったく同じことができる
type Address = {
country: string;
city: string;
};
type Person = {
name: string;
address: Address;
};
TypeScriptaddress の型として、別で定義した Address 型を使っています。interface でも type でも、ネストの表現の仕方は同じです。
ネストを分割して書くか、その場で書くか
その場で書くパターン(小さいとき・一度きりのとき)
type User = {
id: number;
profile: {
nickname: string;
bio: string;
};
};
TypeScript「profile はここでしか使わないし、そんなに大きくない」
こういうときは、その場で { ... } と書いてしまっても読みやすいです。
分割して名前をつけるパターン(再利用したいとき・大きくなってきたとき)
type Profile = {
nickname: string;
bio: string;
};
type User = {
id: number;
profile: Profile;
};
TypeScriptProfile を別名として切り出すことで、
- 他の型でも
Profileを再利用できる - 「ここにはプロフィールが入るんだな」と意味が伝わりやすくなる
というメリットが出てきます。
「ネストが深くなってきた」「同じ形があちこちに出てきた」と感じたら、
「一段切り出して名前をつける」のが良いタイミングです。
ネストしたオブジェクト型と optional / readonly
ネストの中にも optional を使える
type Address = {
country: string;
city: string;
building?: string;
};
type User = {
name: string;
address?: Address;
};
TypeScriptここでは、
building?は「建物名はあってもなくてもいい」address?は「住所自体がないユーザーもいる」
という仕様を、そのまま型にしています。
実際の値としては、こんなパターンが全部 OK です。
const u1: User = { name: "Taro" };
const u2: User = {
name: "Hanako",
address: {
country: "Japan",
city: "Osaka",
},
};
const u3: User = {
name: "Ken",
address: {
country: "Japan",
city: "Nagoya",
building: "Some Building 101",
},
};
TypeScriptネストしたオブジェクトでも、「どの階層のどのプロパティが必須で、どこが任意か」を細かく設計できます。
ネストの中にも readonly を使える
type Address = {
readonly country: string;
city: string;
};
type User = {
readonly id: number;
name: string;
address: Address;
};
TypeScriptここでは、
idは一度決めたら変えないcountryも一度決めたら変えない(国が変わることはない想定)cityやnameは変わってもよい
というルールを型で表現しています。
ネストしていても、「どこを固定して、どこを変えていいか」をプロパティ単位でコントロールできます。
ネストが深くなったときに意識してほしいこと
「階層ごとに“意味のある塊”として型を切る」
たとえば、こんなデータ構造を考えてみます。
const data = {
user: {
id: 1,
name: "Taro",
address: {
country: "Japan",
city: "Tokyo",
},
},
meta: {
createdAt: "2025-01-01",
updatedAt: "2025-01-02",
},
};
TypeScriptこれをそのまま1つの型で書くこともできますが、
読みやすさと再利用性を考えると、階層ごとに分けた方がきれいです。
type Address = {
country: string;
city: string;
};
type User = {
id: number;
name: string;
address: Address;
};
type Meta = {
createdAt: string;
updatedAt: string;
};
type Response = {
user: User;
meta: Meta;
};
TypeScriptこうしておくと、
Userだけを別の関数の引数に使えるMetaを他のレスポンス型でも使い回せる- 「この型は何を表しているのか」が名前から伝わる
という状態になります。
ネストしたオブジェクト型は、「全部を1行で書く」ものではなく、
「意味のある階層ごとに分けて、名前をつけていく」ための土台だと捉えてほしい。
初心者がまず掴んでおきたい「ネストしたオブジェクト型」の感覚
ネストしたオブジェクト型の本質は、とてもシンプルです。
- オブジェクトの中に、さらに「プロパティ名: 型」の組み合わせが入っているだけ
- それをそのまま
{ ... }で書くか、別の型として切り出して名前をつけるかの違いだけ - optional や readonly も、ネストの中で普通に使える
だから、まずはこう考えてみてください。
「このデータ、現実世界ではどういう“入れ子構造”になっている?」
「ユーザーの中に住所があって、住所の中に国と市があるよね」
その構造を、そのまま型にしていく。
途中で「この塊には名前をつけた方が分かりやすいな」と思ったら、type や interface に切り出す。
そうやって少しずつ「構造を型に写し取る」感覚が育ってくると、
ネストしたオブジェクト型は、ただの記法ではなく、
「現実のデータ構造をそのまま安全に扱うための設計図」として見えてきます。
