json-ptr icon indicating copy to clipboard operation
json-ptr copied to clipboard

Type safe JsonPointer.get

Open brandonryan opened this issue 2 years ago • 4 comments

I made this type because I wanted a type safe way to pull properties from an object via a path, and I thought it might be useful to this library. I chose to make any properties not found in the object never, but you could use unknown to keep current functionality if you wanted. Just thought I'd share.

type TakeProp<T extends string> = T extends `${infer Prop}/${string}` ? Prop : never

type TakeRest<T extends string> = T extends `${string}/${infer Rest}` ? Rest : T

type PathExtract<Value, Path extends string> = 
    TakeProp<Path> extends never //if path type is not a path, get the prop from Value
        ? Path extends keyof Value
            ? Value[Path]
            : never
        : TakeProp<Path> extends keyof Value //check if prop is in value
            ? PathExtract<Value[TakeProp<Path>], TakeRest<Path>> //recurse
            : never //failure
            
const num: number = PathExtract<{a: {b: number}}, "a/b">

This currently does not cover ~0 and ~1 but i think with a little more work that could be figured out.

brandonryan avatar Mar 07 '22 21:03 brandonryan

Updated version that allows for number indexed types (like array)

type TakeProp<T extends string> = T extends `${infer Prop}/${string}` ? Prop : never
type TakeRest<T extends string> = T extends `${string}/${infer Rest}` ? Rest : T
export type PathExtract<Value, Path extends string> = 
	TakeProp<Path> extends never ? //if path type is not a path, get the prop from Value
		Path extends `${number}` ? //if we are dealing with a number index
			number extends keyof Value ? Value[number] : unknown
		: Path extends keyof Value ? Value[Path] : unknown //otherwise just regular prop get
	: TakeProp<Path> extends `${number}` ? //try to see if prop is a number
		number extends keyof Value ?
			PathExtract<Value[number], TakeRest<Path>>
		: unknown
	: TakeProp<Path> extends keyof Value ? //check if prop is in value
		PathExtract<Value[TakeProp<Path>], TakeRest<Path>> //recurse
	: unknown //failure

brandonryan avatar Mar 09 '22 18:03 brandonryan

Finalized version. This one can handle decoding as well. Also cleaned up the types

//This is a big utility type to let us path walk a type with a string
export type SplitPath<T extends string> = T extends `${infer Prop}/${infer Rest}` ? [Prop, Rest] : [unknown, T]

//Deal with json pointer encoding
export type Decode<T extends string> = 
    T extends `${infer A}~0${infer B}` ? `${Decode<A>}~${Decode<B>}` :
    T extends `${infer A}~1${infer B}` ? `${Decode<A>}/${Decode<B>}` :
    T

export type ExtractProp<Value, Prop extends string> = 
    //try to see if prop is a number
    Prop extends `${number}` ? 
        number extends keyof Value ? Value[number] :
        unknown :
    //check if prop is in value
	Decode<Prop> extends keyof Value ? Value[Decode<Prop>] : unknown

export type PathExtract<Value, Path extends string> =
    Extract<Value, SplitPath<Path>[0], SplitPath<Path>[1]>

export type Extract<Value, Prop, Rest extends string> = 
    Prop extends string ? PathExtract<ExtractProp<Value, Prop>, Rest> :
    ExtractProp<Value, Rest>

brandonryan avatar Apr 04 '22 18:04 brandonryan

amazing job! so we have template literals now in TS? been waiting for soo long!

amir-arad avatar Aug 03 '22 07:08 amir-arad

wow this is cool!

CodeFromAnywhere avatar Aug 04 '24 11:08 CodeFromAnywhere