@hyperjump/json-pointer 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,24 +1,22 @@
1
- JSON Pointer
2
- ============
1
+ # JSON Pointer
3
2
 
4
3
  This is an implementation of RFC-6901 JSON Pointer. JSON Pointer is designed for
5
4
  referring to data values within a JSON document. It's designed to be URL
6
5
  friendly so it can be used as a URL fragment that points to a specific part of
7
6
  the JSON document.
8
7
 
9
- Installation
10
- ------------
8
+ ## Installation
9
+
11
10
  Includes support for node.js (ES Modules, TypeScript) and browsers.
12
11
 
13
12
  ```bash
14
13
  npm install @hyperjump/json-pointer
15
14
  ```
16
15
 
17
- Usage
18
- -----
16
+ ## Usage
19
17
 
20
18
  ```javascript
21
- const JsonPointer = require("@hyperjump/json-pointer");
19
+ import * as JsonPointer from "@hyperjump/json-pointer";
22
20
 
23
21
  const value = {
24
22
  "foo": {
@@ -27,7 +25,7 @@ const value = {
27
25
  };
28
26
 
29
27
  // Construct pointers
30
- const fooPointer = JsonPointer.append(JsonPointer.nil, "foo"); // "/foo"
28
+ const fooPointer = JsonPointer.append("foo", JsonPointer.nil); // "/foo"
31
29
  const fooBarPointer = JsonPointer.append(fooPointer, "bar"); // "/foo/bar"
32
30
 
33
31
  // Get a value from a pointer
@@ -55,8 +53,40 @@ const deleteFooBar = JsonPointer.remove(fooBarPointer);
55
53
  deleteFooBar(value); // { "foo": {} }
56
54
  ```
57
55
 
58
- Contributing
59
- ------------
56
+ ## API
57
+
58
+ * **nil**: ""
59
+
60
+ The empty pointer.
61
+ * **pointerSegments**: (pointer: string) => Generator\<string>
62
+
63
+ An iterator for the segments of a JSON Pointer that handles escaping.
64
+ * **append**: (segment: string, pointer: string) => string
65
+
66
+ Append a segment to a JSON Pointer.
67
+ * **get**: (pointer: string, subject: any) => any
68
+
69
+ Use a JSON Pointer to get a value. This function can be curried.
70
+ * **set**: (pointer: string, subject: any, value: any) => any
71
+
72
+ Immutably set a value using a JSON Pointer. Returns a new version of
73
+ `subject` with the value set. The original `subject` is not changed, but the
74
+ value isn't entirely cloned. Values that aren't changed will point to
75
+ the same value as the original. This function can be curried.
76
+ * **assign**: (pointer: string, subject: any, value: any) => void
77
+
78
+ Mutate a value using a JSON Pointer. This function can be curried.
79
+ * **unset**: (pointer: string, subject: any) => any
80
+
81
+ Immutably delete a value using a JSON Pointer. Returns a new version of
82
+ `subject` without the value. The original `subject` is not changed, but the
83
+ value isn't entirely cloned. Values that aren't changed will point to the
84
+ same value as the original. This function can be curried.
85
+ * **remove**: (pointer: string, subject: any) => void
86
+
87
+ Delete a value using a JSON Pointer. This function can be curried.
88
+
89
+ ## Contributing
60
90
 
61
91
  ### Tests
62
92
 
package/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export const nil: "";
2
+ export const pointerSegments: (pointer: string) => Generator<string>;
2
3
  export const append: (
3
4
  (segment: string, pointer: string) => string
4
5
  ) & (
package/lib/index.js CHANGED
@@ -1,11 +1,6 @@
1
- import curry from "just-curry-it";
2
-
3
-
4
1
  export const nil = "";
5
2
 
6
- const EXISTS = Symbol("EXISTS");
7
-
8
- const segmentGenerator = (pointer) => {
3
+ export const pointerSegments = function* (pointer) {
9
4
  if (pointer.length > 0 && pointer[0] !== "/") {
10
5
  throw Error("Invalid JSON Pointer");
11
6
  }
@@ -13,132 +8,147 @@ const segmentGenerator = (pointer) => {
13
8
  let segmentStart = 1;
14
9
  let segmentEnd = 0;
15
10
 
16
- return (mode) => {
17
- if (mode === EXISTS) {
18
- return segmentEnd < pointer.length;
19
- }
20
-
21
- if (segmentEnd >= pointer.length) {
22
- return;
23
- }
24
-
11
+ while (segmentEnd < pointer.length) {
25
12
  const position = pointer.indexOf("/", segmentStart);
26
13
  segmentEnd = position === -1 ? pointer.length : position;
27
- const segment = unescape(pointer.slice(segmentStart, segmentEnd));
14
+ const segment = pointer.slice(segmentStart, segmentEnd);
28
15
  segmentStart = segmentEnd + 1;
29
16
 
30
- return segment;
31
- };
17
+ yield unescape(segment);
18
+ }
32
19
  };
33
20
 
34
21
  export const get = (pointer, subject = undefined) => {
35
- const nextSegment = segmentGenerator(pointer);
36
- const fn = (subject) => _get(nextSegment, subject, nil);
37
- return subject === undefined ? fn : fn(subject);
22
+ if (subject === undefined) {
23
+ const segments = [...pointerSegments(pointer)];
24
+ return (subject) => _get(segments, subject);
25
+ } else {
26
+ return _get(pointerSegments(pointer), subject);
27
+ }
38
28
  };
39
29
 
40
- const _get = (nextSegment, subject, cursor) => {
41
- if (!nextSegment(EXISTS)) {
42
- return subject;
43
- } else {
44
- const segment = nextSegment();
45
- return _get(nextSegment, applySegment(subject, segment, cursor), append(segment, cursor));
30
+ const _get = (segments, subject) => {
31
+ let cursor = nil;
32
+ for (const segment of segments) {
33
+ subject = applySegment(subject, segment, cursor);
34
+ cursor = append(segment, cursor);
46
35
  }
36
+
37
+ return subject;
47
38
  };
48
39
 
49
40
  export const set = (pointer, subject = undefined, value = undefined) => {
50
- const nextSegment = segmentGenerator(pointer);
51
- const fn = curry((subject, value) => _set(nextSegment, subject, value, nil));
52
- return subject === undefined ? fn : fn(subject, value);
41
+ if (subject === undefined) {
42
+ const segments = [...pointerSegments(pointer)];
43
+ return (subject, value) => _set(segments.values(), subject, value);
44
+ } else {
45
+ return _set(pointerSegments(pointer), subject, value);
46
+ }
53
47
  };
54
48
 
55
- const _set = (nextSegment, subject, value, cursor) => {
56
- const segment = nextSegment();
57
- if (segment === undefined) {
49
+ const _set = (segments, subject, value, cursor = nil) => {
50
+ const segment = segments.next();
51
+ if (segment.done) {
58
52
  return value;
59
- } else if (nextSegment(EXISTS)) {
60
- if (Array.isArray(subject)) {
61
- const clonedSubject = [...subject];
62
- clonedSubject[segment] = _set(nextSegment, applySegment(subject, segment, cursor), value, append(segment, cursor));
63
- return clonedSubject;
64
- } else {
65
- return { ...subject, [segment]: _set(nextSegment, applySegment(subject, segment, cursor), value, append(segment, cursor)) };
66
- }
67
- } else if (Array.isArray(subject)) {
68
- const clonedSubject = [...subject];
69
- clonedSubject[computeSegment(subject, segment)] = value;
70
- return clonedSubject;
53
+ }
54
+
55
+ if (Array.isArray(subject)) {
56
+ subject = [...subject];
71
57
  } else if (typeof subject === "object" && subject !== null) {
72
- return { ...subject, [segment]: value };
58
+ subject = { ...subject };
73
59
  } else {
74
- return applySegment(subject, segment, cursor);
60
+ applySegment(subject, segment.value, cursor);
75
61
  }
62
+ cursor = append(segment.value, cursor);
63
+
64
+ const computedSegment = computeSegment(subject, segment.value);
65
+ subject[computedSegment] = _set(segments, subject[computedSegment], value, cursor);
66
+ return subject;
76
67
  };
77
68
 
78
69
  export const assign = (pointer, subject = undefined, value = undefined) => {
79
- const nextSegment = segmentGenerator(pointer);
80
- const fn = curry((subject, value) => _assign(nextSegment, subject, value, nil));
81
- return subject === undefined ? fn : fn(subject, value);
70
+ if (subject === undefined) {
71
+ const segments = [...pointerSegments(pointer)];
72
+ return (subject, value) => _assign(segments.values(), subject, value);
73
+ } else {
74
+ return _assign(pointerSegments(pointer), subject, value);
75
+ }
82
76
  };
83
77
 
84
- const _assign = (nextSegment, subject, value, cursor) => {
85
- const segment = nextSegment();
86
- if (segment === undefined) {
87
- return;
88
- } else if (!nextSegment(EXISTS) && !isScalar(subject)) {
89
- subject[computeSegment(subject, segment)] = value;
90
- } else {
91
- _assign(nextSegment, applySegment(subject, segment, cursor), value, append(segment, cursor));
78
+ const _assign = (segments, subject, value, cursor = nil) => {
79
+ let lastSegment;
80
+ let lastSubject;
81
+ for (let segment of segments) {
82
+ segment = computeSegment(subject, segment);
83
+ lastSegment = segment;
84
+ lastSubject = subject;
85
+ subject = applySegment(subject, segment, cursor);
86
+ cursor = append(segment, cursor);
87
+ }
88
+
89
+ if (lastSubject !== undefined) {
90
+ lastSubject[lastSegment] = value;
92
91
  }
93
92
  };
94
93
 
95
94
  export const unset = (pointer, subject = undefined) => {
96
- const nextSegment = segmentGenerator(pointer);
97
- const fn = (subject) => _unset(nextSegment, subject, nil);
98
- return subject === undefined ? fn : fn(subject);
95
+ if (subject === undefined) {
96
+ const segments = [...pointerSegments(pointer)];
97
+ return (subject) => _unset(segments.values(), subject);
98
+ } else {
99
+ return _unset(pointerSegments(pointer), subject);
100
+ }
99
101
  };
100
102
 
101
- const _unset = (nextSegment, subject, cursor) => {
102
- const segment = nextSegment();
103
- if (segment === undefined) {
103
+ const _unset = (segments, subject, cursor = nil) => {
104
+ const segment = segments.next();
105
+ if (segment.done) {
104
106
  return;
105
- } else if (nextSegment(EXISTS)) {
106
- const value = applySegment(subject, segment, cursor);
107
- return { ...subject, [segment]: _unset(nextSegment, value, append(segment, cursor)) };
108
- } else if (Array.isArray(subject)) {
109
- const clonedSubject = [...subject];
110
- delete clonedSubject[computeSegment(subject, segment)];
111
- return clonedSubject;
107
+ }
108
+
109
+ if (Array.isArray(subject)) {
110
+ subject = [...subject];
112
111
  } else if (typeof subject === "object" && subject !== null) {
113
- // eslint-disable-next-line no-unused-vars
114
- const { [segment]: _, ...result } = subject;
115
- return result;
112
+ subject = { ...subject };
116
113
  } else {
117
- return applySegment(subject, segment, cursor);
114
+ applySegment(subject, segment.value, cursor);
118
115
  }
116
+ cursor = append(segment.value, cursor);
117
+
118
+ const computedSegment = computeSegment(subject, segment.value);
119
+ const unsetSubject = _unset(segments, subject[computedSegment], cursor);
120
+ if (computedSegment in subject) {
121
+ subject[computedSegment] = unsetSubject;
122
+ }
123
+ return subject;
119
124
  };
120
125
 
121
126
  export const remove = (pointer, subject = undefined) => {
122
- const nextSegment = segmentGenerator(pointer);
123
- const fn = (subject) => _remove(nextSegment, subject, nil);
124
- return subject === undefined ? fn : fn(subject);
127
+ if (subject === undefined) {
128
+ const segments = [...pointerSegments(pointer)];
129
+ return (subject) => _remove(segments.values(), subject);
130
+ } else {
131
+ return _remove(pointerSegments(pointer), subject);
132
+ }
125
133
  };
126
134
 
127
- const _remove = (nextSegment, subject, cursor) => {
128
- const segment = nextSegment();
129
- if (segment === undefined) {
130
- return;
131
- } else if (nextSegment(EXISTS)) {
132
- const value = applySegment(subject, segment, cursor);
133
- _remove(nextSegment, value, append(segment, cursor));
134
- } else if (!isScalar(subject)) {
135
- delete subject[segment];
136
- } else {
137
- applySegment(subject, segment, cursor);
135
+ const _remove = (segments, subject, cursor = nil) => {
136
+ let lastSegment;
137
+ let lastSubject;
138
+ for (let segment of segments) {
139
+ segment = computeSegment(subject, segment);
140
+ lastSegment = segment;
141
+ lastSubject = subject;
142
+ subject = applySegment(subject, segment, cursor);
143
+ cursor = append(segment, cursor);
144
+ }
145
+
146
+ if (lastSubject !== undefined) {
147
+ delete lastSubject[lastSegment];
138
148
  }
139
149
  };
140
150
 
141
- export const append = curry((segment, pointer) => pointer + "/" + escape(segment));
151
+ export const append = (segment, pointer) => pointer + "/" + escape(segment);
142
152
 
143
153
  const escape = (segment) => segment.toString().replace(/~/g, "~0").replace(/\//g, "~1");
144
154
  const unescape = (segment) => segment.toString().replace(/~1/g, "/").replace(/~0/g, "~");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperjump/json-pointer",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "An RFC-6901 JSON Pointer implementation",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -8,7 +8,7 @@
8
8
  "scripts": {
9
9
  "clean": "xargs -a .gitignore rm -rf",
10
10
  "lint": "eslint lib",
11
- "test": "mocha 'lib/**/*.spec.ts'"
11
+ "test": "vitest --watch=false"
12
12
  },
13
13
  "repository": "github:hyperjump-io/json-pointer",
14
14
  "keywords": [
@@ -22,20 +22,13 @@
22
22
  "url": "https://github.com/sponsors/jdesrosiers"
23
23
  },
24
24
  "devDependencies": {
25
- "@types/chai": "*",
26
- "@types/mocha": "*",
27
25
  "@typescript-eslint/eslint-plugin": "*",
28
26
  "@typescript-eslint/parser": "*",
29
- "chai": "*",
30
27
  "eslint": "*",
31
28
  "eslint-import-resolver-node": "*",
32
29
  "eslint-import-resolver-typescript": "*",
33
30
  "eslint-plugin-import": "*",
34
- "mocha": "*",
35
- "ts-node": "*",
36
- "typescript": "*"
37
- },
38
- "dependencies": {
39
- "just-curry-it": "^5.3.0"
31
+ "typescript": "*",
32
+ "vitest": "*"
40
33
  }
41
34
  }