[英]Powershell: How to dynamically update a nested JSON array?
我想要做的是获取任何格式良好的 JSON 文件/对象并在其中搜索路径。 如果找不到路径,请继续。 如果找到,则更新该值。 更新后,将更新后的 JSON 保存到原始文件中。
问题在于,格式良好的 JSON 结构提前未知。 我可能正在磁盘上搜索数百个 .json 文件,因此可以忽略与我的任何搜索词都不匹配的文件。
我正在努力思考如何解决这个问题。 大多数示例都没有包含键值之一的数组的 JSON 对象,或者当涉及数组时,它们不会动态访问属性。
此链接: Powershell:如何更新/替换 Json 和 XML 对象中的数据和值显示了(某种)“真实”的 JSON 结构,但接受的答案依赖于了解 JSON 结构是什么(OP 没有要求帮助动态路径)。
这个链接: Set Value of Nested Object Property by Name in PowerShell有一些非常接近的东西,尽管当数组混合时,它在设置时无法正常工作。
下面是一些用于解决此问题的示例 JSON,但同样,在脚本运行之前,结构是未知的。 我正在遍历磁盘上的文件列表,并为每个文件执行。
$JSON = ConvertFrom-Json '{
"key1":"key 1 value",
"options":{
"outDir":"./app-dir",
"lib":[
"someLibrary",
"anotherLibrary"
],
"someObjects":[
{
"first":"I am first"
},
{
"second":"I am second"
}
]
}
}'
搜索此 json 的字符串可能如下所示:
$SearchString = 'options.someObjects.first'
或者,一些不存在的东西,比如:
$SearchString = 'options.someObjects.foo'
使用第二篇文章中的递归函数 GetValue 可以很好地获取(并且比我正在做的更优雅):
function GetValue($object, $key)
{
$p1,$p2 = $key.Split(".")
if($p2) { return GetValue -object $object.$p1 -key $p2 }
else { return $object.$p1 }
}
但是,函数 SetValue 不适用于数组。 它返回一个错误,指出“在这个对象上找不到属性‘first’。”
function SetValue($object, $key, $Value)
{
$p1,$p2 = $key.Split(".")
if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
else { $object.$p1 = $Value }
}
我知道这是因为 $JSON.options.someObjects 是一个数组,因此要使用“first”键访问对象,路径将是:
$JSON.options.someObjects[0].first
这就是我遇到的问题。 一旦到达需要迭代的路径的一部分,我如何动态迭代所有对象? 路径的那部分可以在任何地方,或者向下更多层,等等......
奇怪的是,powershell 允许您在获取值时动态迭代数组,但在尝试设置它时不允许。
这是一个完整的示例,它演示了整个问题:
#Create JSON:
$JSON = ConvertFrom-Json '{
"key1":"key 1 value",
"options":{
"outDir":"./app-dir",
"lib":[
"someLibrary",
"anotherLibrary"
],
"someObjects":[
{
"first":"I am first"
},
{
"second":"I am second"
}
]
}
}'
$SearchPath = 'options.someObjects.first'
$NewValue = 'I am a new value'
function GetValue($object, $key)
{
$p1,$p2 = $key.Split(".")
if($p2) { GetValue -object $object.$p1 -key $p2 }
else { return $object.$p1 }
}
function SetValue($object, $key, $Value)
{
$p1,$p2 = $key.Split(".")
if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
else { return $object.$p1 = $Value }
}
GetValue -object $JSON -key $SearchPath
SetValue -object $JSON -key $SearchPath -Value $NewValue
我一直在搜索各种不同的术语,试图为这个问题找到一个好的解决方案,但到目前为止,没有运气。 我很确定我不是第一个想做这种事情的人,如果我在某处错过了答案,我深表歉意。
您的SetValue
脚本有两个问题:
[Object]
) 与对象数组 ( [Object[]]
)你不能返回像return $object.$p1 = $Value
这样的赋值。 赋值本身不返回任何内容,将导致将$Null
返回给调用者。
此外,如果您为每个递归调用返回$Object
,您将需要由每个父调用者将其设为 void ( $Null = SetValue -object...
),以便它仅由顶级调用者返回。 但请记住,您实际上是在原始 ( $JSON
) 对象中戳$NewValue
!。 如果您不希望那样,您将需要找出顶级调用者,并且只在递归调用之前复制顶级的$Object
。
您不仅要处理包含单个对象的属性,而且每个属性都可能包含一个集合对象。 事实上,叶子属性SomeObject
就是一个例子。 这意味着集合中的每个对象都有自己独特的一组属性(可以与兄弟对象具有相同的属性名称):
$JSON = ConvertFrom-Json '{
"key1":"key 1 value",
"options":{
"outDir":"./app-dir",
"lib":[
"someLibrary",
"anotherLibrary"
],
"someObjects":[
{
"first":"I am first"
},
{
"first":"I am first too"
},
{
"second":"I am second"
}
]
}
}'
请注意,您实际上可能会在 Json 对象的每个级别遇到对象集合。
从 PSv3 开始,你有一个名为Member Enumeration的功能,它可以让你列出这些属性,比如: ([Object[]]$SomeObject).First
但你不能像这样设置(所有)相关属性: ([Object[]]$SomeObject).First = $Value
。 (这就是为什么您的SetValue
函数不起作用而您的GetValue
函数起作用的原因。请注意,它实际上为上述“ I am first too
”Json 示例返回了两个项目)。
换句话说,您需要遍历每个级别上的所有对象集合以设置相关属性:
function SetValue($object, $key, $Value)
{
$p1,$p2 = $key.Split(".",2)
if($p2) { $object | ?{$Null -ne $_.$p1} | %{SetValue -object $_.$p1 -key $p2 -Value $Value} }
else { $object | ?{$Null -ne $_.$p1} | %{$_.$p1 = $Value} }
}
SetValue -object $JSON -key $SearchPath -Value $NewValue
$Json | ConvertTo-Json -Depth 5
{
"key1": "key 1 value",
"options": {
"outDir": "./app-dir",
"lib": [
"someLibrary",
"anotherLibrary"
],
"someObjects": [
{
"first": "I am a new value"
},
{
"second": "I am second"
}
]
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.