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

时间:2019-03-27 04:37:08

标签: arrays json powershell nested

我想做的是获取格式正确的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

我一直在搜索各种不同的术语,以期找到一个解决该问题的好方法,但是到目前为止,还没有运气。我可以肯定地说,我不是第一个想要做这种事情的人,如果我在某个地方错过了答案,我深表歉意。

1 个答案:

答案 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"
                                        }
                                    ]
                }
}