npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rfc6902-mobx

v2.2.2

Published

Complete implementation of RFC6902 (patch and diff)

Downloads

6

Readme

rfc6902

npm version Travis CI Build Status Coverage Status

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.

Quickstart

npm install --save rfc6902

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}},
]);

Now the value of users is:

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

Create tests for a patch and a given object:

var obj = {flavors: ['apple', 'banana', 'cherry']};
rfc6902.createTests(obj, [
  {op: 'remove', path: '/flavors/1'}
]);

[{op: 'test', path: '/flavors/1', value: 'banana'}]

API

rfc6902 exports three methods, no default. So in ES6 syntax, that would be:

import {applyPatch, createPatch, createTests} from 'rfc6902';

Using TypeScript annotations:

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

    The operations in patch are applied to object in-place, and it returns a list of results. The returned list will have the same length as 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): 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.

  • interface Operation { op: string; from?: string; path?: string; value?: string; }

Demo

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

Determinism

If you've ever implemented Levenshtein's algorithm, or played tricks with git stash to get a reasonable sequence of commits, you'll realize that computing diffs is rarely deterministic.

Applying json-patch documents is way 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 completely specified by RFC6902.

Tutorial

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 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.

JSON Patch (RFC6902)

The RFC is only 18 pages long, and pretty straightforward, 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 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, and the length of the array is decremented by 1.
      • 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, 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.
      • from: JSON Pointer
      • path: JSON Pointer
    • copy: Get the value at from and set path to that value. Same as move, but don'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-2016 Christopher Brown. MIT Licensed.