如何正确构建与TypeScript类型的键匹配的键数组?

huangapple go评论58阅读模式
英文:

How to properly build an array of keys which match a type's keys in TypeScript?

问题

你可以尝试使用 TypeScript 的类型推断来正确类型化传递给.select方法的键。这里是一个可能的解决方案:

首先,你可以定义一个类型,该类型从MyTable中提取有效的键:

type ValidKeys<T> = Extract<keyof T, string>;

然后,在你的代码中,你可以使用这个类型来确保selectKeys仅包含有效的键:

const selectKeys = Object.keys(json) as ValidKeys<MyTable>[];

这将确保selectKeys 中的键与 MyTable 中的属性匹配,从而正确类型化传递给.select方法的参数。

你不再需要 getKeys 函数,因为 TypeScript 的类型系统将在编译时强制执行类型检查,确保传递的键是有效的。

这个解决方案应该能够帮助你避免类型错误,并正确地类型化传递给 .select 方法的键。

英文:

I am using the Kysely SQL builder for JS (as per Vercel's recommendation, although the docs/community is sparse). It is a fully typed SQL builder. You construct a db object with a schema, and when you do queries it recognizes the table names and attributes (pretty fancy if I do say so myself).

import &#39;dotenv/config&#39;
import { createKysely } from &#39;@vercel/postgres-kysely&#39;
import { DB } from &#39;kysely-codegen&#39;

export const db = createKysely&lt;DB&gt;()
export { sql } from &#39;kysely&#39;

The DB is generated from the PostgreSQL schema directly, and stored in the kysely-codegen node_modules folder. It looks like this (short snippet):

export interface MyTable {
  id: string
  foo: string
  bar: boolean
}

export interface DB {
  my_table: MyTable
}

Now, my question revolves around using the select function, which takes an array of keys of MyTable (when querying my_table).

const record = await db
  .selectFrom(&#39;my_table&#39;)
  .select([&#39;foo&#39;, &#39;id&#39;])
  .executeTakeFirst()

That works fine. But it doesn't work when I do this:

// simulate not knowing what the input is
// as if you were making a browser JSON API request
const json = JSON.parse(fs.readFileSync(&#39;test.json&#39;))
const selectKeys = Object.keys(json)

const record = await db
  .selectFrom(&#39;my_table&#39;)
  .select(selectKeys)
  .executeTakeFirst()

I get essentially this error:

> Argument of type 'string[]' is not assignable to parameter of type 'SelectArg<DB, "my_table", SelectExpression<DB, "my_table">>'.

I can fix it like this:

const record = await db
  .selectFrom(&#39;my_table&#39;)
  .select(selectKeys as Array&lt;keyof MyTable&gt;)
  .executeTakeFirst()

I can also guarantee somewhere earlier in the code that the selectKeys is built with the keys of my_table, by doing something like this:

const MY_TABLE_KEYS: Array&lt;keyof MyTable&gt; = [&#39;id&#39;, &#39;foo&#39;, &#39;bar&#39;]

function getKeys(json) {
  const keys = []
  for (const key in json) {
    if (MY_TABLE_KEYS.includes(key)) {
      keys.push(key)
    }
  }
  return keys
}

I have basically duplicated/copied the keys from the DB interface into an array, but there are several problems with the getKeys function, not sure a way around it. A rough TS playground is here demonstrating some of the challenges I'm facing.

The question is, how can I properly type the keys to pass to the .select method?

答案1

得分: 1

以下是您要翻译的内容:

你可能正在寻找一种类型谓词/类型保护。类型谓词是一种函数,如果函数返回 true,则断言/声明参数是特定类型。在这种情况下,一个简单的谓词可能如下所示:

(key: string): key is keyof MyTable => { return true; }

在上面的简单示例中,我始终返回 true。要使其有用,您应该检查键是否实际上是您已知的键之一。您可以使用下面的示例来获取您要查找的内容。

interface MyTable {
	id: string;
	foo: string;
	bar: boolean;
}

const json: Partial<MyTable> = { id: "hello", foo: "world" };

// 从 json 中获取键,然后过滤掉所有非 MyTable 键
// 我们的过滤器是一个类型谓词
const keys = Object.keys(json).filter((key): key is keyof MyTable =>
	["id", "foo", "bar"].includes(key),
);

const record: Partial<MyTable> = {};
keys.forEach((key) => (record[key] = json[key]));

您会注意到,record[key] = json[key] 仍然存在一些问题,但这是因为 json[key] 可能为 undefined(因为它是部分类型)。

(请注意,我只提供了翻译,没有回答其他问题。)

英文:

You might be looking for a type predicate / type guard. Type predicates are functions that assert/declare that an argument is a specific type if the function returns true. In this case a simple predicate might look like

(key: string): key is keyof MyTable =&gt; { return true; }

In the simple example above, I always return true. To make this useful you would wan't to check if the key is actually one of the keys you know about. You can use this example below to get what you're looking for.

interface MyTable {
	id: string;
	foo: string;
	bar: boolean;
}

const json: Partial&lt;MyTable&gt; = { id: &quot;hello&quot;, foo: &quot;world&quot; };

// Get keys from json, then filter out all non-MyTable-keys
// our filter is a predicate
const keys = Object.keys(json).filter((key): key is keyof MyTable =&gt;
	[&quot;id&quot;, &quot;foo&quot;, &quot;bar&quot;].includes(key),
);

const record: Partial&lt;MyTable&gt; = {};
keys.forEach((key) =&gt; (record[key] = json[key]));

You'll notice that you still have some issues with the record[key] = json[key] but that's because json[key] may be undefined (because it's a partial);

huangapple
  • 本文由 发表于 2023年5月18日 12:21:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76277708.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定