打字稿 JSON 路径类型

时间:2021-03-15 16:19:00

标签: typescript generics tuples typescript-generics jsonpath

我正在尝试构建一些类型,以便我可以定义一个通用函数

  • 接受一个对象和一个 JSON 路径
    (暂时可以忽略数组)
  • 自动推导出正确的返回类型
  • IDE-AutoComplete 提示所有可能的路径

我已经成功地完成了第一个项目,但是,我无法获得任何自动完成提示。

我尝试构建一个工作类型,该类型创建所有有效路径的联合。 但这就是我卡住的地方。

这是我目前得到的

// helper types
type Length<T> = T extends { length: infer L } ? L : never;

type PopFront<T extends unknown[]> = T extends [infer U, ...infer R] ? U : never;
type Shift<T extends unknown[]> = T extends [infer U, ...infer R] ? R : never;

type Filter<T extends unknown[], U> = T extends [] ? [] : T extends [infer F, ...infer R] ? F extends U ? Filter<R, T> : [F, ...Filter<R, U>] : never
type TupleIncludes<T extends unknown[], U> = Length<Filter<T, U>> extends Length<T> ? false : true
type StringIncludes<S extends string, D extends string> = S extends `${infer T}${D}${infer U}` ? true : false; 

type Includes<T extends unknown[]|string, U> = T extends unknown[] ? TupleIncludes<T, U> : T extends string ? U extends string ? StringIncludes<T, U> : never : never
type Join<T extends unknown[], D extends string> = PopFront<T> extends string ? Length<T> extends 1 ? `${PopFront<T>}` : Shift<T> extends string[] ? `${PopFront<T>}${D}${Join<Shift<T>, D>}` : never : never; 

type Split<S extends string, D extends string> =
    string extends S ? string[] :
        S extends '' ? [] :
            S extends `${infer T}${D}${infer U}` ?  [T, ...Split<U, D>] : [S];

// return type deduction from path
type NestedType<T, P extends string> = (
  Includes<P, '.'> extends true
    ? PopFront<Split<P, '.'>> extends keyof T
      ? NestedType<T[PopFront<Split<P, '.'>>], Join<Shift<Split<P, '.'>>, '.'>>
      : never
    : P extends keyof T ? T[P] : never
);

// Demo

const dictionary = {
  someProp: 123,
  nested: {
    moreProps: 333,
    deeper: {
      evenDeeper: {
        deepest: 'str'
      }
    },
    alsoDeeper: {
      randomProp: {
        anotherProp: 'yay'
      }
    }
  }
} as const;
type MyDict = typeof dictionary;

type Test1 = NestedType<MyDict, 'nested.alsoDeeper.randomProp.anotherProp'>; // = yay

const Fn = <T, P extends string>(dict: T, path: P): NestedType<T, P> => {
  // skip impl.
  return undefined as any;
};

const testFromFn = Fn(dictionary, 'nested.moreProps'); // = 333

上面的代码都很好,现在到我尝试构建的类型, 这应该给我所有有效的路径:

// If you look at type "Debug" below, this surprisingly works.
// ...apart from the `Type instantiation is excessively deep and possibly infinite.`-error
type ValidPaths<T> = keyof T extends never ? never : ({
     [K in keyof T]: T[K] extends never ? never : T[K] extends Record<string|number|symbol, unknown>
       ? K extends string ? `${K}.${ValidPaths<T[K]>}` | K : never
       : K
   })[keyof T];
type Debug = ValidPaths<MyDict>;

// So I also tried with Tuples.
type ValidPathTuples<T> = keyof T extends never ? never : ({
     [K in keyof T]: T[K] extends never ? never : T[K] extends Record<string|number|symbol, unknown>
       ? [K, ...ValidPathTuples<T[K]>] | [K]
       : [K]
   })[keyof T];
// Which seems fine, too!
type DebugTuples = ValidPathTuples<MyDict>;
// But now, I cannot join them to path-strings
type DebugTuples1 = Join<ValidPathTuples<MyDict>, '.'>;

我到底错过了什么,还是我走错了路?
为什么元组版本可以正常工作,而使用字符串文字模板的版本却不能?

Playground

编辑:

我刚刚想到了一些东西。

我把我的加入类型改成了这个

type Join<T extends unknown[], D extends string> = T extends string[]
  ? PopFront<T> extends string ? Length<T> extends 1 ? `${PopFront<T>}` : `${PopFront<T>}${D}${Join<Shift<T>, D>}` : never
  : never;

现在 Join<ValidPathTuples, '.'> 产生正确的结果。 唯一要做的就是删除 Shift<T> extends string[] ? trueExpr : falseExpr; 并使用已删除的 trueExpr

我真的不明白这让¯\_(ツ)_/¯ 有什么不同

0 个答案:

没有答案