jiff
Version:
JSON diff and patch based on rfc6902
162 lines (136 loc) • 4.18 kB
JavaScript
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
var _parse = require('./jsonPointerParse');
exports.find = find;
exports.join = join;
exports.absolute = absolute;
exports.parse = parse;
exports.contains = contains;
exports.encodeSegment = encodeSegment;
exports.decodeSegment = decodeSegment;
exports.parseArrayIndex = parseArrayIndex;
exports.isValidArrayIndex = isValidArrayIndex;
// http://tools.ietf.org/html/rfc6901#page-2
var separator = '/';
var separatorRx = /\//g;
var encodedSeparator = '~1';
var encodedSeparatorRx = /~1/g;
var escapeChar = '~';
var escapeRx = /~/g;
var encodedEscape = '~0';
var encodedEscapeRx = /~0/g;
/**
* Find the parent of the specified path in x and return a descriptor
* containing the parent and a key. If the parent does not exist in x,
* return undefined, instead.
* @param {object|array} x object or array in which to search
* @param {string} path JSON Pointer string (encoded)
* @param {?function(index:Number, array:Array, context:object):Number} findContext
* optional function used adjust array indexes for smarty/fuzzy patching, for
* patches containing context. If provided, context MUST also be provided.
* @param {?{before:Array, after:Array}} context optional patch context for
* findContext to use to adjust array indices. If provided, findContext MUST
* also be provided.
* @returns {{target:object|array|number|string, key:string}|undefined}
*/
function find(x, path, findContext, context) {
if(typeof path !== 'string') {
return;
}
if(path === '') {
// whole document
return { target: x, key: void 0 };
}
if(path === separator) {
return { target: x, key: '' };
}
var parent = x, key;
var hasContext = context !== void 0;
_parse(path, function(segment) {
// hm... this seems like it should be if(typeof x === 'undefined')
if(x == null) {
// Signal that we prematurely hit the end of the path hierarchy.
parent = null;
return false;
}
if(Array.isArray(x)) {
key = hasContext
? findIndex(findContext, parseArrayIndex(segment), x, context)
: segment === '-' ? segment : parseArrayIndex(segment);
} else {
key = segment;
}
parent = x;
x = x[key];
});
return parent === null
? void 0
: { target: parent, key: key };
}
function absolute(path) {
return path[0] === separator ? path : separator + path;
}
function join(segments) {
return segments.join(separator);
}
function parse(path) {
var segments = [];
_parse(path, segments.push.bind(segments));
return segments;
}
function contains(a, b) {
return b.indexOf(a) === 0 && b[a.length] === separator;
}
/**
* Decode a JSON Pointer path segment
* @see http://tools.ietf.org/html/rfc6901#page-3
* @param {string} s encoded segment
* @returns {string} decoded segment
*/
function decodeSegment(s) {
// See: http://tools.ietf.org/html/rfc6901#page-3
return s.replace(encodedSeparatorRx, separator).replace(encodedEscapeRx, escapeChar);
}
/**
* Encode a JSON Pointer path segment
* @see http://tools.ietf.org/html/rfc6901#page-3
* @param {string} s decoded segment
* @returns {string} encoded segment
*/
function encodeSegment(s) {
return s.replace(escapeRx, encodedEscape).replace(separatorRx, encodedSeparator);
}
var arrayIndexRx = /^(0|[1-9]\d*)$/;
/**
* Return true if s is a valid JSON Pointer array index
* @param {String} s
* @returns {boolean}
*/
function isValidArrayIndex(s) {
return arrayIndexRx.test(s);
}
/**
* Safely parse a string into a number >= 0. Does not check for decimal numbers
* @param {string} s numeric string
* @returns {number} number >= 0
*/
function parseArrayIndex (s) {
if(isValidArrayIndex(s)) {
return +s;
}
throw new SyntaxError('invalid array index ' + s);
}
function findIndex (findContext, start, array, context) {
var index = start;
if(index < 0) {
throw new Error('array index out of bounds ' + index);
}
if(context !== void 0 && typeof findContext === 'function') {
index = findContext(start, array, context);
if(index < 0) {
throw new Error('could not find patch context ' + context);
}
}
return index;
}