🔥

[TypeScript]2つのオブジェクトの差分を取得

2023/11/20に公開1
type GenericObject = { [key: string]: any };

const obj1: GenericObject = {
  a: "a1",
  b: "b1",
};

const obj2: GenericObject = {
  a: "a1",
  b: "b2",
  c: "c1",
};

// beforeとafterの差分を取得
const getDiff = (
  before: GenericObject,
  after: GenericObject,
): GenericObject => {
  const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);

  return Array.from(allKeys).reduce<GenericObject>((diff, key) => {
    if (before[key] !== after[key]) {
      diff[key] =  key in after ? after[key] : before[key];
    }
    return diff;
  }, {});
};

console.log(getDiff(obj1, obj2)); //{ "b": "b2", "c": "c1" } 
console.log(getDiff(obj1, obj1)); // {}
console.log(getDiff(obj2, obj1)); // { "b": "b1", "c": "c1" }

// beforeに存在するキーの中でbeforeと異なるafterのプロパティを取得する
const getDiffInAfterForBeforeKeys = (
  before: GenericObject,
  after: GenericObject,
): GenericObject => {
  return Object.fromEntries(
    Object.entries(after).filter(([key, value]) =>
      key in before && before[key] !== value),
  );
};

console.log(getDiffInAfterForBeforeKeys(obj1, obj2)); //{ "b": "b2"}
console.log(getDiffInAfterForBeforeKeys(obj1, obj1)); // {}
console.log(getDiffInAfterForBeforeKeys(obj2, obj1)); // { "b": "b1"}

Discussion

nap5nap5

便利そうと思い、Deepでもいけるか、ぼくも挑戦してみました。

定義側

import { crush, construct } from 'radash'

export type GenericObject = { [key: string]: any };

// beforeとafterの差分を取得
export const getDiff = (
  before: GenericObject,
  after: GenericObject,
): GenericObject => {
  const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);

  return Array.from(allKeys).reduce<GenericObject>((diff, key) => {
    if (before[key] !== after[key]) {
      diff[key] = key in after ? after[key] : before[key];
    }
    return diff;
  }, {});
};

export type TData = Record<string, unknown>

export const getDiff2 = <Source extends TData, Destination extends TData>(
  source: Source,
  destination: Destination,
): TData => {
  const before = crush(source) as TData
  const after = crush(destination) as TData
  // @see https://github.com/sindresorhus/type-fest/blob/main/source/merge-deep.d.ts#L416-L456
  // Make your implementation ...
  const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);
  const diffData = Array.from(allKeys).reduce<TData>((diff, key) => {
    if (before[key] !== after[key]) {
      diff[key] = key in after ? after[key] : before[key];
    }
    return diff;
  }, {});
  return construct(diffData) as TData
};

使用側

import { describe, test, expect } from "vitest";
import { GenericObject, TData, getDiff, getDiff2 } from ".";

describe("original getDiff", () => {
  const obj1: GenericObject = {
    a: "a1",
    b: "b1",
  };

  const obj2: GenericObject = {
    a: "a1",
    b: "b2",
    c: "c1",
  };

  test("obj1 - obj2", () => {
    const outputData = getDiff(obj1, obj2);
    expect(outputData).toStrictEqual({ b: "b2", c: "c1" });
  });

  test("obj1 - obj1", () => {
    const outputData = getDiff(obj1, obj1);
    expect(outputData).toStrictEqual({});
  });

  test("obj2 - obj1", () => {
    const outputData = getDiff(obj2, obj1);
    expect(outputData).toStrictEqual({ b: "b1", c: "c1" });
  });
});

describe("getDiff2", () => {
  const obj1: GenericObject = {
    a: "a1",
    b: "b1",
  };

  const obj2: GenericObject = {
    a: "a1",
    b: "b2",
    c: "c1",
  };

  test("obj1 - obj2", () => {
    const outputData = getDiff2(obj1, obj2);
    expect(outputData).toStrictEqual({ b: "b2", c: "c1" });
  });

  test("obj1 - obj1", () => {
    const outputData = getDiff2(obj1, obj1);
    expect(outputData).toStrictEqual({});
  });

  test("obj2 - obj1", () => {
    const outputData = getDiff2(obj2, obj1);
    expect(outputData).toStrictEqual({ b: "b1", c: "c1" });
  });
});

describe("getDiff2 deep", () => {
  const obj1: TData = {
    a: {
      b: "@",
      c: false,
      d: [1, 2, 3],
    }
  }

  const obj2: TData = {
    a: {
      b: 111,
      d: [true, false],
    }
  }

  test("obj1 - obj2", () => {
    const outputData = getDiff2(obj1, obj2);
    expect(outputData).toStrictEqual({ a: { b: 111, c: false, d: [true, false, 3] } });
  });

  test("obj1 - obj1", () => {
    const outputData = getDiff2(obj1, obj1);
    expect(outputData).toStrictEqual({});
  });

  test("obj2 - obj1", () => {
    const outputData = getDiff2(obj2, obj1);
    expect(outputData).toStrictEqual({ a: { b: '@', c: false, d: [1, 2, 3] } });
  });
});

demo code.

https://codesandbox.io/p/sandbox/cocky-minsky-qkkwqh?file=%2Fsrc%2Findex.ts%3A1%2C1