Skip to content

Commit 1c66d6e

Browse files
committed
Allow manipulating values other than JSON
1 parent 46430d0 commit 1c66d6e

File tree

4 files changed

+125
-82
lines changed

4 files changed

+125
-82
lines changed

src/pointer.ts

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {JsonConvertible, JsonStructure, JsonValue} from '@croct/json';
1+
import {JsonConvertible} from '@croct/json';
22

33
/**
44
* A value that can be converted to a JSON pointer.
@@ -15,6 +15,31 @@ export type JsonPointerSegment = string | number;
1515
*/
1616
export type JsonPointerSegments = JsonPointerSegment[];
1717

18+
/**
19+
* A record or array representing the root of a structure.
20+
*/
21+
export type RootStructure = Record<string | number | symbol, any> | any[];
22+
23+
export type RootValue = any;
24+
25+
/**
26+
* A union of all possible values in a structure.
27+
*/
28+
export type ReferencedValue<T> = NestedValue<T>;
29+
30+
/**
31+
* A union of all possible values in a structure, excluding the given type.
32+
*/
33+
type NestedValue<T, U = never> = T | (
34+
T extends object
35+
? T extends U
36+
? never
37+
: T extends Array<infer I>
38+
? NestedValue<I, U | T>
39+
: T[keyof T] | NestedValue<T[keyof T], U | T>
40+
: never
41+
);
42+
1843
/**
1944
* An error indicating a problem related to JSON pointer operations.
2045
*/
@@ -51,7 +76,7 @@ export class InvalidReferenceError extends JsonPointerError {
5176
/**
5277
* A key-value pair representing a JSON pointer segment and its value.
5378
*/
54-
export type Entry = [JsonPointerSegment | null, JsonValue];
79+
export type Entry<T> = [JsonPointerSegment | null, T];
5580

5681
/**
5782
* An RFC 6901-compliant JSON pointer.
@@ -273,15 +298,15 @@ export class JsonPointer implements JsonConvertible {
273298
/**
274299
* Returns the value at the referenced location.
275300
*
276-
* @param {JsonValue} value The value to read from.
301+
* @param {RootValue} value The value to read from.
277302
*
278-
* @returns {JsonValue} The value at the referenced location.
303+
* @returns {ReferencedValue} The value at the referenced location.
279304
*
280305
* @throws {InvalidReferenceError} If a numeric segment references a non-array value.
281306
* @throws {InvalidReferenceError} If a string segment references an array value.
282307
* @throws {InvalidReferenceError} If there is no value at any level of the pointer.
283308
*/
284-
public get(value: JsonValue): JsonValue {
309+
public get<T extends RootValue>(value: T): ReferencedValue<T> {
285310
const iterator = this.traverse(value);
286311

287312
let result = iterator.next();
@@ -304,11 +329,11 @@ export class JsonPointer implements JsonConvertible {
304329
*
305330
* This method gracefully handles missing values by returning `false`.
306331
*
307-
* @param {JsonStructure} root The value to check if the reference exists in.
332+
* @param {RootValue} root The value to check if the reference exists in.
308333
*
309-
* @returns {JsonValue} Returns `true` if the value exists, `false` otherwise.
334+
* @returns {boolean} Returns `true` if the value exists, `false` otherwise.
310335
*/
311-
public has(root: JsonStructure): boolean {
336+
public has(root: RootValue): boolean {
312337
try {
313338
this.get(root);
314339
} catch {
@@ -321,8 +346,8 @@ export class JsonPointer implements JsonConvertible {
321346
/**
322347
* Sets the value at the referenced location.
323348
*
324-
* @param {JsonStructure} root The value to write to.
325-
* @param {JsonValue} value The value to set at the referenced location.
349+
* @param {RootValue} root The value to write to.
350+
* @param {unknown} value The value to set at the referenced location.
326351
*
327352
* @throws {InvalidReferenceError} If the pointer references the root of the structure.
328353
* @throws {InvalidReferenceError} If a numeric segment references a non-array value.
@@ -331,17 +356,19 @@ export class JsonPointer implements JsonConvertible {
331356
* @throws {InvalidReferenceError} If setting the value to an array would cause it to become
332357
* sparse.
333358
*/
334-
public set(root: JsonStructure, value: JsonValue): void {
359+
public set<T extends RootValue>(root: T, value: unknown): void {
335360
if (this.isRoot()) {
336361
throw new JsonPointerError('Cannot set root value.');
337362
}
338363

339-
const parent = this.getParent().get(root);
364+
const target = this.getParent().get(root);
340365

341-
if (typeof parent !== 'object' || parent === null) {
366+
if (typeof target !== 'object' || target === null) {
342367
throw new JsonPointerError(`Cannot set value at "${this.getParent()}".`);
343368
}
344369

370+
const parent: RootStructure = target;
371+
345372
const segmentIndex = this.segments.length - 1;
346373
const segment = this.segments[segmentIndex];
347374

@@ -381,30 +408,32 @@ export class JsonPointer implements JsonConvertible {
381408
* is a no-op. Pointers referencing array elements remove the element while keeping
382409
* the array dense.
383410
*
384-
* @param {JsonStructure} root The value to write to.
411+
* @param {RootValue} root The value to write to.
385412
*
386-
* @returns {JsonValue} The unset value, or `undefined` if the referenced location
413+
* @returns {ReferencedValue|undefined} The unset value, or `undefined` if the referenced location
387414
* does not exist.
388415
*
389416
* @throws {InvalidReferenceError} If the pointer references the root of the root.
390417
*/
391-
public unset(root: JsonStructure): JsonValue | undefined {
418+
public unset<T extends RootValue>(root: T): ReferencedValue<T> | undefined {
392419
if (this.isRoot()) {
393420
throw new InvalidReferenceError('Cannot unset the root value.');
394421
}
395422

396-
let parent: JsonValue;
423+
let target: ReferencedValue<T>;
397424

398425
try {
399-
parent = this.getParent().get(root);
426+
target = this.getParent().get(root);
400427
} catch {
401428
return undefined;
402429
}
403430

404-
if (typeof parent !== 'object' || parent === null) {
431+
if (typeof target !== 'object' || target === null) {
405432
return undefined;
406433
}
407434

435+
const parent: RootStructure = target;
436+
408437
const segmentIndex = this.segments.length - 1;
409438
const segment = this.segments[segmentIndex];
410439

@@ -434,17 +463,17 @@ export class JsonPointer implements JsonConvertible {
434463
/**
435464
* Returns an iterator over the stack of values that the pointer references.
436465
*
437-
* @param {JsonValue} root The value to traverse.
466+
* @param {RootValue} root The value to traverse.
438467
*
439-
* @returns {Iterator<JsonPointer>} An iterator over the stack of values that the
468+
* @returns {Iterator<Entry<ReferencedValue<T>>} An iterator over the stack of values that the
440469
* pointer references.
441470
*
442471
* @throws {InvalidReferenceError} If a numeric segment references a non-array value.
443472
* @throws {InvalidReferenceError} If a string segment references an array value.
444473
* @throws {InvalidReferenceError} If there is no value at any level of the pointer.
445474
*/
446-
public* traverse(root: JsonValue): Iterator<Entry> {
447-
let current: JsonValue = root;
475+
public* traverse<T extends RootValue>(root: T): Iterator<Entry<ReferencedValue<T>>> {
476+
let current: ReferencedValue<T> = root;
448477

449478
yield [null, current];
450479

@@ -487,15 +516,13 @@ export class JsonPointer implements JsonConvertible {
487516
);
488517
}
489518

490-
const nextValue = current[segment];
491-
492-
if (nextValue === undefined) {
519+
if (!(segment in current)) {
493520
throw new InvalidReferenceError(
494521
`Property "${segment}" does not exist at "${this.truncatedAt(i)}".`,
495522
);
496523
}
497524

498-
current = nextValue;
525+
current = current[segment as keyof typeof current] as ReferencedValue<T>;
499526

500527
yield [segment, current];
501528
}
@@ -508,7 +535,7 @@ export class JsonPointer implements JsonConvertible {
508535
*
509536
* @returns {boolean} `true` if the pointers are logically equal, `false` otherwise.
510537
*/
511-
public equals(other: any): other is this {
538+
public equals(other: unknown): other is JsonPointer {
512539
if (this === other) {
513540
return true;
514541
}

src/relativePointer.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {JsonConvertible, JsonStructure, JsonValue} from '@croct/json';
1+
import {JsonConvertible, JsonStructure} from '@croct/json';
22
import {
33
JsonPointer,
44
JsonPointerSegments,
@@ -8,6 +8,9 @@ import {
88
JsonPointerLike,
99
Entry,
1010
InvalidReferenceError,
11+
RootStructure,
12+
ReferencedValue,
13+
RootValue,
1114
} from './pointer';
1215

1316
/**
@@ -245,18 +248,18 @@ export class JsonRelativePointer implements JsonConvertible {
245248
/**
246249
* Returns the value at the referenced location.
247250
*
248-
* @param {JsonValue} root The value to read from.
251+
* @param {RootValue} root The value to read from.
249252
* @param {JsonPointer} pointer The base pointer to resolve the current pointer against.
250253
*
251-
* @returns {JsonValue} The value at the referenced location.
254+
* @returns {ReferencedValue|JsonPointerSegment} The value at the referenced location.
252255
*
253256
* @throws {InvalidReferenceError} If a numeric segment references a non-array value.
254257
* @throws {InvalidReferenceError} If a string segment references an array value.
255258
* @throws {InvalidReferenceError} If an array index is out of bounds.
256259
* @throws {InvalidReferenceError} If there is no value at any level of the pointer.
257260
* @throws {InvalidReferenceError} If the pointer references the key of the root value.
258261
*/
259-
public get(root: JsonValue, pointer = JsonPointer.root()): JsonValue {
262+
public get<T extends RootValue>(root: T, pointer = JsonPointer.root()): ReferencedValue<T>|JsonPointerSegment {
260263
const stack = this.getReferenceStack(root, pointer);
261264
const [segment, value] = stack[stack.length - 1];
262265

@@ -268,20 +271,20 @@ export class JsonRelativePointer implements JsonConvertible {
268271
return segment;
269272
}
270273

271-
return this.getRemainderPointer().get(value);
274+
return this.getRemainderPointer().get(value as T);
272275
}
273276

274277
/**
275278
* Checks whether the value at the referenced location exists.
276279
*
277280
* This method gracefully handles missing values by returning `false`.
278281
*
279-
* @param {JsonValue} root The value to check if the reference exists in.
282+
* @param {RootValue} root The value to check if the reference exists in.
280283
* @param {JsonPointer} pointer The base pointer to resolve the current pointer against.
281284
*
282-
* @returns {JsonValue} Returns `true` if the value exists, `false` otherwise.
285+
* @returns {boolean} Returns `true` if the value exists, `false` otherwise.
283286
*/
284-
public has(root: JsonValue, pointer: JsonPointer = JsonPointer.root()): boolean {
287+
public has(root: RootValue, pointer: JsonPointer = JsonPointer.root()): boolean {
285288
try {
286289
this.get(root, pointer);
287290
} catch {
@@ -294,8 +297,8 @@ export class JsonRelativePointer implements JsonConvertible {
294297
/**
295298
* Sets the value at the referenced location.
296299
*
297-
* @param {JsonValue} root The value to write to.
298-
* @param {JsonValue} value The value to set at the referenced location.
300+
* @param {RootValue} root The value to write to.
301+
* @param {unknown} value The value to set at the referenced location.
299302
* @param {JsonPointer} pointer The base pointer to resolve the current pointer against.
300303
*
301304
* @throws {InvalidReferenceError} If the pointer references the root of the structure.
@@ -306,7 +309,7 @@ export class JsonRelativePointer implements JsonConvertible {
306309
* @throws {InvalidReferenceError} If setting the value to an array would cause it to become
307310
* sparse.
308311
*/
309-
public set(root: JsonValue, value: JsonValue, pointer = JsonPointer.root()): void {
312+
public set(root: RootValue, value: unknown, pointer = JsonPointer.root()): void {
310313
if (this.isKeyPointer()) {
311314
throw new JsonPointerError('Cannot write to a key.');
312315
}
@@ -337,15 +340,15 @@ export class JsonRelativePointer implements JsonConvertible {
337340
* is a no-op. Pointers referencing array elements remove the element while keeping
338341
* the array dense.
339342
*
340-
* @param {JsonValue} root The value to write to.
343+
* @param {RootValue} root The value to write to.
341344
* @param {JsonPointer} pointer The base pointer to resolve the current pointer against.
342345
*
343346
* @returns {JsonValue} The unset value, or `undefined` if the referenced location
344347
* does not exist.
345348
*
346349
* @throws {InvalidReferenceError} If the pointer references the root of the structure.
347350
*/
348-
public unset(root: JsonValue, pointer = JsonPointer.root()): JsonValue | undefined {
351+
public unset<T extends RootStructure>(root: T, pointer = JsonPointer.root()): ReferencedValue<T> | undefined {
349352
if (this.isKeyPointer()) {
350353
throw new JsonPointerError('Cannot write to a key.');
351354
}
@@ -354,36 +357,36 @@ export class JsonRelativePointer implements JsonConvertible {
354357
const remainderPointer = this.getRemainderPointer();
355358

356359
if (!remainderPointer.isRoot()) {
357-
return remainderPointer.unset(stack[stack.length - 1][1] as JsonStructure);
360+
return remainderPointer.unset(stack[stack.length - 1][1]);
358361
}
359362

360363
if (stack.length < 2) {
361364
throw new JsonPointerError('Cannot unset the root value.');
362365
}
363366

364367
const segment = stack[stack.length - 1][0]!;
365-
const structure = stack[stack.length - 2][1] as JsonStructure;
368+
const structure = stack[stack.length - 2][1];
366369

367370
return JsonPointer.from([segment]).unset(structure);
368371
}
369372

370373
/**
371374
* Returns the stack of references to the value at the referenced location.
372375
*
373-
* @param {JsonValue} root The value to read from.
376+
* @param {RootValue} root The value to read from.
374377
* @param {JsonPointer} pointer The base pointer to resolve the current pointer against.
375378
*
376-
* @returns {Entry[]} The list of entries in top-down order.
379+
* @returns {Entry<ReferencedValue>[]} The list of entries in top-down order.
377380
*
378381
* @throws {InvalidReferenceError} If a numeric segment references a non-array value.
379382
* @throws {InvalidReferenceError} If a string segment references an array value.
380383
* @throws {InvalidReferenceError} If an array index is out of bounds.
381384
* @throws {InvalidReferenceError} If there is no value at any level of the pointer.
382385
*/
383-
private getReferenceStack(root: JsonValue, pointer: JsonPointer): Entry[] {
386+
private getReferenceStack<T extends RootValue>(root: T, pointer: JsonPointer): Array<Entry<ReferencedValue<T>>> {
384387
const iterator = pointer.traverse(root);
385388
let current = iterator.next();
386-
const stack: Entry[] = [];
389+
const stack: Array<Entry<ReferencedValue<T>>> = [];
387390

388391
while (current.done === false) {
389392
stack.push(current.value);
@@ -436,7 +439,7 @@ export class JsonRelativePointer implements JsonConvertible {
436439
*
437440
* @returns {boolean} `true` if the pointers are logically equal, `false` otherwise.
438441
*/
439-
public equals(other: any): other is this {
442+
public equals(other: any): other is JsonRelativePointer {
440443
if (this === other) {
441444
return true;
442445
}

0 commit comments

Comments
 (0)