1
- import { JsonConvertible , JsonStructure , JsonValue } from '@croct/json' ;
1
+ import { JsonConvertible } from '@croct/json' ;
2
2
3
3
/**
4
4
* A value that can be converted to a JSON pointer.
@@ -15,6 +15,39 @@ export type JsonPointerSegment = string | number;
15
15
*/
16
16
export type JsonPointerSegments = JsonPointerSegment [ ] ;
17
17
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
+ ? NestedValue < Diff < T , U > , U >
37
+ : T extends Array < infer I >
38
+ ? NestedValue < I , U | T >
39
+ : NestedValue < T [ keyof T ] , U | T >
40
+ : never
41
+ ) ;
42
+
43
+ type Diff < T extends object , M > = M extends infer U
44
+ ? T extends U
45
+ ? Exclude < keyof T , keyof U > extends never
46
+ ? never
47
+ : Pick < T , Exclude < keyof T , keyof U > >
48
+ : never
49
+ : never ;
50
+
18
51
/**
19
52
* An error indicating a problem related to JSON pointer operations.
20
53
*/
@@ -51,7 +84,7 @@ export class InvalidReferenceError extends JsonPointerError {
51
84
/**
52
85
* A key-value pair representing a JSON pointer segment and its value.
53
86
*/
54
- export type Entry = [ JsonPointerSegment | null , JsonValue ] ;
87
+ export type Entry < T > = [ JsonPointerSegment | null , T ] ;
55
88
56
89
/**
57
90
* An RFC 6901-compliant JSON pointer.
@@ -273,15 +306,15 @@ export class JsonPointer implements JsonConvertible {
273
306
/**
274
307
* Returns the value at the referenced location.
275
308
*
276
- * @param {JsonValue } value The value to read from.
309
+ * @param {RootValue } value The value to read from.
277
310
*
278
- * @returns {JsonValue } The value at the referenced location.
311
+ * @returns {ReferencedValue } The value at the referenced location.
279
312
*
280
313
* @throws {InvalidReferenceError } If a numeric segment references a non-array value.
281
314
* @throws {InvalidReferenceError } If a string segment references an array value.
282
315
* @throws {InvalidReferenceError } If there is no value at any level of the pointer.
283
316
*/
284
- public get ( value : JsonValue ) : JsonValue {
317
+ public get < T extends RootValue > ( value : T ) : ReferencedValue < T > {
285
318
const iterator = this . traverse ( value ) ;
286
319
287
320
let result = iterator . next ( ) ;
@@ -304,11 +337,11 @@ export class JsonPointer implements JsonConvertible {
304
337
*
305
338
* This method gracefully handles missing values by returning `false`.
306
339
*
307
- * @param {JsonStructure } root The value to check if the reference exists in.
340
+ * @param {RootValue } root The value to check if the reference exists in.
308
341
*
309
- * @returns {JsonValue } Returns `true` if the value exists, `false` otherwise.
342
+ * @returns {boolean } Returns `true` if the value exists, `false` otherwise.
310
343
*/
311
- public has ( root : JsonStructure ) : boolean {
344
+ public has ( root : RootValue ) : boolean {
312
345
try {
313
346
this . get ( root ) ;
314
347
} catch {
@@ -321,8 +354,8 @@ export class JsonPointer implements JsonConvertible {
321
354
/**
322
355
* Sets the value at the referenced location.
323
356
*
324
- * @param {JsonStructure } root The value to write to.
325
- * @param {JsonValue } value The value to set at the referenced location.
357
+ * @param {RootValue } root The value to write to.
358
+ * @param {unknown } value The value to set at the referenced location.
326
359
*
327
360
* @throws {InvalidReferenceError } If the pointer references the root of the structure.
328
361
* @throws {InvalidReferenceError } If a numeric segment references a non-array value.
@@ -331,17 +364,19 @@ export class JsonPointer implements JsonConvertible {
331
364
* @throws {InvalidReferenceError } If setting the value to an array would cause it to become
332
365
* sparse.
333
366
*/
334
- public set ( root : JsonStructure , value : JsonValue ) : void {
367
+ public set < T extends RootValue > ( root : T , value : unknown ) : void {
335
368
if ( this . isRoot ( ) ) {
336
369
throw new JsonPointerError ( 'Cannot set root value.' ) ;
337
370
}
338
371
339
- const parent = this . getParent ( ) . get ( root ) ;
372
+ const target = this . getParent ( ) . get ( root ) ;
340
373
341
- if ( typeof parent !== 'object' || parent === null ) {
374
+ if ( typeof target !== 'object' || target === null ) {
342
375
throw new JsonPointerError ( `Cannot set value at "${ this . getParent ( ) } ".` ) ;
343
376
}
344
377
378
+ const parent : RootStructure = target ;
379
+
345
380
const segmentIndex = this . segments . length - 1 ;
346
381
const segment = this . segments [ segmentIndex ] ;
347
382
@@ -381,30 +416,32 @@ export class JsonPointer implements JsonConvertible {
381
416
* is a no-op. Pointers referencing array elements remove the element while keeping
382
417
* the array dense.
383
418
*
384
- * @param {JsonStructure } root The value to write to.
419
+ * @param {RootValue } root The value to write to.
385
420
*
386
- * @returns {JsonValue } The unset value, or `undefined` if the referenced location
421
+ * @returns {ReferencedValue|undefined } The unset value, or `undefined` if the referenced location
387
422
* does not exist.
388
423
*
389
424
* @throws {InvalidReferenceError } If the pointer references the root of the root.
390
425
*/
391
- public unset ( root : JsonStructure ) : JsonValue | undefined {
426
+ public unset < T extends RootValue > ( root : T ) : ReferencedValue < T > | undefined {
392
427
if ( this . isRoot ( ) ) {
393
428
throw new InvalidReferenceError ( 'Cannot unset the root value.' ) ;
394
429
}
395
430
396
- let parent : JsonValue ;
431
+ let target : ReferencedValue < T > ;
397
432
398
433
try {
399
- parent = this . getParent ( ) . get ( root ) ;
434
+ target = this . getParent ( ) . get ( root ) ;
400
435
} catch {
401
436
return undefined ;
402
437
}
403
438
404
- if ( typeof parent !== 'object' || parent === null ) {
439
+ if ( typeof target !== 'object' || target === null ) {
405
440
return undefined ;
406
441
}
407
442
443
+ const parent : RootStructure = target ;
444
+
408
445
const segmentIndex = this . segments . length - 1 ;
409
446
const segment = this . segments [ segmentIndex ] ;
410
447
@@ -434,17 +471,17 @@ export class JsonPointer implements JsonConvertible {
434
471
/**
435
472
* Returns an iterator over the stack of values that the pointer references.
436
473
*
437
- * @param {JsonValue } root The value to traverse.
474
+ * @param {RootValue } root The value to traverse.
438
475
*
439
- * @returns {Iterator<JsonPointer > } An iterator over the stack of values that the
476
+ * @returns {Iterator<Entry<ReferencedValue<T> > } An iterator over the stack of values that the
440
477
* pointer references.
441
478
*
442
479
* @throws {InvalidReferenceError } If a numeric segment references a non-array value.
443
480
* @throws {InvalidReferenceError } If a string segment references an array value.
444
481
* @throws {InvalidReferenceError } If there is no value at any level of the pointer.
445
482
*/
446
- public * traverse ( root : JsonValue ) : Iterator < Entry > {
447
- let current : JsonValue = root ;
483
+ public * traverse < T extends RootValue > ( root : T ) : Iterator < Entry < ReferencedValue < T > > > {
484
+ let current : ReferencedValue < T > = root ;
448
485
449
486
yield [ null , current ] ;
450
487
@@ -487,15 +524,13 @@ export class JsonPointer implements JsonConvertible {
487
524
) ;
488
525
}
489
526
490
- const nextValue = current [ segment ] ;
491
-
492
- if ( nextValue === undefined ) {
527
+ if ( ! ( segment in current ) ) {
493
528
throw new InvalidReferenceError (
494
529
`Property "${ segment } " does not exist at "${ this . truncatedAt ( i ) } ".` ,
495
530
) ;
496
531
}
497
532
498
- current = nextValue ;
533
+ current = current [ segment as keyof typeof current ] as ReferencedValue < T > ;
499
534
500
535
yield [ segment , current ] ;
501
536
}
@@ -508,7 +543,7 @@ export class JsonPointer implements JsonConvertible {
508
543
*
509
544
* @returns {boolean } `true` if the pointers are logically equal, `false` otherwise.
510
545
*/
511
- public equals ( other : any ) : other is this {
546
+ public equals ( other : unknown ) : other is JsonPointer {
512
547
if ( this === other ) {
513
548
return true ;
514
549
}
0 commit comments