{"id":15396234,"url":"https://github.com/marcolink/generate-json-patch","last_synced_at":"2026-03-01T16:02:12.800Z","repository":{"id":181159008,"uuid":"666304097","full_name":"marcolink/generate-json-patch","owner":"marcolink","description":"A simple function to diff any object and generate a JSON Patch","archived":false,"fork":false,"pushed_at":"2026-02-12T11:10:51.000Z","size":280,"stargazers_count":13,"open_issues_count":1,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-12T18:13:07.538Z","etag":null,"topics":["generator","javascript","json","jsonpatch","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcolink.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-07-14T07:25:29.000Z","updated_at":"2026-02-12T11:10:53.000Z","dependencies_parsed_at":null,"dependency_job_id":"dc920edf-e1f2-451d-810b-bbf272f9e530","html_url":"https://github.com/marcolink/generate-json-patch","commit_stats":null,"previous_names":["marcolink/generate-json-patch"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/marcolink/generate-json-patch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fgenerate-json-patch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fgenerate-json-patch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fgenerate-json-patch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fgenerate-json-patch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcolink","download_url":"https://codeload.github.com/marcolink/generate-json-patch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fgenerate-json-patch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29974321,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T15:41:30.362Z","status":"ssl_error","status_checked_at":"2026-03-01T15:37:07.343Z","response_time":124,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["generator","javascript","json","jsonpatch","typescript"],"created_at":"2024-10-01T15:32:27.756Z","updated_at":"2026-03-01T16:02:12.793Z","avatar_url":"https://github.com/marcolink.png","language":"TypeScript","readme":"# generate-json-patch\n\nCreate [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902/) compliant JSON Patch objects based on two given [JSON](https://www.ecma-international.org/publications-and-standards/standards/ecma-404/) objects with a configurable interface. \n\n[![Version](https://img.shields.io/npm/v/generate-json-patch.svg)](https://npmjs.org/package/generate-json-patch)\n[![Downloads/week](https://img.shields.io/npm/dw/generate-json-patch.svg)](https://npmjs.org/package/generate-json-patch)\n[![Size](https://img.shields.io/bundlephobia/min/generate-json-patch.svg)](https://npmjs.org/package/generate-json-patch)\n[![Tests](https://github.com/marcolink/generate-json-patch/workflows/CI%20Tests/badge.svg?branch=main)](https://github.com/marcolink/generate-json-patch/actions/workflows/test.yml)\n[![License](https://img.shields.io/npm/l/generate-json-patch.svg)](https://github.com/marcoxlink/generate-json-patch/blob/main/package.json)\n[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)\n# TL;DR\n- Can diff any two [JSON](https://www.ecma-international.org/publications-and-standards/standards/ecma-404/)  compliant objects - returns differences as [JSON Patch](http://jsonpatch.com/).\n- Elegant array diffing by providing an `objectHash` to match array elements\n- Ignore specific keys by providing a `propertyFilter`\n- LCS-based move detection (or disable moves with `array.ignoreMove`)\n- Limit traversal with `maxDepth` to collapse deep trees into a single replace\n- :paw_prints: ***Is it small?*** Zero dependencies - it's ~**3 KB** (minified).\n- Ships ESM + CJS builds with types\n- :crystal_ball: ***Is it fast?*** I haven't done any performance comparison yet.\n- The interface is inspired by [jsondiffpatch](https://github.com/benjamine/jsondiffpatch)\n- **100%** Typescript\n\n# Installation \nWorks on node and browser environments. \n```bash\nnpm install generate-json-patch\n```\n\n# Usage\n\n```typescript\nimport { generateJSONPatch } from 'generate-json-patch';\n\nconst before = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1972,\n  colors: ['red', 'silver', 'yellow'],\n  engine: [\n    { name: 'Cologne V6 2.6', hp: 125 },\n    { name: 'Cologne V6 2.0', hp: 90 },\n    { name: 'Cologne V6 2.3', hp: 108 },\n    { name: 'Essex V6 3.0', hp: 138 },\n  ],\n};\n\nconst after = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1974,\n  colors: ['red', 'silver', 'yellow'],\n  engine: [\n    { name: 'Essex V6 3.0', hp: 138 },\n    { name: 'Cologne V6 2.6', hp: 125 },\n    { name: 'Cologne V6 2.3', hp: 108 },\n    { name: 'Cologne V6 2.0', hp: 90 },\n  ],\n};\n\nconst patch = generateJSONPatch(before, after);\n\nconsole.log(patch);\n// [\n//   { op: 'replace', path: '/year', value: 1974 },\n//   { op: 'move', from: '/engine/3', path: '/engine/0' },\n// ]\n```\n\n## Configuration\n\n`generateJSONPatch(before, after, config?)` accepts the options below. The examples reuse the same payload shown in the Usage section.\n\n### `objectHash`\n\nMatch array elements by a stable hash instead of position. Useful to detect moves and edits for arrays of objects.\n\n```typescript\nimport { generateJSONPatch, type JsonValue, type ObjectHashContext, pathInfo } from 'generate-json-patch';\n\nconst before = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1972,\n  colors: ['red', 'silver', 'yellow'],\n  engine: [\n    { name: 'Cologne V6 2.6', hp: 125 },\n    { name: 'Cologne V6 2.0', hp: 90 },\n    { name: 'Cologne V6 2.3', hp: 108 },\n    { name: 'Essex V6 3.0', hp: 138 },\n  ],\n};\n\nconst after = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1974,\n  colors: ['red', 'silver', 'yellow'],\n  engine: [\n    { name: 'Essex V6 3.0', hp: 138 },\n    { name: 'Cologne V6 2.6', hp: 125 },\n    { name: 'Cologne V6 2.3', hp: 108 },\n    { name: 'Cologne V6 2.0', hp: 90 },\n  ],\n};\n\nconst patch = generateJSONPatch(before, after, {\n  objectHash(value: JsonValue, context: ObjectHashContext) {\n    const { length, last } = pathInfo(context.path);\n    if (length === 2 \u0026\u0026 last === 'engine') {\n      // keep engine comparisons stable by model name\n      // @ts-expect-error JsonValue does not guarantee shape\n      return value?.name;\n    }\n    // default to position for other arrays\n    return context.index.toString();\n  },\n});\n\nconsole.log(patch);\n// [\n//   { op: 'replace', path: '/year', value: 1974 },\n//   { op: 'move', from: '/engine/3', path: '/engine/0' },\n// ]\n```\n\n### `propertyFilter`\n\nSkip properties when diffing. Return `false` to ignore a field.\n\n```typescript\nconst before = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1972,\n  vin: 'secret-123',\n};\n\nconst after = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1974,\n  vin: 'secret-456',\n};\n\nconst patch = generateJSONPatch(before, after, {\n  propertyFilter(propertyName) {\n    return propertyName !== 'vin';\n  },\n});\n\nconsole.log(patch);\n// [\n//   { op: 'replace', path: '/year', value: 1974 }\n// ]\n```\n\n### `array.ignoreMove`\n\nPrevent move operations if order does not matter to you. The resulting patch will not reorder arrays.\n\n```typescript\nconst before = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1972,\n  engine: [\n    { name: 'Cologne V6 2.6', hp: 125 },\n    { name: 'Cologne V6 2.0', hp: 90 },\n    { name: 'Cologne V6 2.3', hp: 108 },\n    { name: 'Essex V6 3.0', hp: 138 },\n  ],\n};\n\nconst after = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1972,\n  engine: [\n    { name: 'Essex V6 3.0', hp: 138 },\n    { name: 'Cologne V6 2.6', hp: 125 },\n    { name: 'Cologne V6 2.3', hp: 108 },\n    { name: 'Cologne V6 2.0', hp: 90 },\n  ],\n};\n\nconst unorderedPatch = generateJSONPatch(before, after, {\n  objectHash: (value: any) =\u003e value.name,\n  array: { ignoreMove: true },\n});\n\nconsole.log(unorderedPatch);\n// []\n```\n\n### `maxDepth`\n\nStop descending deeper than a given path depth. When the limit is reached, a `replace` is emitted for that subtree.\n\n```typescript\nconst before = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1972,\n  specs: {\n    trim: 'Base',\n    colorOptions: ['red', 'silver', 'yellow'],\n  },\n};\n\nconst after = {\n  manufacturer: 'Ford',\n  model: 'Granada',\n  year: 1974,\n  specs: {\n    trim: 'Ghia',\n    colorOptions: ['red', 'silver', 'yellow'],\n  },\n};\n\nconst patch = generateJSONPatch(before, after, { maxDepth: 2 });\n\nconsole.log(patch);\n// [\n//   {\n//     op: 'replace',\n//     path: '/specs',\n//     value: { trim: 'Ghia', colorOptions: ['red', 'silver', 'yellow'] },\n//   },\n//   { op: 'replace', path: '/year', value: 1974 },\n// ]\n```\n\n### Patch Context\n\nBoth config functions (`objectHash`, `propertyFilter`) receive a context as the second parameter to drive fine-grained decisions:\n\n- `side`: `'left' | 'right'` indicating the value being inspected\n- `path`: JSON Pointer-style path to the current value\n- `index`: only on `objectHash`, giving the array index being processed\n\nSee the `objectHash` example above for how `pathInfo` can be combined with the context to scope hashing logic.\n\n### How moves are found (Longest Common Subsequence)\n\nWhen `ignoreMove` is `false`, array reorders emit move operations instead of delete/add pairs. We minimize moves by:\n\n1. Hashing array elements with `objectHash` to get stable identifiers.\n2. Computing the **[Longest Common Subsequence (LCS)](https://en.wikipedia.org/wiki/Longest_common_subsequence)** between the current order and the target order. The LCS represents items that stay in place.\n3. Walking the target order and moving only the out-of-place items, keeping LCS items anchored. This yields the smallest set of `{ op: 'move', from, path }` operations needed to reach the target sequence.\n\nThis is implemented in `move-operations.ts` (`longestCommonSequence` + `moveOperations`) and is exercised in the tests in `src/move-operations.spec.ts`.\n\n\u003e For more examples, check out the [tests](./src/index.spec.ts)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcolink%2Fgenerate-json-patch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcolink%2Fgenerate-json-patch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcolink%2Fgenerate-json-patch/lists"}