• Stars
    star
    283
  • Rank 145,210 (Top 3 %)
  • Language
    TypeScript
  • Created over 10 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Complete implementation of RFC6902 in TypeScript

rfc6902

latest version published to npm monthly downloads from npm Travis CI build status Coverage status on Coveralls

Complete implementation of RFC6902 "JavaScript Object Notation (JSON) Patch" (including RFC6901 "JavaScript Object Notation (JSON) Pointer"), for creating and consuming application/json-patch+json documents. Also offers "diff" functionality without using Object.observe.

Demo

Simple web app using the browser-compiled version of the code.

Quickstart

Install locally

npm install --save rfc6902

Import in your script

var rfc6902 = require('rfc6902')

Calculate diff between two objects

rfc6902.createPatch({first: 'Chris'}, {first: 'Chris', last: 'Brown'})
//⇒ [ { op: 'add', path: '/last', value: 'Brown' } ]

Apply a patch to some object

var users = [{first: 'Chris', last: 'Brown', age: 20}]
rfc6902.applyPatch(users, [
  {op: 'replace', path: '/0/age', value: 21},
  {op: 'add', path: '/-', value: {first: 'Raphael', age: 37}},
])

The applyPatch function returns [null, null], indicating there were two patches, both applied successfully.

The users variable is modified in place; evaluate it to examine the end result:

users
//⇒ [ { first: 'Chris', last: 'Brown', age: 21 },
//    { first: 'Raphael', age: 37 } ]

API

In ES6 syntax:

import {applyPatch, createPatch} from 'rfc6902'

Using TypeScript annotations for clarity:

applyPatch(object: any, patch: Operation[]): Array<Error | null>

The operations in patch are applied to object in-place. Returns a list of results as long as the given patch. If all operations were successful, each item in the returned list will be null. If any of them failed, the corresponding item in the returned list will be an Error instance with descriptive .name and .message properties.

createPatch(input: any, output: any, diff?: VoidableDiff): Operation[]

Returns a list of operations (a JSON Patch) of the required operations to make input equal to output. In most cases, there is more than one way to transform an object into another. This method is more efficient than wholesale replacement, but does not always provide the optimal list of patches. It uses a simple Levenshtein-type implementation with Arrays, but it doesn't try for anything much smarter than that, so it's limited to remove, add, and replace operations.

Optional diff argument

The optional diff argument allows the user to specify a partial function that's called before the built-in diffAny function. For example, to avoid recursing into instances of a custom class, say, MyObject:

function myDiff(input: any, output: any, ptr: Pointer) {
  if ((input instanceof MyObject || output instanceof MyObject) && input != output) {
    return [{op: 'replace', path: ptr.toString(), value: output}]
  }
}
const my_patch = createPatch(input, output, myDiff)

This will short-circuit on encountering an instance of MyObject, but otherwise recurse as usual.

Operation

interface Operation {
  op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test'
  from?: string
  path?: string
  value?: string
}

Different operations use different combinations of from / value; see JSON Patch (RFC6902) below.

Changelog

I'm not going to copy & paste my relatively descriptive commit messages into groups here; rather, these are just the changes that merited major version bumps:

4.x.x → 5.0.0 (2021-12-15)

  • Short-circuits JSON pointer traversal over the prototype-polluting tokens __proto__, constructor, and prototype. I.e., /a/__proto__/b and /a/b evaluate to the same thing. This is in violation of the spec, which makes no special provisions for this idiosyncrasy of the JavaScript language, but AFAIK there's no way to strictly comply with the spec in JavaScript. It would probably be more correct to throw an error in those cases, but this 'solution' feels less disruptive / more in line with workarounds implemented by other libraries.

3.x.x → 4.0.0 (2020-07-27)

  • Potential performance regression due to consolidating separate compare(a, b): boolean and diff(a, b): Operation[] logic into basically defining compare(a, b) as !diff(a, b).length (i.e., diff(a, b) returns empty array). This simplifies the codebase and ensures underlying semantics do not diverge, but potentially does unnecessary work in computing a full diff when all we really care about is whether there is at least one difference. It also facilitates the user completely specifying custom diff functionality with just one diff function, as opposed to a diff and corresponding compare (and avoids the headache of having to propagate both of those around internally).

2.x.x → 3.0.0 (2018-09-17)

  • Corrects improper behavior in a few buggy edge cases, which might conceivably break consumers relying on incorrect behavior. (Tbh that applies to most bugfixes but I felt there were enough to add up to incrementing the major version.)
  • Also moves around some of the internal API that was not intended to be used externally, but technically was exported. If you're only importing the public API of applyPatch, createPatch, and createTests from 'rfc6902', nothing has changed.

Implementation details

Determinism

If you've ever implemented Levenshtein's algorithm, or played tricks with git rebase to get a reasonable sequence of commits, you'll realize that computing diffs is rarely deterministic. E.g., to transform the string ab → bc, you could:

  1. Delete a (⇒ b)
  2. and then append c (⇒ bc)

Or...

  1. Replace b with c (⇒ ac)
  2. and then replace a with b (⇒ bc)

Both consist of two operations, so either one is a valid solution.

Applying json-patch documents is much easier than generating them, which might explain why, when I started this project, there were more than five patch-applying RFC6902 implementations in NPM, but none for generating a patch from two distinct objects. (There was one that used Object.observe(), which only works when you're the one making the changes, and only as long as Object.observe() hasn't been deprecated, which it has.)

So when comparing your data objects, you'll want to ensure that the patches it generates meet your needs. The algorithm used by this library is not optimal, but it's more efficient than the strategy of wholesale replacing everything that's not an exact match.

Of course, this only applies to generating the patches. Applying them is deterministic and unambiguously specified by RFC6902.

JSON Pointer (RFC6901)

The RFC is a quick and easy read, but here's the gist:

  • JSON Pointer is a system for pointing to some fragment of a JSON document.
  • A pointer is a string that is composed of zero or more /reference-token parts.
    • When there are zero (the empty string), the pointer indicates the entire JSON document.
    • Otherwise, the parts are read from left to right, each one selecting part of the current document, and presenting only that fragment of the document to the next part.
  • The reference-token bits are usually Object keys, but may also be (decimal) numerals, to indicate array indices.

E.g., consider the NPM registry:

{
  "_updated": 1417985649051,
  "flickr-with-uploads": {
    "name": "flickr-with-uploads",
    "description": "Flickr API with OAuth 1.0A and uploads",
    "repository": {
      "type": "git",
      "url": "git://github.com/chbrown/flickr-with-uploads.git"
    },
    "homepage": "https://github.com/chbrown/flickr-with-uploads",
    "keywords": [
      "flickr",
      "api",
      "backup"
    ],
    ...
  },
  ...
}
  1. /_updated: this selects the value of that key, which is just a number: 1417985649051
  2. /flickr-with-uploads: This selects the entire object:
    {
      "name": "flickr-with-uploads",
      "description": "Flickr API with OAuth 1.0A and uploads",
      "repository": {
        "type": "git",
        "url": "git://github.com/chbrown/flickr-with-uploads.git"
      },
      "homepage": "https://github.com/chbrown/flickr-with-uploads",
      "keywords": [
        "flickr",
        "api",
        "backup"
      ],
      ...
    }
  3. /flickr-with-uploads/name: this effectively applies the /name pointer to the result of the previous item, which selects the string, "flickr-with-uploads".
  4. /flickr-with-uploads/keywords/1: Array indices start at 0, so this selects the second item from the keywords array, namely, "api".

Rules:

  • A pointer, if it is not empty, must always start with a slash; otherwise, it is an "Invalid pointer syntax" error.
  • If a key within the JSON document contains a forward slash character (which is totally valid JSON, but not very nice), the / in the desired key should be replaced by the escape sequence, ~1.
  • If a key within the JSON document contains a tilde (again valid JSON, but not very common), the ~ should be replaced by the other escape sequence, ~0. This allows keys containing the literal string ~1 (which is especially cruel) to be referenced by a JSON pointer (e.g., /~01 should return true when applied to the object {"~1":true}).
  • All double quotation marks, reverse slashes, and control characters must escaped, since a JSON Pointer is a JSON string.
  • A pointer that refers to a non-existent value counts as an error, too. But not necessarily as fatal as a syntax error.

Example

This project implements JSON Pointer functionality in rfc6902/pointer; e.g.:

const {Pointer} = require('rfc6902/pointer')
const repository = {
  contributors: ['chbrown', 'diachedelic', 'nathanrobinson', 'kbiedrzycki', 'stefanmaric']
}
const pointer = Pointer.fromJSON('/contributors/0')
//⇒ Pointer { tokens: [ '', 'contributors', '0' ] }
pointer.get(repository)
//⇒ 'chbrown'

JSON Patch (RFC6902)

The RFC is only 18 pages long, but here are the basics:

A JSON Patch document is a JSON document such that:

  • The MIME Type is application/json-patch+json
  • The file extension is .json-patch
  • It is an array of patch objects, potentially empty.
  • Each patch object has a key, op, with one of the following six values, and an operator-specific set of other keys.
    • add: Insert the given value at path. Or replace it, if it already exists. If the parent of the intended target does not exist, produce an error. If the final reference-token of path is "-", and the parent is an array, append value to it.
      • path: JSON Pointer
      • value: JSON object
    • remove: Remove the value at path. Produces an error if it does not exist. If path refers to an element within an array, splice it out so that subsequent elements fill in the gap (decrementing the length of the array).
      • path: JSON Pointer
    • replace: Replace the current value at path with value. It's exactly the same as performing a remove operation and then an add operation on the same path, since there must be a pre-existing value.
      • path: JSON Pointer
      • value: JSON object
    • move: Remove the value at from, and set path to that value. There must be a value at from, but not necessarily at path; it's the same as performing a remove operation, and then an add operation, but on different paths.
      • from: JSON Pointer
      • path: JSON Pointer
    • copy: Get the value at from and set path to that value. Same as move, but doesn't remove the original value.
      • from: JSON Pointer
      • path: JSON Pointer
    • test: Check that the value at path is equal to value. If it is not, the entire patch is considered to be a failure.
      • path: JSON Pointer
      • value: JSON object

License

Copyright 2014-2021 Christopher Brown. MIT Licensed.

More Repositories

1

overdrive

Bash script to download mp3s from the OverDrive audiobook service
Shell
427
star
2

liwc-python

Linguistic Inquiry and Word Count (LIWC) analyzer
Python
190
star
3

unmap

Unpack a JavaScript Source Map back into filesystem structure
JavaScript
173
star
4

macos-pasteboard

Like OS X's built-in pbpaste but more flexible and raw
Swift
89
star
5

slda

Supervised Latent Dirichlet Allocation for Classification
C++
84
star
6

flickr-with-uploads

Flickr API for Node.js using OAuth 1.0a, including upload support and featuring a CLI
JavaScript
47
star
7

osx-notifier

Send notifications to the OS X Notification Center using terminal-notifier.app
JavaScript
41
star
8

twttr

Twitter API client for Clojure supporting REST and Streaming endpoints
Clojure
35
star
9

macos-wifi

MacOS Wi-Fi (CoreWLAN) utility
Swift
34
star
10

lexicons

Lexicons for n-gram sentiment analysis
Python
20
star
11

flickr-sync

Deprecated! Use flickr-with-uploads instead
JavaScript
17
star
12

amulet

As-soon-as-possible streaming asynchronous Mustache template engine for Node.js
JavaScript
16
star
13

aclweb-data

Data from https://aclweb.org/anthology/
HTML
16
star
14

candc

C&C (Clark & Curran) Parser downloads
Python
14
star
15

nlp

NLP Homework (Spring 2013)
Java
13
star
16

twilight

Twitter Streaming API tools and data transformations for Node.js
JavaScript
13
star
17

afm

Collection of AFM (Adobe Font Metrics) specifications
TypeScript
12
star
18

BluetoothLE-HeartRate

Node.js Bluetooth Low Energy (BLE) heart rate (HR) sensor data collector
JavaScript
10
star
19

pdfi

PDF parsing, drawing, and text extraction
TypeScript
9
star
20

acl-anthology-network

Post-processing for "ACL Anthology Network" corpus (aanrelease2013)
Python
9
star
21

dropyll

Use Dropbox to edit your Jekyll website (with staging area)
JavaScript
9
star
22

macos-tags

Command line tool for manipulating OS X filesystem tags
Swift
8
star
23

autoauth

Automatic OAuth token generation from basic user account credentials
JavaScript
8
star
24

iOSpy

iOS MobileSync backup data extraction
Python
8
star
25

unidata

Javascript interface to the Unicode Character Database
JavaScript
8
star
26

chrome-unxss

Chrome extension to modify website headers on the fly
JavaScript
8
star
27

audible

Audio extraction and chapter splitting from Audible audiobooks
Shell
7
star
28

scripts

Multi-use scripts for my PATH
Python
7
star
29

fs-change

Monitor changes to specified files or directories, run arbitrary scripts in response
JavaScript
7
star
30

turk

Amazon Mechanical Turk API
TypeScript
7
star
31

config

My preferred system preferences
Shell
7
star
32

socks-server

SOCKS4/SOCKS5 proxy server
JavaScript
6
star
33

refseer

RefSeer dataset downloader
Makefile
6
star
34

openxml

openxml is a Python library to create and manipulate .docx and .pptx files
Python
6
star
35

fancy-clojure

Fancy printing — prettier than pretty
Clojure
6
star
36

pybtex

Fork of http://pybtex.sourceforge.net/
Python
6
star
37

presidents

Textual data (and scrapers) produced by the United States presidency
Jupyter Notebook
5
star
38

brew-tour

Web UI and summarizer to facilitate pruning Homebrew-installed packages
JavaScript
5
star
39

argv

Simpler command line argument parsing in Python
Python
5
star
40

tex

TeX (and BibTeX) for JavaScript!
TypeScript
5
star
41

stanford-parser

Stanford parser with sane logging
Java
5
star
42

viz

Python-powered terminal visualizations
Python
4
star
43

synology

Synology configuration notes
Shell
4
star
44

jsed

JavaScript stream editor: transform JSON via the command line
JavaScript
4
star
45

formious

Online experimentation framework (Mechanical Turk oriented)
JavaScript
4
star
46

pi

Simpler python package installation
Python
4
star
47

justext

UNMAINTAINED; use https://github.com/miso-belica/jusText instead
Python
4
star
48

bartleby

BibTeX (and TeX) parsing with Clojure
Clojure
4
star
49

sqlcmd

Incremental and immutable SQL command builder
TypeScript
4
star
50

osx-tap

Mac OS X key logger
Objective-C
3
star
51

booktool

eBook (EPUB and Audiobook) management tool
Python
3
star
52

textarea

HTML Textarea element enhancements (vanilla JavaScript; no dependencies)
TypeScript
3
star
53

aclweb

Make-driven ACL anthology downloader
JavaScript
3
star
54

BluetoothLE-Explorer

Command line explorer for Bluetooth Low Energy (BLE) devices
JavaScript
3
star
55

domlike

A better DomHandler for fb55's htmlparser2
TypeScript
3
star
56

disqust-python

Disqus API client
Python
3
star
57

xdoc-python

Python DOCX parsing. You should use xdoc instead: https://github.com/chbrown/xdoc
Python
3
star
58

to-sql

Read tabular data (Excel, csv, tsv) into PostgreSQL
TypeScript
3
star
59

xdoc

Document object manipulation
TypeScript
2
star
60

iTunesMeta

Tools for manipulating iTunes in Python
Python
2
star
61

kdd-2013-usb

Contents of the KDD 2013 USB drive
CSS
2
star
62

macos-notification

Generate plain notifications on Mac OS X from the command line
Swift
2
star
63

lexing

Regex-based lexer
TypeScript
2
star
64

ansible-wordpress

Ansible playbook for installing WordPress on a Digital Ocean droplet
PHP
2
star
65

npm-search-server

NPM registry ElasticSearch API with download counts
TypeScript
2
star
66

regex-router

Route http requests via regular expressions
TypeScript
2
star
67

jsonarea

React component for editing/validating JSON (as text)
TypeScript
2
star
68

plist-utils

Tools for manipulating files/streams in Apple's "property list" format
Shell
2
star
69

pbwatch

Polling the Mac OS X pasteboard with Python
Python
2
star
70

macos-location

Logger daemon for monitoring your macOS's location via CoreLocation updates
Swift
2
star
71

github-corpora

Tools for crawling the GitHub API and data pulled from the public API
Python
2
star
72

chicken

Port of 'Chicken of the VNC' from SourceForge
Shell
2
star
73

topic-sentiment-authorship

Topic-Sentiment Authorship
Jupyter Notebook
2
star
74

routes-clojure

URL path parsing and generation via routes data structures
Clojure
2
star
75

libpam-storepw

PAM module to store password
C
2
star
76

filesequence

Write to an indexed sequence of files using the standard Python file API
Python
2
star
77

streaming

Common stream.Transform implementations and other Node.js streaming helpers
TypeScript
2
star
78

divvy-history

Historical data for http://divvybikes.com/stations/json
Python
1
star
79

set

Javascript to display set cards through HTML5 canvas
JavaScript
1
star
80

xmltree

DOM-driven tools for XML viewing
TypeScript
1
star
81

walk-stream

Recursive filesystem walker for Node.js that implements stream.Readable
JavaScript
1
star
82

misc-js

Custom client-side JavaScript libraries for use with jQuery, Backbone, and Handlebars
JavaScript
1
star
83

cameo-twitter

Small-scale Twitter crawling and archiving
JavaScript
1
star
84

appfog-mongo-bottle

Minimum working example of connecting MongoDB and Bottle on Appfog
Python
1
star
85

photos-python

Photo management utilities implemented in Python
Python
1
star
86

npm-reallink

Publish-like 'npm link' replacement (for TypeScript development)
JavaScript
1
star
87

citation-analysis

Citation analysis tools
Python
1
star
88

npm-ui

Web UI companion to 'npm-search-server'
JavaScript
1
star
89

notify-ui

Client-side helper for displaying flash messages
TypeScript
1
star
90

sv

Any-separated values
TypeScript
1
star
91

tarry

Utility library for manipulating JavaScript Arrays
TypeScript
1
star
92

ritual

Database and API for enhancing shell history and clipboard processing
TypeScript
1
star
93

hackpad

Hackpad API client with CLI for archiving
JavaScript
1
star
94

marked-cli

Alternate CLI for Markdown interpreter, 'marked'
JavaScript
1
star
95

taskdb

Database + REST API server for managing (storing / allocating) annotation tasks
TypeScript
1
star
96

confrm

Conference Resource Management
JavaScript
1
star
97

yaml-utils

CLI tools for converting between JSON and YAML
JavaScript
1
star
98

dbml

Homework for Dana Ballard's Machine Learning course
MATLAB
1
star
99

xmlconv

XML conversion by convention
JavaScript
1
star
100

wiktionary

Tools for working with Wiktionary data
Python
1
star