jsonpointerx
Version:
fast jsonpointer (rfc6901) implementation
255 lines (252 loc) • 8.73 kB
JavaScript
function _extends() {
_extends = Object.assign || function assign(target) {
for(var i = 1; i < arguments.length; i++){
var source = arguments[i];
for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends.apply(this, arguments);
}
const fromJpStringSearch = /~[01]/g;
const toJpStringSearch = /[~/]/g;
class JsonPointer {
get segments() {
return this._segments.slice(0);
}
get root() {
return this._segments.length === 0 ? true : false;
}
/**
* Get a value from a referenced location within an object
*
* @param input - The object to be read from
* @returns The value from the referenced location or undefined
*/ get(input) {
return this.fnGet(input);
}
/**
* fallback if compilation (using 'new Function') is disabled
*
* @param input - The object to be read from
* @returns The value from the referenced location or undefined
*/ notCompiledGet(input) {
let node = input;
for(let idx = 0; idx < this._segments.length;){
if (node == undefined) {
return undefined;
}
node = node[this._segments[idx++]];
}
return node;
}
/**
* Set a value to the referenced location within an object
*
* @param obj - To object to be written in
* @param [value] - The value to be written to the referenced location
* @returns returns 'value' if pointer.length === 1 or 'input' otherwise
*
* throws if 'input' is not an object
* throws if 'set' is called for a root JSON pointer
* throws on invalid array index references
* throws if one of the ancestors is a scalar (js engine): Cannot create propery 'foo' on 'baz'
*/ set(input, value) {
if (typeof input !== 'object') {
throw new Error('Invalid input object.');
}
if (this._segments.length === 0) {
throw new Error(`Set for root JSON pointer is not allowed.`);
}
const len = this._segments.length - 1;
let node = input;
let nextnode;
let part;
for(let idx = 0; idx < len;){
part = this._segments[idx++];
nextnode = node[part];
if (nextnode == undefined) {
if (this._segments[idx] === '-') {
nextnode = [];
} else {
nextnode = {};
}
if (Array.isArray(node)) {
if (part === '-') {
node.push(nextnode);
} else {
const i = parseInt(part, 10);
if (isNaN(i)) {
throw Error(`Invalid JSON pointer array index reference (level ${idx}).`);
}
node[i] = nextnode;
}
} else {
node[part] = nextnode;
}
}
node = nextnode;
}
part = this._segments[len];
if (value === undefined) {
delete node[part];
} else {
if (Array.isArray(node)) {
if (part === '-') {
node.push(value);
} else {
const i = parseInt(part, 10);
if (isNaN(i)) {
throw Error(`Invalid JSON pointer array index reference at end of pointer.`);
}
node[i] = value;
}
} else {
node[part] = value;
}
}
return input;
}
concat(p) {
return new JsonPointer(this._segments.concat(p.segments));
}
concatSegment(segment) {
return new JsonPointer(this._segments.concat(segment));
}
concatPointer(pointer) {
return this.concat(JsonPointer.compile(pointer));
}
toString() {
if (this._segments.length === 0) {
return '';
}
return '/'.concat(this._segments.map((v)=>v.replace(toJpStringSearch, JsonPointer.toJpStringReplace)).join('/'));
}
toURIFragmentIdentifier() {
if (this._segments.length === 0) {
return '#';
}
return '#/'.concat(this._segments.map((v)=>encodeURIComponent(v).replace(toJpStringSearch, JsonPointer.toJpStringReplace)).join('/'));
}
compileFunctions() {
let body = '';
for(let idx = 0; idx < this._segments.length;){
const segment = this._segments[idx++].replace(/\\/g, '\\\\').replace(/'/g, "\\'");
body += `
if (node == undefined) return undefined;
node = node['${segment}'];
`;
}
body += `
return node;
`;
this.fnGet = new Function('node', body);
}
/**
* Instantiate a new 'JsonPointer' from encoded json-pointer
*
* @static
* @param pointer - The encoded json-pointer
* @param {boolean} [decodeOnly] - only decode and do not compile (using 'new Function')
* @returns {JsonPointer}
*/ static compile(pointer, decodeOnly) {
const segments = pointer.split('/');
const firstSegment = segments.shift();
if (firstSegment === '') {
return new JsonPointer(segments.map((v)=>v.replace(fromJpStringSearch, JsonPointer.fromJpStringReplace)), decodeOnly);
}
if (firstSegment === '#') {
return new JsonPointer(segments.map((v)=>decodeURIComponent(v.replace(fromJpStringSearch, JsonPointer.fromJpStringReplace))), decodeOnly);
}
throw new Error(`Invalid JSON pointer '${pointer}'.`);
}
/**
* Get a value from a referenced location within an object
*
* @static
* @param obj - The object to be read from
* @param {string} pointer - The encoded json-pointer
* @returns The value from the referenced location or undefined
*/ static get(obj, pointer) {
return JsonPointer.compile(pointer).get(obj);
}
/**
* Set a value to the referenced location within an object
*
* @static
* @param obj - To object to be written in
* @param pointer - The encoded json-pointer
* @param [value] - The value to be written to the referenced location
* @returns returns 'value' if pointer.length === 1 or 'input' otherwise
*/ static set(obj, pointer, value) {
return JsonPointer.compile(pointer, true).set(obj, value);
}
/**
* set global options
*
* @static
* @param {JsonPointerOpts} opts
*/ static options(opts) {
if (opts) {
JsonPointer.opts = _extends({}, JsonPointer.opts, opts);
}
return JsonPointer.opts;
}
static fromJpStringReplace(v) {
switch(v){
case '~1':
return '/';
case '~0':
return '~';
}
/* istanbul ignore next */ throw new Error('JsonPointer.escapedReplacer: this should not happen');
}
static toJpStringReplace(v) {
switch(v){
case '/':
return '~1';
case '~':
return '~0';
}
/* istanbul ignore next */ throw new Error('JsonPointer.unescapedReplacer: this should not happen');
}
/**
* Creates an instance of JsonPointer.
* @param [segments] - The path segments of the json-pointer / The decoded json-pointer
* @param [noCompile] - disable compiling (using 'new Function')
*/ constructor(segments, noCompile){
var _JsonPointer_opts;
if (segments) {
if (Array.isArray(segments)) {
this._segments = segments;
} else {
this._segments = [
segments
];
}
} else {
this._segments = [];
}
if ((_JsonPointer_opts = JsonPointer.opts) == null ? void 0 : _JsonPointer_opts.blacklist) {
var _JsonPointer_opts1;
const blacklist = (_JsonPointer_opts1 = JsonPointer.opts) == null ? void 0 : _JsonPointer_opts1.blacklist;
this._segments.forEach((segment)=>{
if (blacklist.includes(segment)) {
throw new Error(`JSON pointer segment '${segment}' is blacklisted`);
}
});
}
if (noCompile || JsonPointer.opts && JsonPointer.opts.noCompile) {
this.fnGet = this.notCompiledGet;
} else {
this.compileFunctions();
}
}
}
JsonPointer.opts = {
blacklist: [
'__proto__',
'prototype'
]
};
export { JsonPointer };