Skip to content

Instantly share code, notes, and snippets.

@farhad-taran
Last active March 23, 2022 22:35
Show Gist options
  • Save farhad-taran/c8afa75c4b5c31af172f8d55551c54c2 to your computer and use it in GitHub Desktop.
Save farhad-taran/c8afa75c4b5c31af172f8d55551c54c2 to your computer and use it in GitHub Desktop.
HTTP PATCH implementation in typescript

the HTTP sepecification for the PATCH method describes it as a more selective way of updating a resource. this can be easily implemented in typescript or javascript as demonstrated below:

export default function patchObjects(original: any, patch: any) {
  const originalCopy = { ...original };
  Object.keys(patch).forEach((key) => {
    const matchingOriginalProp = originalCopy[key];
    if (typeof matchingOriginalProp === 'object') {
      originalCopy[key] = patchObjects(matchingOriginalProp, patch[key]);
    } else {
      originalCopy[key] = patch[key];
    }
  });
  return originalCopy;
}

Tests:

import patchObjects from './object.helpers';

type TestDataType = {
  name: string;
  details: {
    age: number;
    attributes: {
      height: number;
      weight: number;
      eyeColor: string;
    };
  };
};

describe('PatchObjects', () => {
  it('replaces fields that have been passed in to be patched', () => {
    const original: TestDataType = {
      name: 'xxx',
      details: {
        age: 1,
        attributes: {
          height: 111,
          weight: 222,
          eyeColor: 'blue',
        },
      },
    };

    const patchData = {
      details: {
        attributes: {
          weight: 666,
        },
      },
    };

    const actual = patchObjects(original, patchData);

    expect(actual).toEqual({
      name: 'xxx',
      details: {
        age: 1,
        attributes: {
          height: 111,
          weight: 666,
          eyeColor: 'blue',
        },
      },
    });
  });

  it('does not replace fields if they are not in patch', () => {
    const original: TestDataType = {
      name: 'xxx',
      details: {
        age: 1,
        attributes: {
          height: 111,
          weight: 222,
          eyeColor: 'blue',
        },
      },
    };

    const patchData = {};

    const actual = patchObjects(original, patchData);

    expect(actual).toEqual(original);
  });

  it('adds fields in if they are in patch', () => {
    const original = {
      details: {
        age: 1,
        attributes: {
          height: 111,
          weight: 222,
        },
      },
    };

    const patchData = {
      details: {
        name: 'john',
        attributes: {
          color: 'grey',
        },
      },
    };

    const actual = patchObjects(original, patchData);

    expect(actual).toEqual({
      details: {
        name: 'john',
        age: 1,
        attributes: {
          color: 'grey',
          height: 111,
          weight: 222,
        },
      },
    });
  });

  it('only replaces fields with undefined if they are explicitly being patched', () => {
    const original: TestDataType = {
      name: 'xxx',
      details: {
        age: 1,
        attributes: {
          height: 111,
          weight: 222,
          eyeColor: 'blue',
        },
      },
    };

    const patchData = {
      details: {
        age: 1,
        attributes: {
          eyeColor: undefined,
        },
      },
    };

    const actual = patchObjects(original, patchData);

    expect(actual).toEqual({
      name: 'xxx',
      details: {
        age: 1,
        attributes: {
          height: 111,
          weight: 222,
          eyeColor: undefined,
        },
      },
    });
  });
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment