Skip to content

Commit 5617c02

Browse files
committed
Add pointerSegments and refactor
1 parent 516d221 commit 5617c02

File tree

4 files changed

+139
-101
lines changed

4 files changed

+139
-101
lines changed

README.md

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
JSON Pointer
2-
============
1+
# JSON Pointer
32

43
This is an implementation of RFC-6901 JSON Pointer. JSON Pointer is designed for
54
referring to data values within a JSON document. It's designed to be URL
65
friendly so it can be used as a URL fragment that points to a specific part of
76
the JSON document.
87

9-
Installation
10-
------------
8+
## Installation
9+
1110
Includes support for node.js (ES Modules, TypeScript) and browsers.
1211

1312
```bash
1413
npm install @hyperjump/json-pointer
1514
```
1615

17-
Usage
18-
-----
16+
## Usage
1917

2018
```javascript
2119
import * as JsonPointer from "@hyperjump/json-pointer";
@@ -55,8 +53,40 @@ const deleteFooBar = JsonPointer.remove(fooBarPointer);
5553
deleteFooBar(value); // { "foo": {} }
5654
```
5755

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
6090

6191
### Tests
6292

lib/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const nil: "";
2+
export const pointerSegments: (pointer: string) => Generator<string>;
23
export const append: (
34
(segment: string, pointer: string) => string
45
) & (

lib/index.js

Lines changed: 100 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,154 @@
1-
import curry from "just-curry-it";
2-
3-
41
export const nil = "";
52

6-
const EXISTS = Symbol("EXISTS");
7-
8-
const segmentGenerator = (pointer) => {
3+
export const pointerSegments = function* (pointer) {
94
if (pointer.length > 0 && pointer[0] !== "/") {
105
throw Error("Invalid JSON Pointer");
116
}
127

138
let segmentStart = 1;
149
let segmentEnd = 0;
1510

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) {
2512
const position = pointer.indexOf("/", segmentStart);
2613
segmentEnd = position === -1 ? pointer.length : position;
27-
const segment = unescape(pointer.slice(segmentStart, segmentEnd));
14+
const segment = pointer.slice(segmentStart, segmentEnd);
2815
segmentStart = segmentEnd + 1;
2916

30-
return segment;
31-
};
17+
yield unescape(segment);
18+
}
3219
};
3320

3421
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+
}
3828
};
3929

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);
4635
}
36+
37+
return subject;
4738
};
4839

4940
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+
}
5347
};
5448

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) {
5852
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];
7157
} else if (typeof subject === "object" && subject !== null) {
72-
return { ...subject, [segment]: value };
58+
subject = { ...subject };
7359
} else {
74-
return applySegment(subject, segment, cursor);
60+
applySegment(subject, segment.value, cursor);
7561
}
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;
7667
};
7768

7869
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+
}
8276
};
8377

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;
9291
}
9392
};
9493

9594
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+
}
99101
};
100102

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) {
104106
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];
112111
} 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 };
116113
} else {
117-
return applySegment(subject, segment, cursor);
114+
applySegment(subject, segment.value, cursor);
118115
}
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;
119124
};
120125

121126
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+
}
125133
};
126134

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];
138148
}
139149
};
140150

141-
export const append = curry((segment, pointer) => pointer + "/" + escape(segment));
151+
export const append = (segment, pointer) => pointer + "/" + escape(segment);
142152

143153
const escape = (segment) => segment.toString().replace(/~/g, "~0").replace(/\//g, "~1");
144154
const unescape = (segment) => segment.toString().replace(/~1/g, "/").replace(/~0/g, "~");

package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,5 @@
3030
"eslint-plugin-import": "*",
3131
"typescript": "*",
3232
"vitest": "*"
33-
},
34-
"dependencies": {
35-
"just-curry-it": "^5.3.0"
3633
}
3734
}

0 commit comments

Comments
 (0)