【问题标题】:Pick value from object with dynamic key based on input key in Typescript根据 Typescript 中的输入键从具有动态键的对象中选择值
【发布时间】:2020-12-18 18:52:50
【问题描述】:

所以我的后端有一个这样的 JSON

name: "Hello",
nameTranslations: {nl:"Hallo", en: "Hello", fr: "Bonjour"}
description: "Hello",
descriptionTranslations: {nl:"Hallo", en: "Hello", fr: "Bonjour"}

我想构建一个函数,我将在其中放入对象和键,它会自动找到可能的最佳翻译。在 Javascript 中很容易,但我想正确使用 Typescript,所以如果我忘记查询翻译(我使用 GraphQL),我不会处理运行时错误(我使用 GraphQL)

function getText(object, key, language) {
  const best = object[`${key}Translations`][language];
  if (best) {
    return best;
  }

  return object[key];
}

我有一些 Typescript 要求

  • 我只想要输入对象的可能键
  • 如果对象或“keyTranslations”上的键不可用,我希望 Typescript 返回错误

更新 根据@michmich112 的回答,我确实使用了以下功能

function translate<T extends BackendTranslations>(translations: T, fallback: string, language: keyof BackendTranslations) => {
      return translations[language] || fallback;
}

translate(project.nameTranslations, project.name)

我将保持答案开放,因为我仍然对这是否可以在 Typescript 中工作感兴趣

【问题讨论】:

  • @michmich 的回答有什么问题?
  • 他的建议确实解决了我的问题,但没有回答问题,如果原始函数可以是类型安全的并且也符合我的要求,我仍然感兴趣

标签: typescript template-literals


【解决方案1】:

这是我的尝试:

const json = {
  name: "Hello",
  nameTranslations: { nl: "Hallo", en: "Hello", fr: "Bonjour" },
  description: "Hello",
  descriptionTranslations: { nl: "Hallo", en: "Hello", fr: "Bonjour" }
}

type BackendTranslations = {
  nl: string;
  en: string;
  fr: string;
}


type Raw<T> = T extends `${infer R}Translations` ? R : never

/**
 * Once you will have more specific interface for json
 * this overloading will help you,
 * for now it always returns string , because all properties/nested properties of
 * JSON are strings
 */
//function getText<K extends Raw<keyof Data>>(obj: Data, key: K, language: keyof Translations): K extends string ? Data[`${K}Translations`][keyof Translations] : Data[K];

function getText<
  Data extends Record<string, unknown>,
  K extends Raw<keyof Data>
>(obj: Data, key: K, language: keyof BackendTranslations) {
  return (
    (obj[`${key}Translations`] as BackendTranslations)[language] || obj[key]
  );
}

const result = getText(json, 'name', 'en') // ok
const result1 = getText(json, 'nameX', 'en') // error
const result2 = getText(json, 'nameTranslations', 'en') // error
const result3 = getText(json, 'description', 'en') // ok

请记住,我的解决方案仅适用于 TypeScript 4。*

【讨论】:

  • 真的很好, K extends Raw >(obj: Data, key: K, language: keyof BackendTranslations) { return ( (obj[${key}Translations] as BackendTranslations)[language] || obj[key] ); } ```
  • @RichardLindhout 我已经接受了你的建议。现在好了
  • 非常感谢,Typescript 4 在模板文字方面非常出色,但我不明白如何编写它们,感谢您的帮助!
  • 你可以找到模板字符串的原始PR,有很多例子。另请查看 TS 文档
【解决方案2】:

首先,您需要为您的 JSON 创建一个接口:

interface ITranslationData {
  nl: string
  en: string
  fr: string
}

interface ITranslation {
  name: string
  nameTranslations: ITranslationData
  description: string
  descriptionTranslations: ITranslationData
}

一旦你有了,你就可以在你的函数上使用如下类型:

function getText(obj: ITranslation, 
  key: keyof ITranslation,
  language: keyof ITranslationData): string {
  const best = obj[key][language];
  if (best) {
    return best;
  }

  return obj[key];
}

这将要求您的密钥准确无误,这意味着您将无法进行const best = object[${key}Translations][language];

这也意味着您可以在一行中编写代码:

function getText(obj: ITranslation, 
  key: keyof ITranslation,
  language: keyof ITranslationData): string {
  return obj[key][language] || obj[key];
}

【讨论】:

  • 我认为我同意使用不同功能的观点
  • 感谢您的宝贵时间和建议!
猜你喜欢
  • 1970-01-01
  • 2021-08-22
  • 2019-07-11
  • 1970-01-01
  • 2021-06-21
  • 2019-03-03
  • 1970-01-01
  • 2020-09-28
  • 1970-01-01
相关资源
最近更新 更多