为什么在转换JSON时,powershell在单行中给出的结果不同于双行?

时间:2013-12-30 22:20:18

标签: json powershell

概述

从powershell 3提示符,我想调用一个RESTful服务,获取一些JSON,然后打印它。我发现如果我将数据转换为powershell对象,然后将powershell对象转换回json,我会得到一个漂亮的漂亮打印字符串。但是,如果我将两个转换组合成一个带管道的单线程,我会得到不同的结果。

TL; DR:这个:

PS> $psobj = $orig | ConvertFrom-JSON
PS> $psobj | ConvertTo-JSON

...给我的结果与此不同:

PS> $orig | ConvertFrom-JSON | ConvertTo-JSON

原始数据

[
  {
    "Type": "1",
    "Name": "QA"
  },
  {
    "Type": "2",
    "Name": "whatver"
  }
]

分两步进行转换

我要删除空格(因此它适合一行......),将其转换为powershell对象,然后将其转换回JSON。这很有效,并且给了我正确的数据:

PS> $orig = '[{"Type": "1","Name": "QA"},{"Type": "2","Name": "DEV"}]'
PS> $psobj = $orig | ConvertFrom-JSON
PS> $psobj | ConvertTo-JSON
[
    {
        "Type":  "1",
        "Name":  "QA"
    },
    {
        "Type":  "2",
        "Name":  "DEV"
    }
]

将两个步骤与管道

组合

但是,如果我将最后两个语句组合成一个单行,我会得到不同的结果:

PS> $orig | ConvertFrom-JSON | ConvertTo-JSON
{
    "value":  [
                  {
                      "Type":  "1",
                      "Name":  "QA"
                  },
                  {
                      "Type":  "2",
                      "Name":  "DEV"
                  }
              ],
    "Count":  2
}

注意添加了“value”和“Count”键。为什么会有区别?我确定它与返回JSON对象而不是JSON数组的愿望有关,但我不明白为什么我进行转换的方式会影响最终结果。

4 个答案:

答案 0 :(得分:7)

解决方案是用括号括起前两个操作:

PS C:\> ($orig | ConvertFrom-JSON) | ConvertTo-JSON
[
    {
        "Type":  "1",
        "Name":  "QA"
    },
    {
        "Type":  "2",
        "Name":  "DEV"
    }
]

括号允许您同时获取前两个操作的输出。如果没有它们,powershell将尝试解析它单独获取的任何对象。由PSCustomObject生成的$orig | ConvertFrom-JSON集合包含两个PSCustomObjects用于1 / QA和2 / DEV对,因此通过管道输出该PowerShell尝试处理键/值对一次一个。

使用括号是对输出进行“分组”的一种较短方式,允许您在不输入变量的情况下对其进行操作。

答案 1 :(得分:5)

注意:问题仍然存在于 Windows PowerShell v5.1 ,但 PowerShell 核心不受影响

现有答案提供了有效的解决方法 - 将$orig | ConvertFrom-JSON括在(...)中 - 但不要正确解释问题;此外,解决方法不能在所有情况下使用。

至于为什么使用中间变量没有出现问题:

在阵列逐个与阵列作为整体(作为单个对象)发射阵列元素之间的流水线之间的区别是无效的您在变量中收集输出;例如,$a = 1, 2实际上等同于$a = Write-Output -NoEnumerate 1, 2,即使后者最初将数组1, 2作为单个对象发出;但是,如果进一步的管道段处理对象,则区别很重要 - 见下文。

有问题的行为两个因素的组合:

  • ConvertFrom-Json通过管道将数组作为单个对象发送,从而偏离了正常的输出行为。也就是说,使用表示数组的JSON字符串,ConvertFrom-Json通过管道将生成的对象数组作为单个对象发送

    • 您可以按如下方式验证ConvertFrom-Json令人惊讶的行为:

      PS> '[ "one", "two" ]' | ConvertFrom-Json | Get-Member
      
      TypeName: System.Object[]  # !! should be: System.String
      ...
      
      • 如果ConvertFrom-Json逐个通过管道传递其输出 - 正如cmdlet通常所做的那样 - Get-Member将返回项目的(不同)类型在集合中,在这种情况下为[System.String]

      • 在命令的(...)强制枚举中包含命令,这就是为($orig | ConvertFrom-Json) | ConvertTo-Json提供有效解决方法的原因。

    • 这种行为 - 在PowerShell Core 中仍然存在 - 是否应该改变 - 正在this GitHub issue中讨论。

  • System.Array类型 - 所有数组的基本类型 - 具有通过PowerShell的ETS(扩展类型系统)定义的.Count属性 - 请参阅{{ 3}})会导致ConvertTo-Json在其创建的JSON字符串中包含该属性,并且数组元素包含在同级value属性中。 / p>

    • 只有当ConvertTo-Json将数组作为一个整体 视为输入对象时才会发生此,由此ConvertFrom-Json生成案件;例如,, (1, 2) | ConvertTo-Json表示问题(一个嵌套数组,其内部数组作为单个对象发送),但是 1, 2 | ConvertTo-Json没有(数组元素是单独发送的)。

    • 这个ETS提供的.Count属性在PSv3中被有效废弃,当数组由于PowerShell而隐式获得.Count属性时,现在将显式实现的接口成员呈现为好吧,它表现了ICollection.Count属性(另外,所有对象都被赋予了隐式 .Count属性,以统一标量的处理和集合)。

    • 因此,这个ETS属性已在PowerShell Core 中删除,但仍存在于Windows PowerShell v5.1中 - 请参阅下面的解决方法。

解决方法(PowerShell Core 中不需要)

以前多次向Get-Help about_Types.ps1xml提示帽子。

注意:根据定义,此解决方法是PSv3 +,因为Convert*-Json cmdlet仅在v3中引入。

鉴于 ETS - 提供.Count属性是(a)问题的原因和(b)在PSv3 +中有效过时,解决方案是简单地删除< / em> it 调用ConvertTo-Json之前 - 在会话中执行一次就足够了,它不应该影响其他命令:

 Remove-TypeData System.Array # Remove the redundant ETS-supplied .Count property

这样,无关的.Count.value属性应该已经消失:

 PS> '[ "one", "two" ]' | ConvertFrom-Json | ConvertTo-Json
 [
   "one",
   "two"
 ]

上述解决方法还修复了数组值属性的问题 ; e.g:

PS> '' | Select-Object @{ n='prop'; e={ @( 1, 2 ) } } | ConvertTo-Json
{
    "prop":  [
                 1,
                 2
             ]
}

如果没有变通方法,"prop"的值也会包含无关的.Count.value属性。

答案 2 :(得分:3)

首先,为什么会发生这种情况?

PowerShell会自动将多个对象包装到名为PSMemberSet的集合中,该集合上有Count属性。它基本上是PowerShell管理任意对象数组的方式。发生的事情是Count属性被添加到生成的JSON中,从而产生了您所看到的不良结果。

我们可以通过以下方式证明我刚才所说的内容:

$Json = @"
[
    {
        "Type":  "1",
        "Name":  "QA"
    },
    {
        "Type":  "2",
        "Name":  "DEV"
    }
]
"@;

# Deserialize the JSON into an array of "PSCustomObject" objects
$Deserialized = ConvertFrom-Json -InputObject $Json;
# Examine the PSBase property of the PowerShell array
# Note the .NET object type name: System.Management.Automation.PSMemberSet
$Deserialized.psbase | Get-Member;

以下是

的输出
   TypeName: System.Management.Automation.PSMemberSet

Name           MemberType            Definition                                                                                                                                                                      
----           ----------            ----------                                                                                                                                                                      
Add            Method                int IList.Add(System.Object value)                                                                                                                                              
Address        Method                System.Object&, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Address(int )                                                                       
Clear          Method                void IList.Clear()                      
......
......
Count          Property              int Count {get;}  

您可以通过引用SyncRoot(实现PSMemberSet .NET接口)的ICollection属性并将该属性的值传递给{{1来解决此问题。 }}

这是一个完整的,有效的例子:

ConvertTo-Json

将显示正确的(预期)输出,类似于以下内容:

$Json = @"
[
    {
        "Type":  "1",
        "Name":  "QA"
    },
    {
        "Type":  "2",
        "Name":  "DEV"
    }
]
"@;

($Json | ConvertFrom-Json) | ConvertTo-Json;

答案 3 :(得分:0)

我在这里遇到了同样的问题。我可以使用ForEach-ObjectPSCustomObject解决此问题。希望它可以帮到你。

'' | ForEach-Object { [PSCustomObject]@{ prop= @( 1, 2 ) }} | ConvertTo-Json

它给出了这个,这是预期的行为并使用更清洁的方法:

{
  "prop": [
        1,
        2
  ]
}