Skip to content

Instantly share code, notes, and snippets.

@tmcw

tmcw/README.md Secret

Last active December 4, 2021 16:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tmcw/86c958655202885a71c89cfcc5e6b850 to your computer and use it in GitHub Desktop.
Save tmcw/86c958655202885a71c89cfcc5e6b850 to your computer and use it in GitHub Desktop.
Tiny addition to jsonpointer

Tiny addition (and port to TypeScript) of node-jsonpointer. This adds a clone method, that when given an object and a path, returns a version of that object with the given path cloned.

Example:

const foo = { bar: [{x: 1}] };
const clone = pointer.clone(foo, 'bar/0/x');
pointer.set(clone, 'bar/0/x', 42);
const hasExcape = /~/;
const escapeMatcher = /~[01]/g;
type Pointer = string | string[];
function escapeReplacer(m: string) {
switch (m) {
case "~1":
return "/";
case "~0":
return "~";
}
throw new Error("Invalid tilde escape: " + m);
}
function untilde(str: string) {
if (!hasExcape.test(str)) return str;
return str.replace(escapeMatcher, escapeReplacer);
}
function setter(obj: any, pointer: Pointer, value: any) {
let part: string | number;
let hasNextPart: boolean;
if (pointer[1] === "constructor" && pointer[2] === "prototype") return obj;
if (pointer[1] === "__proto__") return obj;
for (let p = 1, len = pointer.length; p < len; ) {
part = untilde(pointer[p++]);
hasNextPart = len > p;
if (typeof obj[part] === "undefined") {
// support setting of /-
if (Array.isArray(obj) && part === "-") {
part = obj.length;
}
// support nested objects/array when setting values
if (hasNextPart) {
if (
(pointer[p] !== "" && (pointer[p] as any) < Infinity) ||
pointer[p] === "-"
)
obj[part] = [];
else obj[part] = {};
}
}
if (!hasNextPart) break;
obj = obj[part];
}
const oldValue = obj[part!];
if (value === undefined) delete obj[part!];
else obj[part!] = value;
return oldValue;
}
export function compilePointer(pointer: Pointer) {
if (typeof pointer === "string") {
pointer = pointer.split("/");
if (pointer[0] === "") return pointer;
throw new Error("Invalid JSON pointer.");
} else if (Array.isArray(pointer)) {
return pointer;
}
throw new Error("Invalid JSON pointer.");
}
export function get(obj: any, pointer: Pointer) {
if (typeof obj !== "object") throw new Error("Invalid input object.");
pointer = compilePointer(pointer);
const len = pointer.length;
if (len === 1) return obj;
for (let p = 1; p < len; ) {
obj = obj[untilde(pointer[p++])];
if (len === p) return obj;
if (typeof obj !== "object") return undefined;
}
}
export function clone(obj: any, pointer: Pointer) {
if (typeof obj !== "object") throw new Error("Invalid input object.");
pointer = compilePointer(pointer);
const len = pointer.length;
if (len === 1) return obj;
const rootPointer = Object.assign({}, obj);
let levelPointer = rootPointer;
for (let p = 1; p < len - 1; ) {
const key = untilde(pointer[p++]);
// Clone objects and arrays
if (Array.isArray(levelPointer[key])) {
levelPointer[key] = levelPointer[key].slice();
} else if (
levelPointer[key] !== null &&
typeof levelPointer[key] === "object"
) {
levelPointer[key] = Object.assign({}, levelPointer[key]);
}
levelPointer = levelPointer[key];
if (len === p) return rootPointer;
}
return rootPointer;
}
export function set(obj: any, pointer: Pointer, value: any) {
if (typeof obj !== "object") throw new Error("Invalid input object.");
pointer = compilePointer(pointer);
if (pointer.length === 0) throw new Error("Invalid JSON pointer for set.");
return setter(obj, pointer, value);
}
export function compile(pointer: Pointer) {
const compiled = compilePointer(pointer);
return {
get: function (object: any) {
return get(object, compiled);
},
set: function (object: any, value: any) {
return set(object, compiled, value);
},
};
}
@thadk
Copy link

thadk commented Dec 4, 2021

Tom released this slightly updated version a few weeks later which has a generic: https://gist.github.com/tmcw/c5186205a22d74f9fc624ed07ef68716

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment