Skip to content
Open
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
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/jsondiffpatch/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jsondiffpatch",
"version": "0.7.3",
"version": "0.7.4",
"author": "Benjamin Eidelman <beneidel@gmail.com>",
"description": "JSON diff & patch (object and array diff, text diff, multiple output formats)",
"contributors": ["Benjamin Eidelman <beneidel@gmail.com>"],
Expand Down
33 changes: 29 additions & 4 deletions packages/jsondiffpatch/src/formatters/jsonpatch-apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ const add = (obj: unknown, path: string, value: unknown) => {
const last = parts.pop() as string;
const parent = get(obj, parts);
if (Array.isArray(parent)) {
const index = Number.parseInt(last, 10);
if (index < 0 || index > parent.length) {
const index = last === "-" ? parent.length : Number.parseInt(last, 10);
if (Number.isNaN(index) || index < 0 || index > parent.length) {
throw new Error(
`cannot set /${parts.concat([last]).join("/")} in ${JSON.stringify(
obj,
Expand All @@ -186,6 +186,11 @@ const add = (obj: unknown, path: string, value: unknown) => {
parent.splice(index, 0, clone(value));
return;
}
if (last === "-") {
throw new Error(
"JSONPatch 'add' with '-' requires array target",
);
}
if (typeof parent !== "object" || parent === null) {
throw new Error(
`cannot set /${parts.concat([last]).join("/")} in ${JSON.stringify(obj)}`,
Expand All @@ -202,6 +207,9 @@ const remove = (obj: unknown, path: string) => {
// see https://datatracker.ietf.org/doc/html/rfc6902#section-4.2
const parts = parsePathFromRFC6902(path);
const last = parts.pop() as string;
if (last === "-") {
throw new Error("JSONPatch 'remove' path cannot end with '-'");
}
const parent = get(obj, parts);
if (Array.isArray(parent)) {
const index = Number.parseInt(last, 10);
Expand Down Expand Up @@ -236,8 +244,8 @@ const unadd = (obj: unknown, path: string, previousValue: unknown) => {
const last = parts.pop() as string;
const parent = get(obj, parts);
if (Array.isArray(parent)) {
const index = Number.parseInt(last, 10);
if (index < 0 || index > parent.length - 1) {
const index = last === "-" ? parent.length - 1 : Number.parseInt(last, 10);
if (Number.isNaN(index) || index < 0 || index > parent.length - 1) {
throw new Error(
`cannot un-add (rollback) /${parts
.concat([last])
Expand Down Expand Up @@ -265,6 +273,9 @@ const replace = (obj: unknown, path: string, value: unknown) => {
// see https://datatracker.ietf.org/doc/html/rfc6902#section-4.3
const parts = parsePathFromRFC6902(path);
const last = parts.pop() as string;
if (last === "-") {
throw new Error("JSONPatch 'replace' path cannot end with '-'");
}
const parent = get(obj, parts);
if (Array.isArray(parent)) {
const index = Number.parseInt(last, 10);
Expand Down Expand Up @@ -296,6 +307,11 @@ const replace = (obj: unknown, path: string, value: unknown) => {

const move = (obj: unknown, path: string, from: string) => {
// see https://datatracker.ietf.org/doc/html/rfc6902#section-4.4
// '-' is only valid for add
const pathLast = parsePathFromRFC6902(path).slice(-1)[0];
if (pathLast === "-") {
throw new Error("JSONPatch 'move' path cannot end with '-'");
}
const value = remove(obj, from);
try {
add(obj, path, value);
Expand All @@ -308,12 +324,21 @@ const move = (obj: unknown, path: string, from: string) => {

const copy = (obj: unknown, path: string, from: string) => {
// see https://datatracker.ietf.org/doc/html/rfc6902#section-4.5
// '-' is only valid for add
const pathLast = parsePathFromRFC6902(path).slice(-1)[0];
if (pathLast === "-") {
throw new Error("JSONPatch 'copy' path cannot end with '-'");
}
const value = get(obj, from);
return add(obj, path, clone(value));
};

const test = (obj: unknown, path: string, value: unknown): void => {
// see https://datatracker.ietf.org/doc/html/rfc6902#section-4.5
const last = parsePathFromRFC6902(path).slice(-1)[0];
if (last === "-") {
throw new Error("JSONPatch 'test' path cannot end with '-'");
}
const actualValue = get(obj, path);
if (JSON.stringify(value) !== JSON.stringify(actualValue)) {
throw new Error(
Expand Down
23 changes: 23 additions & 0 deletions packages/jsondiffpatch/test/formatters/jsonpatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,27 @@ describe("jsonpatch", () => {
replaceOp("/tree~1item", 2),
]);
});

// RFC 6902 '-' index support (only for add)
describe("RFC6902 '-' index support", () => {
it("should append to end of array when using add with '-'", () => {
const before = { list: [1, 2, 3] };
const ops: jsonpatchFormatter.Op[] = [
{ op: "add", path: "/list/-", value: 4 },
];
const target = jsondiffpatch.clone(before);
formatter.patch(target, ops);
expect(target).toEqual({ list: [1, 2, 3, 4] });
});

it("should throw when using '-' in remove", () => {
const before = { list: [1, 2, 3] };
const ops: jsonpatchFormatter.Op[] = [
{ op: "remove", path: "/list/-" },
];
expect(() => formatter.patch(jsondiffpatch.clone(before), ops)).toThrow(
/JSONPatch 'remove' path cannot end with '-'/,
);
});
});
});