简体   繁体   English

Powershell:如何动态更新嵌套的 JSON 数组?

[英]Powershell: How to dynamically update a nested JSON array?

What I'm trying to do is take any well-formed JSON file/object and search for a path inside it.我想要做的是获取任何格式良好的 JSON 文件/对象并在其中搜索路径。 If the path isn't found, move on.如果找不到路径,请继续。 If it is found, update the value.如果找到,则更新该值。 Once updated, save the updated JSON to the original file.更新后,将更新后的 JSON 保存到原始文件中。

The catch to this, is the well-formed JSON structure is not known ahead of time.问题在于,格式良好的 JSON 结构提前未知。 It's possible I might be searching hundreds of .json files on a disk, so the files that don't match any of my search terms can just be ignored.我可能正在磁盘上搜索数百个 .json 文件,因此可以忽略与我的任何搜索词都不匹配的文件。

I'm struggling to wrap my head around how to solve this problem.我正在努力思考如何解决这个问题。 Most of the examples out there don't have a JSON object with an array for one of the key values, or they don't access the properties dynamically when an array is involved.大多数示例都没有包含键值之一的数组的 JSON 对象,或者当涉及数组时,它们不会动态访问属性。

This link: Powershell: How to Update/Replace data and values in Json and XML Object shows a (sort of)"real" JSON structure, but the accepted answer relies on knowing what the JSON structure is (the OP didn't ask for help with dynamic pathing).此链接: Powershell:如何更新/替换 Json 和 XML 对象中的数据和值显示了(某种)“真实”的 JSON 结构,但接受的答案依赖于了解 JSON 结构是什么(OP 没有要求帮助动态路径)。

This link: Set Value of Nested Object Property by Name in PowerShell has something very close, although when an array is in the mix, it doesn't work properly when setting.这个链接: Set Value of Nested Object Property by Name in PowerShell有一些非常接近的东西,尽管当数组混合时,它在设置时无法正常工作。

Here's some example JSON to use with this problem, though again, the structure is not known before the script runs.下面是一些用于解决此问题的示例 JSON,但同样,在脚本运行之前,结构是未知的。 I'm looping over a list of files on disk, and executing for each file.我正在遍历磁盘上的文件列表,并为每个文件执行。

$JSON = ConvertFrom-Json '{
   "key1":"key 1 value",
   "options":{
      "outDir":"./app-dir",
      "lib":[
         "someLibrary",
         "anotherLibrary"
      ],
      "someObjects":[
         {
            "first":"I am first"
         },
         {
            "second":"I am second"
         }
      ]
   }
}'

The string to search this json might look like the following:搜索此 json 的字符串可能如下所示:

$SearchString = 'options.someObjects.first'

Or perhaps, something non-existent like:或者,一些不存在的东西,比如:

$SearchString = 'options.someObjects.foo'

Using the recursive function GetValue from the 2nd article works beautifully for getting (and much more elegant than what I was doing):使用第二篇文章中的递归函数 GetValue 可以很好地获取(并且比我正在做的更优雅):

function GetValue($object, $key)
{
    $p1,$p2 = $key.Split(".")
    if($p2) { return GetValue -object $object.$p1 -key $p2 }
    else { return $object.$p1 }
}

However, the function SetValue does not work with an array.但是,函数 SetValue 不适用于数组。 It returns an error stating "The property 'first' can not be found on this object."它返回一个错误,指出“在这个对象上找不到属性‘first’。”

function SetValue($object, $key, $Value)
{
    $p1,$p2 = $key.Split(".")
    if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
    else { $object.$p1 = $Value }
}

I am aware this is because $JSON.options.someObjects is an array, therefore to access the object with the "first" key, the path would be:我知道这是因为 $JSON.options.someObjects 是一个数组,因此要使用“first”键访问对象,路径将是:

$JSON.options.someObjects[0].first

That's the problem I'm having.这就是我遇到的问题。 How do I dynamically iterate over all objects once it reaches a part of the path that needs iterating?一旦到达需要迭代的路径的一部分,我如何动态迭代所有对象? That part of the path could be anywhere, or more levels down, etc...路径的那部分可以在任何地方,或者向下更多层,等等......

It's strange that powershell will allow you to dynamically iterate through an array when getting the value, but not when trying to set it.奇怪的是,powershell 允许您在获取值时动态迭代数组,但在尝试设置它时不允许。

Here's a complete example which demonstrates the entire issue:这是一个完整的示例,它演示了整个问题:

#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

I've been searching all kinds of different terms trying to arrive at a good solution for this problem, but so far, no luck.我一直在搜索各种不同的术语,试图为这个问题找到一个好的解决方案,但到目前为止,没有运气。 I'm fairly certain I'm not the 1st person to want to do this sort of thing, apologies if I missed the answer somewhere.我很确定我不是第一个想做这种事情的人,如果我在某处错过了答案,我深表歉意。

There are two issues with your SetValue script:您的SetValue脚本有两个问题:

  • Returning the object返回对象
  • An Object ( [Object] ) vs an object array ( [Object[]] )对象 ( [Object] ) 与对象数组 ( [Object[]] )

Return返回

You can't return an assignment like return $object.$p1 = $Value .你不能返回像return $object.$p1 = $Value这样的赋值。 The assignment itself returns nothing with will result in returning a $Null to caller.赋值本身不返回任何内容,将导致将$Null返回给调用者。
Besides, if you return the $Object for each recursive call, you will need to void ( $Null = SetValue -object... ) it by each parent caller so that it is only returned by the top caller.此外,如果您为每个递归调用返回$Object ,您将需要由每个父调用者将其设为 void ( $Null = SetValue -object... ),以便它仅由顶级调用者返回。 but keep in mind that you are actually poking the $NewValue in the original ( $JSON ) object!.但请记住,您实际上是在原始 ( $JSON ) 对象中戳$NewValue !。 If you don't want that, you will need to figure out the top caller and only copy the $Object at the top level before the recursive call.如果您不希望那样,您将需要找出顶级调用者,并且在递归调用之前复制顶级的$Object

Object array对象数组

You not just dealing with properties containing single objects but each property might potentially contain a collection objects.您不仅要处理包含单个对象的属性,而且每个属性都可能包含一个集合对象。 In fact, the leaf property SomeObject is an example of this.事实上,叶子属性SomeObject就是一个例子。 Meaning that each object in the collection has its own unique set of properties (which could have the same property name as the sibling object):这意味着集合中的每个对象都有自己独特的一组属性(可以与兄弟对象具有相同的属性名称):

$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"
         }
      ]
   }
}'

Note that you might actually encounter a object collection at every level of the Json object.请注意,您实际上可能会在 Json 对象的每个级别遇到对象集合。
Since PSv3 you have a feature called Member Enumeration which lets you list these properties in ones, like: ([Object[]]$SomeObject).First but you can't just set (all) the concerned properties like this: ([Object[]]$SomeObject).First = $Value .从 PSv3 开始,你有一个名为Member Enumeration的功能,它可以让你列出这些属性,比如: ([Object[]]$SomeObject).First但你不能像这样设置(所有)相关属性: ([Object[]]$SomeObject).First = $Value (That is why your SetValue function doesn't work and your GetValue function does. Note that it actually returns two items for the above " I am first too " Json example). (这就是为什么您的SetValue函数不起作用而您的GetValue函数起作用的原因。请注意,它实际上为上述“ I am first too ”Json 示例返回了两个项目)。

Answer回答

In other words, you will need to iterate through all the object collections on each level to set the concerned property:换句话说,您需要遍历每个级别上的所有对象集合以设置相关属性:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM