ts-data-forge
    Preparing search index...

    Function replaceEqualDeep

    • Returns next with every part deeply equal to the same part of current replaced by that part of current (structural sharing). When the whole of next is deeply equal to current, current itself is returned, so an Object.is comparison of the result with current tells whether anything changed, and a reader that compares by reference sees a new object only where the value really changed.

      Only plain objects and arrays are walked into. Anything else — a Map, a Set, a Date, a class instance — is compared whole with fastDeepEqual and kept or replaced whole.

      Array items are matched by index. Give key to match them by key instead: an item for which it returns something other than undefined is matched with the item of current it returns the same key for, and an item it returns undefined for is still matched by index. The one key is used for the arrays at every depth.

      Neither argument is changed. The result shares objects with both, so it relies on neither being changed afterwards.

      Call it where the new value is written over the one held, so that nothing downstream has a new object it must compare again.

      Type Parameters

      • T

      Parameters

      • current: T

        The value held now

      • next: T

        The value to write over it

      • Optionaloptions: Readonly<{ key?: (item: unknown) => unknown }>

      Returns T

      current when next is deeply equal to it; otherwise a value deeply equal to next that reuses the equal parts of current

      type PullReport = DeepReadonly<{
      summary: { open: number };
      pulls: { number: number; title: string }[];
      }>;

      const held: PullReport = {
      summary: { open: 2 },
      pulls: [
      { number: 1, title: 'one' },
      { number: 2, title: 'two' },
      ],
      } as const;

      // A server answers with the whole report every time.
      const answer: PullReport = {
      summary: { open: 2 },
      pulls: [
      { number: 1, title: 'one' },
      { number: 2, title: 'two (edited)' },
      ],
      } as const;

      const written = replaceEqualDeep(held, answer);

      assert.deepStrictEqual(written, answer);

      // What did not change is the object already held,
      assert.strictEqual(written.summary, held.summary);

      assert.strictEqual(written.pulls[0], held.pulls[0]);

      // and what changed is new.
      assert.notStrictEqual(written.pulls[1], held.pulls[1]);

      // An answer that changes nothing gives back the value held.
      assert.strictEqual(replaceEqualDeep(held, structuredClone(held)), held);

      // Match array items by key rather than by index.
      const opened = replaceEqualDeep(
      held.pulls,
      [{ number: 0, title: 'zero' }, ...structuredClone(held.pulls)],
      {
      key: (item) =>
      isRecord(item) && hasKey(item, 'number') ? item.number : undefined,
      },
      );

      assert.strictEqual(opened[1], held.pulls[0]);

      assert.strictEqual(opened[2], held.pulls[1]);