从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数组的愿望有关,但我不明白为什么我进行转换的方式会影响最终结果。
答案 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中 - 请参阅下面的解决方法。
以前多次向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-Object
和PSCustomObject
解决此问题。希望它可以帮到你。
'' | ForEach-Object { [PSCustomObject]@{ prop= @( 1, 2 ) }} | ConvertTo-Json
它给出了这个,这是预期的行为并使用更清洁的方法:
{
"prop": [
1,
2
]
}