【问题标题】:How to provide structural information on dynamically generated JSON如何提供动态生成的 JSON 的结构信息
【发布时间】:2020-07-23 17:06:52
【问题描述】:

我想发布一个 NPM 包(用 Typescript 编写),如下所示:

const networks = { main: 1, test: 2, dev: 3 }

const resource = {
  foo: {
    [networks.main]: "hello",
    [networks.test]: "there",
    [networks.dev]: "friend"
  },
  bar: {
    [networks.main]: "how",
    [networks.test]: "are",
    [networks.dev]: "you"
  },
  baz: {
    [networks.main]: "have a",
    [networks.test]: "good",
    [networks.dev]: "day"
  }
}

而且我想确保用户可以设置一次网络,而不必担心他们在哪个网络上:

const myMainResource = getResourceFor(networks.main)
const myTestResource = getResourceFor(networks.test)
const myDevResource = getResourceFor(networks.dev)

console.log(myMainResource.foo) // "hello"
console.log(myTestResource.foo) // "there"
console.log(myDevResource.foo) // "friend"

这样,他们不必写resource.foo[networks.main],只需写resource.foo就可以得到同样的结果。

真实的对象其实嵌套的多很多,例如:alice.contracts.foobob.protocol.bar等。但是数据的存储方式,网络总是在树的叶子节点处。

我已经编写了 getResourceFor 函数,它通过递归所有资源并替换网络来按预期工作(这就是在运行时“生成”数据的原因)。

但我也想在编辑器中自动完成工作(使用 VS Code)。而且由于对象是在运行时动态创建的,因此编辑器无法真正知道生成的 JSON 对象的形状。

解决这个问题的最佳方法是什么?

如果你想看真正的代码,在这里:https://github.com/studydefi/money-legos

【问题讨论】:

  • "因为对象是在运行时动态创建的" - 示例代码中的对象不是,networks 是常量。你的实际代码是什么样子的?
  • @Bergi 我添加了指向真实代码的链接。当我说它是在运行时动态创建时,我说的是getResourceFor 如何递归整个数据对象并返回一个全新的对象。请参阅此文件:github.com/studydefi/money-legos/blob/master/src/legos.ts
  • 您可能会在 an answer I wrote 中找到一些灵感,其中涉及查找叶节点的路径。
  • @adrianmc 好的,但仍然可以将networks 设为enum,并使用索引类型键入所有查找对象。
  • @ScottSauyet 我认为OP不太关心如何编写getResourceFor算法,而是如何正确键入它。

标签: javascript typescript npm


【解决方案1】:

我最终使用了这个答案:Deep Omit with typescript

它非常混乱,但至少它有效。结果在这里:

type Primitive =
  | string
  | Function
  | number
  | boolean
  | Symbol
  | undefined
  | null;

type MappingToChangeFrom = {
  address: {
    [x: number]: string;
  };
};

type MappingToChangeTo = {
  address: string;
};

type DeepOmitHelper<T> = {
  [P in keyof T]: T[P] extends infer TP //extra level of indirection needed to trigger homomorhic behavior // distribute over unions
    ? TP extends Primitive
      ? TP // leave primitives and functions alone
      : TP extends any[]
      ? DeepOmitArray<TP> // Array special handling
      : TP extends MappingToChangeFrom // IF type equals to { address: { [networkIds: number]: string } }
      ? Omit<TP, "address"> & MappingToChangeTo // Change to { address: string }
      : DeepOmit<TP>
    : never;
};

type DeepOmit<T> = T extends Primitive ? T : DeepOmitHelper<T>;

type DeepOmitArray<T extends any[]> = {
  [P in keyof T]: DeepOmit<T[P]>;
};

type RawLegos = typeof rawLegos;
type RawLegosWithoutNetworkId = DeepOmit<RawLegos>;

const isValidObject = (obj: unknown) => typeof obj === "object" && obj !== null;

// Recursively goes through each field, and changes the address value to the specific value
// i.e. compound.cDai.address[mainnet] = 0x...
//      becomes:
//      compound.cDai.address = 0x....
export const changeAddressValue = (
  networkId: number,
  immutableObj: RawLegos,
): RawLegosWithoutNetworkId => {
  let obj = immutableObj as any;

  // recursive base case, stop here
  if (!isValidObject(immutableObj)) {
    return obj;
  }

  // desctructure the object to create new reference
  obj = { ...immutableObj };
  // iterating over the object using for..in
  for (const key in obj) {
    if (Array.isArray(obj[key])) continue; // ignore arrays (e.g. ABIs)
    if (!isValidObject(obj[key])) continue; // ignore non-valid objects

    if (key === "address") {
      obj[key] = obj.address[networkId] || null;
    } else {
      obj[key] = changeAddressValue(networkId, obj[key]);
    }
  }
  return obj;
};

【讨论】:

    猜你喜欢
    • 2015-05-29
    • 2017-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-07
    • 2016-08-24
    • 1970-01-01
    • 2013-01-27
    相关资源
    最近更新 更多