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,39 @@ export type JsonPointerSegment = string | number;
1515 */
1616export 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+ ? 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+
1851/**
1952 * An error indicating a problem related to JSON pointer operations.
2053 */
@@ -51,7 +84,7 @@ export class InvalidReferenceError extends JsonPointerError {
5184/**
5285 * A key-value pair representing a JSON pointer segment and its value.
5386 */
54- export type Entry = [ JsonPointerSegment | null , JsonValue ] ;
87+ export type Entry < T > = [ JsonPointerSegment | null , T ] ;
5588
5689/**
5790 * An RFC 6901-compliant JSON pointer.
@@ -273,15 +306,15 @@ export class JsonPointer implements JsonConvertible {
273306 /**
274307 * Returns the value at the referenced location.
275308 *
276- * @param {JsonValue } value The value to read from.
309+ * @param {RootValue } value The value to read from.
277310 *
278- * @returns {JsonValue } The value at the referenced location.
311+ * @returns {ReferencedValue } The value at the referenced location.
279312 *
280313 * @throws {InvalidReferenceError } If a numeric segment references a non-array value.
281314 * @throws {InvalidReferenceError } If a string segment references an array value.
282315 * @throws {InvalidReferenceError } If there is no value at any level of the pointer.
283316 */
284- public get ( value : JsonValue ) : JsonValue {
317+ public get < T extends RootValue > ( value : T ) : ReferencedValue < T > {
285318 const iterator = this . traverse ( value ) ;
286319
287320 let result = iterator . next ( ) ;
@@ -304,11 +337,11 @@ export class JsonPointer implements JsonConvertible {
304337 *
305338 * This method gracefully handles missing values by returning `false`.
306339 *
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.
308341 *
309- * @returns {JsonValue } Returns `true` if the value exists, `false` otherwise.
342+ * @returns {boolean } Returns `true` if the value exists, `false` otherwise.
310343 */
311- public has ( root : JsonStructure ) : boolean {
344+ public has ( root : RootValue ) : boolean {
312345 try {
313346 this . get ( root ) ;
314347 } catch {
@@ -321,8 +354,8 @@ export class JsonPointer implements JsonConvertible {
321354 /**
322355 * Sets the value at the referenced location.
323356 *
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.
326359 *
327360 * @throws {InvalidReferenceError } If the pointer references the root of the structure.
328361 * @throws {InvalidReferenceError } If a numeric segment references a non-array value.
@@ -331,17 +364,19 @@ export class JsonPointer implements JsonConvertible {
331364 * @throws {InvalidReferenceError } If setting the value to an array would cause it to become
332365 * sparse.
333366 */
334- public set ( root : JsonStructure , value : JsonValue ) : void {
367+ public set < T extends RootValue > ( root : T , value : unknown ) : void {
335368 if ( this . isRoot ( ) ) {
336369 throw new JsonPointerError ( 'Cannot set root value.' ) ;
337370 }
338371
339- const parent = this . getParent ( ) . get ( root ) ;
372+ const target = this . getParent ( ) . get ( root ) ;
340373
341- if ( typeof parent !== 'object' || parent === null ) {
374+ if ( typeof target !== 'object' || target === null ) {
342375 throw new JsonPointerError ( `Cannot set value at "${ this . getParent ( ) } ".` ) ;
343376 }
344377
378+ const parent : RootStructure = target ;
379+
345380 const segmentIndex = this . segments . length - 1 ;
346381 const segment = this . segments [ segmentIndex ] ;
347382
@@ -381,30 +416,32 @@ export class JsonPointer implements JsonConvertible {
381416 * is a no-op. Pointers referencing array elements remove the element while keeping
382417 * the array dense.
383418 *
384- * @param {JsonStructure } root The value to write to.
419+ * @param {RootValue } root The value to write to.
385420 *
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
387422 * does not exist.
388423 *
389424 * @throws {InvalidReferenceError } If the pointer references the root of the root.
390425 */
391- public unset ( root : JsonStructure ) : JsonValue | undefined {
426+ public unset < T extends RootValue > ( root : T ) : ReferencedValue < T > | undefined {
392427 if ( this . isRoot ( ) ) {
393428 throw new InvalidReferenceError ( 'Cannot unset the root value.' ) ;
394429 }
395430
396- let parent : JsonValue ;
431+ let target : ReferencedValue < T > ;
397432
398433 try {
399- parent = this . getParent ( ) . get ( root ) ;
434+ target = this . getParent ( ) . get ( root ) ;
400435 } catch {
401436 return undefined ;
402437 }
403438
404- if ( typeof parent !== 'object' || parent === null ) {
439+ if ( typeof target !== 'object' || target === null ) {
405440 return undefined ;
406441 }
407442
443+ const parent : RootStructure = target ;
444+
408445 const segmentIndex = this . segments . length - 1 ;
409446 const segment = this . segments [ segmentIndex ] ;
410447
@@ -434,17 +471,17 @@ export class JsonPointer implements JsonConvertible {
434471 /**
435472 * Returns an iterator over the stack of values that the pointer references.
436473 *
437- * @param {JsonValue } root The value to traverse.
474+ * @param {RootValue } root The value to traverse.
438475 *
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
440477 * pointer references.
441478 *
442479 * @throws {InvalidReferenceError } If a numeric segment references a non-array value.
443480 * @throws {InvalidReferenceError } If a string segment references an array value.
444481 * @throws {InvalidReferenceError } If there is no value at any level of the pointer.
445482 */
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 ;
448485
449486 yield [ null , current ] ;
450487
@@ -487,15 +524,13 @@ export class JsonPointer implements JsonConvertible {
487524 ) ;
488525 }
489526
490- const nextValue = current [ segment ] ;
491-
492- if ( nextValue === undefined ) {
527+ if ( ! ( segment in current ) ) {
493528 throw new InvalidReferenceError (
494529 `Property "${ segment } " does not exist at "${ this . truncatedAt ( i ) } ".` ,
495530 ) ;
496531 }
497532
498- current = nextValue ;
533+ current = current [ segment as keyof typeof current ] as ReferencedValue < T > ;
499534
500535 yield [ segment , current ] ;
501536 }
@@ -508,7 +543,7 @@ export class JsonPointer implements JsonConvertible {
508543 *
509544 * @returns {boolean } `true` if the pointers are logically equal, `false` otherwise.
510545 */
511- public equals ( other : any ) : other is this {
546+ public equals ( other : unknown ) : other is JsonPointer {
512547 if ( this === other ) {
513548 return true ;
514549 }
0 commit comments