Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/assign.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,38 @@ describe("JsonPointer.assign", () => {
expect(subject[0]).to.equal("foo");
});
});

When("indexing into the array with an invalid index", () => {
const assign = JsonPointer.assign("/01");

Then("an error should be thrown", () => {
expect(() => assign(subject, "foo")).to.throw(TypeError, "Value at '' is an array and '01' is not a valid array index");
});
});

When("indexing into the array with a segment that isn't a number", () => {
const assign = JsonPointer.assign("/foo");

Then("an error should be thrown", () => {
expect(() => assign(subject, "foo")).to.throw(TypeError, "Value at '' is an array and 'foo' is not a valid array index");
});
});

When("indexing into the array past the largest array index", () => {
const assign = JsonPointer.assign("/4294967295");

Then("an error should be thrown", () => {
expect(() => assign(subject, "foo")).to.throw(TypeError, "Value at '' is an array and '4294967295' is not a valid array index");
});
});

When("indexing into a nested array with an invalid index", () => {
const assign = JsonPointer.assign("/aaa/01");

Then("the error should say where the array is", () => {
expect(() => assign({ aaa: [] }, "foo")).to.throw(TypeError, "Value at '/aaa' is an array and '01' is not a valid array index");
});
});
});

Given("a number", () => {
Expand Down
1 change: 1 addition & 0 deletions lib/get.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe("JsonPointer", () => {
["/bbb/-", { ccc: 13 }],
["/bar", undefined],
["/foo/2", undefined],
["/foo/01", undefined],
["/foo/-", undefined],
["/0", undefined]
];
Expand Down
51 changes: 34 additions & 17 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const _set = (segments, index, subject, value) => {

// currentSubject could also be an array, but this appeases the type system
const currentSubject = /** @type API.JsonObject */ (subject);
const computedSegment = computeSegment(subject, segment);
const computedSegment = computeSegment(subject, segment, cursorAt(segments, index));
currentSubject[computedSegment] = _set(segments, index + 1, currentSubject[computedSegment], value);
return currentSubject;
};
Expand Down Expand Up @@ -210,7 +210,7 @@ const _assign = (segments, subject, value, cursor = nil) => {
let lastSubject;

for (let segment of segments) {
segment = computeSegment(currentSubject, segment);
segment = computeSegment(currentSubject, segment, cursor);
lastSegment = segment;
lastSubject = currentSubject;
currentSubject = applySegment(currentSubject, segment, cursor);
Expand Down Expand Up @@ -274,13 +274,12 @@ const _unset = (segments, index, subject) => {

// currentSubject could also be an array, but this appeases the type system
const currentSubject = /** @type API.JsonObject */ (subject);
const computedSegment = computeSegment(currentSubject, segment);
const unsetSubject = _unset(segments, index + 1, currentSubject[computedSegment]);
if (computedSegment in currentSubject) {
const unsetSubject = _unset(segments, index + 1, getSegment(currentSubject, segment));
if (hasSegment(currentSubject, segment)) {
if (unsetSubject === undefined) {
delete currentSubject[computedSegment];
delete currentSubject[segment];
} else {
currentSubject[computedSegment] = unsetSubject;
currentSubject[segment] = unsetSubject;
}
}
return currentSubject;
Expand Down Expand Up @@ -324,8 +323,7 @@ const _remove = (segments, subject, cursor = nil) => {
/** @type API.Json | undefined */
let lastSubject;

for (let segment of segments) {
segment = computeSegment(currentSubject, segment);
for (const segment of segments) {
lastSegment = segment;
lastSubject = currentSubject;
currentSubject = applySegment(currentSubject, segment, cursor);
Expand All @@ -337,7 +335,10 @@ const _remove = (segments, subject, cursor = nil) => {
}

// lastSubject could also be an array, but this appeases the type system
delete /** @type API.JsonObject */ (lastSubject)[lastSegment];
const container = /** @type API.JsonObject */ (lastSubject);
if (hasSegment(container, lastSegment)) {
delete container[lastSegment];
}
};

/** @type API.append */
Expand All @@ -364,25 +365,39 @@ const unescape = (segment) => {
* @overload
* @param {API.Json[]} value
* @param {string} segment
* @param {string} cursor
* @returns {number}
*
* @overload
* @param {API.Json | undefined} value
* @param {string} segment
* @param {string} cursor
* @returns {string}
*
* @param {API.Json | undefined} value
* @param {string} segment
* @param {string} cursor
* @returns {string | number}
*/
const computeSegment = (value, segment) => {
if (Array.isArray(value)) {
return segment === "-" ? value.length : parseInt(segment, 10);
} else {
const computeSegment = (value, segment, cursor) => {
if (!Array.isArray(value)) {
return segment;
}

if (segment === "-") {
return value.length;
}

if (!/^(?:0|[1-9][0-9]*)$/.test(segment) || Number(segment) > MAX_INDEX) {
throw TypeError(`Value at '${cursor}' is an array and '${segment}' is not a valid array index`);
}

return Number(segment);
};

/** The largest array index in JavaScript. A larger number is an ordinary property, which `JSON.stringify` drops. */
const MAX_INDEX = 2 ** 32 - 2;

/** @type (value: API.Json | undefined, segment: string, cursor?: string) => API.Json | undefined */
const applySegment = (value, segment, cursor = "") => {
if (!isContainer(value)) {
Expand All @@ -394,13 +409,15 @@ const applySegment = (value, segment, cursor = "") => {

/** @type (value: API.JsonObject | API.Json[], segment: string) => API.Json | undefined */
const getSegment = (value, segment) => {
const computedSegment = computeSegment(value, segment);
if (Object.hasOwn(value, computedSegment)) {
if (hasSegment(value, segment)) {
// value could also be an array, but this appeases the type system
return /** @type API.JsonObject */ (value)[computedSegment];
return /** @type API.JsonObject */ (value)[segment];
}
};

/** @type (value: API.JsonObject | API.Json[], segment: string) => boolean */
const hasSegment = (value, segment) => Object.prototype.propertyIsEnumerable.call(value, segment);

/** @type (value: API.Json | undefined) => value is API.JsonObject | API.Json[] */
const isContainer = (value) => typeof value === "object" && value !== null;

Expand Down
22 changes: 22 additions & 0 deletions lib/remove.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,28 @@ describe("JsonPointer.remove", () => {
expect(subject).to.eql([]);
});
});

When("deleting with an invalid index", () => {
beforeEach(() => {
subject = ["aaa", "bbb"];
JsonPointer.remove("/01", subject);
});

Then("the value should be unchanged", () => {
expect(subject).to.eql(["aaa", "bbb"]);
});
});

When("deleting the array's length", () => {
beforeEach(() => {
subject = ["aaa", "bbb"];
JsonPointer.remove("/length", subject);
});

Then("the value should be unchanged", () => {
expect(subject).to.eql(["aaa", "bbb"]);
});
});
});

Given("a number", () => {
Expand Down
32 changes: 32 additions & 0 deletions lib/set.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,38 @@ describe("JsonPointer.set", () => {
expect(subject.length).to.equal(0);
});
});

When("indexing into the array with an invalid index", () => {
const set = JsonPointer.set("/01");

Then("an error should be thrown", () => {
expect(() => set(subject, "foo")).to.throw(TypeError, "Value at '' is an array and '01' is not a valid array index");
});
});

When("indexing into the array with a segment that isn't a number", () => {
const set = JsonPointer.set("/foo");

Then("an error should be thrown", () => {
expect(() => set(subject, "foo")).to.throw(TypeError, "Value at '' is an array and 'foo' is not a valid array index");
});
});

When("indexing into the array past the largest array index", () => {
const set = JsonPointer.set("/4294967295");

Then("an error should be thrown", () => {
expect(() => set(subject, "foo")).to.throw(TypeError, "Value at '' is an array and '4294967295' is not a valid array index");
});
});

When("indexing into a nested array with an invalid index", () => {
const set = JsonPointer.set("/aaa/01");

Then("the error should say where the array is", () => {
expect(() => set({ aaa: [] }, "foo")).to.throw(TypeError, "Value at '/aaa' is an array and '01' is not a valid array index");
});
});
});

Given("a number", () => {
Expand Down
28 changes: 28 additions & 0 deletions lib/unset.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,34 @@ describe("JsonPointer.unset", () => {
expect(result).to.eql(subject);
});
});

When("deleting with an invalid index", () => {
/** @type Json | undefined */
let result;

beforeEach(() => {
subject = ["aaa", "bbb"];
result = JsonPointer.unset("/01", subject);
});

Then("nothing should be removed", () => {
expect(result).to.eql(["aaa", "bbb"]);
});
});

When("deleting the array's length", () => {
/** @type Json | undefined */
let result;

beforeEach(() => {
subject = ["aaa", "bbb"];
result = JsonPointer.unset("/length", subject);
});

Then("nothing should be removed", () => {
expect(result).to.eql(["aaa", "bbb"]);
});
});
});

Given("a number", () => {
Expand Down
Loading