我编写了一个函数,它将JSON对象和定义要替换的值的Map作为输入;并返回相同的JSON对象,其中所有出现的值都被相应的替换项替换--替换项可以是任何值。
这将更改对象的类型,但我不知道如何在TypeScript中反映此更改。
函数如下:
function replaceJsonValues<Source, Replacement, Output>(
obj: Source,
translatedKeyData: Map<string, Replacement>
): Output {
let stringifiedObject = JSON.stringify(obj);
for (const [key, keyProp] of translatedKeyData.entries()) {
stringifiedObject = stringifiedObject.replaceAll(`"${key}"`, JSON.stringify(keyProp));
}
return JSON.parse(stringifiedObject);
}
type SourceType = {
foo: string;
baz: {
test: string;
}
}[]
type ReplacementType = {
fancy: string;
}
const source: SourceType = [{ foo: "bar", baz: { test: "bar" } }];
const replacement: ReplacementType = { fancy: "replacement" };
const result = replaceJsonValues(source, new Map([["bar", replacement]]));
// ^?
console.log(result)
在TSPlayground见。
如何修改它以使Output
类型正确?
1条答案
按热度按时间wztqucjr1#
有几个问题需要解决:
translatedKeyData
中键/值对之间的特定关系,如果是这样,您就不能轻松地使用Map
来完成此操作。在TypeScript中,Map
的类型为Map<K, V>
,这是一种类似于记录的类型,其中所有键都被视为彼此相同的类型。并且所有的值都被认为是彼此相同的类型...特定键和特定值之间的任何关联都将丢失。有一些方法可以更改Map
的类型,以便它确实跟踪此类连接,如Typescript: How can I make entries in an ES6 Map based on an object key/value type中所述,但是使用普通对象开始会容易得多。您可以使用{k1: v1, k2: v2, ...}
代替new Map([["k1", v1], ["k2", v2], ...])
。您可以使用theObject.entries()
method代替theentries()
method ofMap
。例如,因为您以JavaScript值开始和结束,所以您可以通过遍历对象本身来执行概念上相同的转换,这样您就可以确信您只替换了字符串值的键或字符串值,而不是奇怪的东西。
obj
和MaptranslatedKeyData
上要替换的所有字符串值的文本类型时,这才有可能起作用。那么替换将不会被正确地键入。这意味着你需要在编译时知道源代码和Map的细节。如果它们只在运行时是已知的,那么你的选择将非常有限,我将假设你有编译时已知的值,并且这些值将用const
Assert初始化,以向编译器给予最大的信息。好了,现在输入:我会这样做:
所以源对象是generic类型
S
,Map对象是泛型类型M
,然后返回类型是ReplaceValues<S, M>
,这是一个递归的conditionalt类型,它遍历S
的不同情况并相应地执行替换。首先:
S extends keyof M ? M[S]
意味着如果源代码S
是M
中的一个键,那么你可以用M
中对应的属性M[S]
替换S
,这是直接的字符串值替换:实现中typeof obj === "string"
代码块的类型级别版本。那么:
S extends readonly any[] ? { [I in keyof S]: ReplaceValues<S[I], M> } :
意味着如果源代码S
是一个类数组类型,那么我们将这个类数组类型Map到另一个类数组类型,其中每个值都被递归地替换,这是实现中Array.isArray(obj)
代码块的类型级别版本。那么:
S extends object ? { [K in keyof S as ( K extends keyof M ? M[K] extends string ? M[K] : K : K )]: ReplaceValues<S[K], M> } :
意味着如果源S
是一个非数组对象,那么我们Map对象的键和值,这样在M
中找到的任何键都被重新Map,同时递归地将ReplaceValues
应用于每个值类型。最后,如果所有这些都是false,那么我们返回
S
。这是从底部掉下去的,所以数字还是数字,布尔还是布尔,等等。这是return obj
在实现底部的类型级别版本。好吧,让我们看看它是否有效:
看起来不错!编译器将
"bar"
值替换为ReplacementType
,将"qux"
键替换为"foop"
,这与运行时实际生成的对象完全一致:Playground代码链接