diff --git a/lib/assign.test.js b/lib/assign.test.js index 2b61ea9..fd5ad16 100644 --- a/lib/assign.test.js +++ b/lib/assign.test.js @@ -114,6 +114,14 @@ 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"); + }); + }); }); Given("a number", () => { diff --git a/lib/get.test.js b/lib/get.test.js index dc9a48d..6b635a6 100644 --- a/lib/get.test.js +++ b/lib/get.test.js @@ -49,6 +49,7 @@ describe("JsonPointer", () => { ["/bbb/-", { ccc: 13 }], ["/bar", undefined], ["/foo/2", undefined], + ["/foo/01", undefined], ["/foo/-", undefined], ["/0", undefined] ]; diff --git a/lib/index.js b/lib/index.js index fc1a5cb..98f35df 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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; }; @@ -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); @@ -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; @@ -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); @@ -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 */ @@ -364,23 +365,34 @@ 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)) { + throw TypeError(`Value at '${cursor}' is an array and '${segment}' is not a valid array index`); + } + + return Number(segment); }; /** @type (value: API.Json | undefined, segment: string, cursor?: string) => API.Json | undefined */ @@ -394,13 +406,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; diff --git a/lib/set.test.js b/lib/set.test.js index 4bcf9be..c495ade 100644 --- a/lib/set.test.js +++ b/lib/set.test.js @@ -192,6 +192,14 @@ 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"); + }); + }); }); Given("a number", () => {