【问题标题】:Best Typescript Typing for JSON String { String : String[] }JSON String { String : String[] } 的最佳打字稿
【发布时间】:2021-10-31 09:40:36
【问题描述】:

我有一个 JSON 文件,想在 typescript 中使用,但不太确定如何定义类型/接口。

它基本上定义了虚拟键盘的布局。所以它会有一个layoutName,然后每一行都会有一个名字。

布局名称(例如“默认”、“表情符号”)。我想成为一个可以是任何值的通用字符串,所以我在考虑记录类型。行名应该一致,但它们必须动态访问,所以我认为它们也应该是记录?

这是 JSON 文件中的一个示例。

{
  "default": {
    "row1": [
      "1",
      "2",
      "3",
      "4",
      "5",
      "6",
      "7",
      "8",
      "9",
      "0",
      "-",
      "=",
      "Backspace"
    ],
    "row1_shift": [
      "!",
      "@",
      "#",
      "$",
      "%",
      "^",
      "&",
      "*",
      "(",
      ")",
      "_",
      "+",
      "Backspace"
    ],
},
"emoji": {
  "row1" : []
}

谢谢

【问题讨论】:

  • 所有行都是always required 还是optional?你能说出行名是什么吗?
  • 它们在技术上是可选的,但我认为“可选”意味着“可以是空集合”。我目前有interface KeyboardLayout { row1: string[]; row1_shift: string[]; row2: string[]; row2_shift: string[]; row3: string[]; row3_shift: string[]; row4: string[]; row4_shift: string[]; row5: string[]; row5_shift: string[]; } interface KeyboardLayouts { default: KeyboardLayout; ... } 虽然可能有任意数量的布局,而不仅仅是“默认”。行和布局名称都需要能够动态访问。
  • 你能解释一下你所说的“动态”是什么意思吗?也许有一个minimal reproducible example 你会如何使用它?
  • 可以这样称呼:let currentRow = Application.keyboard[layout]["row" + index + modifier] 其中index = number,修饰符类似于“_shift”。一个 getProperty 函数,它可能是一个更好的解决方案,或者是一个类型,以便我可以简单地使用 [key] 访问所有内容
  • 那么like this?看看编译器如何判断模板字面量是正确的类型来索引到virtualKeyboard[layout]

标签: typescript


【解决方案1】:

所以我认为您确实想使用string index signature 作为界面的顶级键。但是,对于下一个级别,如果您有一组已知键,您可能应该使用它们,即使您必须动态访问属性。例如:

type RowNames = `row${1 | 2 | 3 | 4 | 5}${"" | "_shift"}`;
interface VirtualKeyboard {
  [layoutName: string]: Partial<Record<RowNames, string[]>>;
}

这相当于

/* type VirtualKeyboard = {
    [x: string]: {
        row1?: string[] | undefined;
        row1_shift?: string[] | undefined;
        row2?: string[] | undefined;
        row2_shift?: string[] | undefined;
        row3?: string[] | undefined;
        row3_shift?: string[] | undefined;
        row4?: string[] | undefined;
        row4_shift?: string[] | undefined;
        row5?: string[] | undefined;
        row5_shift?: string[] | undefined;
    };
} */

我只是用template literal types简洁地表达RowNames,用the Partial&lt;T&gt;和the Record&lt;K, V&gt; utility types表达子属性类型不重复。但其实是一样的。


然后,在读取属性时,您可以这样做:

const virtualKeyboard: VirtualKeyboard = {/*...*/}

for (const layout in virtualKeyboard) {
  for (const index of [1, 2, 3, 4, 5] as const) {
    for (const modifier of ["", "_shift"] as const) {
      let currentRow = virtualKeyboard[layout][`row${index}${modifier}`];
      if (!currentRow) continue;
      for (const key of currentRow) {
        console.log(key.toUpperCase());
      }
    }
  }
}

第一个循环遍历virtualKeyboard 的所有键。密钥layout 是string 类型,可以用它来索引virtualKeyboard,因为它有一个string 索引签名。对于下一个循环,我使用const-asserted 索引和修饰符数组,以便编译器知道它们分别是1 | 2 | 3 | 4 | 5 和"" | "_shift" 类型。

此时您可以使用实际的template literal value `row${index}${modifier}` 来索引virtualKeyboard[layout]。编译器能够将其视为模板文字 type (由于improvements in TS 4.3;在此之前您可能还需要as const),因此它认为键是子属性的已知键。

这意味着它知道currentRow 是string[] | undefined 类型(它可能是undefined,因为定义中的Partial&lt;&gt;。如果你知道它永远不会是undefined,那么你可以离开出Partial&lt;&gt;)。因此,一旦我们检查了 undefined,我们就可以对其进行迭代,编译器预计每个 key 将是一个 string。

Playground link to code

【讨论】:

    猜你喜欢
    • 2021-11-09
    • 1970-01-01
    • 2020-04-22
    • 2014-04-10
    • 2010-10-14
    • 2021-03-06
    • 1970-01-01
    • 2021-03-13
    • 1970-01-01
    相关资源
    最近更新 更多