我想做的是获取格式正确的JSON文件/对象,并在其中搜索路径。如果找不到该路径,请继续。如果找到,请更新该值。更新后,将更新后的JSON保存到原始文件。
要抓住的是,格式格式的JSON结构事先未知。我可能正在磁盘上搜索数百个.json文件,因此与我的任何搜索词都不匹配的文件可以被忽略。
我正在努力解决这个问题。那里的大多数示例都没有一个带有用于键值之一的数组的JSON对象,或者它们在涉及数组时不会动态访问属性。
此链接:Powershell: How to Update/Replace data and values in Json and XML Object显示了(某种)“真实的” JSON结构,但是可接受的答案取决于知道JSON结构是什么(OP并没有寻求有关动态路径的帮助)。 / p>
此链接: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是一个数组,因此要使用“第一个”键访问对象,路径为:
$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
我一直在搜索各种不同的术语,以期找到一个解决该问题的好方法,但是到目前为止,还没有运气。我可以肯定地说,我不是第一个想要做这种事情的人,如果我在某个地方错过了答案,我深表歉意。
答案 0 :(得分:2)
您的SetValue
脚本有两个问题:
[Object]
)与对象数组
([Object[]]
)您不能返回类似return $object.$p1 = $Value
的作业。分配本身不返回任何内容,将导致向呼叫者返回$Null
。
此外,如果您为每个递归调用返回$Object
,则每个父调用者都需要将其作废($Null = SetValue -object...
),以便仅由顶级调用者返回。但请记住,您实际上是在原始($NewValue
)对象中戳$JSON
!如果您不希望这样做,则需要找出顶级调用者,并在递归调用之前先仅在顶级复制$Object
。
您不仅要处理包含单个对象的属性,而且每个属性都可能包含一个 collection 对象。实际上,叶子属性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
,但您不能仅 set (所有)这样的相关属性:([Object[]]$SomeObject).First = $Value
。 (这就是为什么您的SetValue
函数不起作用而您的GetValue
函数起作用的原因。请注意,
实际上会返回上述“ I am first too
” Json示例的两个项目。
换句话说,您将需要遍历每个级别上的 all 对象集合以设置相关属性:
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"
}
]
}
}