Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type safe JsonPointer.get #52

Open
brandonryan opened this issue Mar 7, 2022 · 4 comments
Open

Type safe JsonPointer.get #52

brandonryan opened this issue Mar 7, 2022 · 4 comments

Comments

@brandonryan
Copy link

brandonryan commented Mar 7, 2022

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
Copy link
Author

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
Copy link
Author

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>

@amir-arad
Copy link

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

@janwilmake
Copy link

wow this is cool!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants