Lambdaカクテル

京都在住Webエンジニアの日記です

TypeScriptのネストしたオブジェクトの内側の型を書き換える

あるオブジェクトのこのフィールドを書き換えたいことあるでしょ。

type A: {
  x: X // => Yにしたい!!!
}

まあこういうときはA2 extends Aみたいなのを宣言するか,まあいろいろやりようはあると思うんですが,これがネストしてたりするわけですよ。5重とかに。毎回extendsとか書きたくないし,そのためにimportしたりinterface宣言みたいなのは書きたくない。宣言おことわり宣言。

そこでこういうのを作りました。先日のやつの焼き直しです。

// Tオブジェクトのプロパティキーのうち,Kを除いたものを返す。
//  NotKeys<{ x: X, y: Y, z: Z }, "y"> === "x" | "z" になる
type NotKeys<T, K> = {
    [P in keyof T]: P extends K ? never : P;
}[keyof T];

// TオブジェクトからKプロパティを落とす型
// type OmitKey<T, K> = Pick<T, Extract<keyof T, NotKeys<T, K>>>;
// 良く見たらExtract不要だった
type OmitKey<T, K> = Pick<T, NotKeys<T, K>>;

// TオブジェクトからKキーを落としてから,Kキーの型がTTになっているオブジェクト型を作って,合体させる
type Modified<T, K extends string, TT> = OmitKey<T, K> & Record<K, TT>

type X = string;
type X2 = boolean;
type Y = number;

interface A {
  x: X, y: Y
};

interface B {
  x: X, y: Y
}

interface T {
  a: A, b: B
}

type T2 = Modified<T, "a", Modified<A, "x", X2>>

const t: T = {
  a: {
    x: "123",
    y: 123
  },
  b: {
    x: "123",
    y: 123
  }
}

const t2: T2 = {
  a: {
    x: true,
    y: 123
  },
  b: {
    x: "123",
    y: 123
  }
}

これのイケてるところは,ネストしていても内部の型を書き換えられることです。

これのイケてないところは,結局A型の名前が出てきてしまうので,import A不可避ということです。T型からKキーで取り出したプロパティの型は自明なので次のModifiedに渡してやりたいのですが,複数の型引数を取る型に型を1つだけ渡す(型のcurrying),みたいなことができないっぽいので諦めます。 僕は型初心者なのでどうしたらよいかわからないです。

参考

blog.3qe.us