遍历 JSON 个文件 PowerShell
Iterating through a JSON file PowerShell
我正在尝试在 PowerShell 中遍历以下 JSON 文件。
没有特别命名顶级标签(例如 17443 和 17444),因为我事先不知道它们,所以我找不到循环数据的方法。
我想为所有记录输出标签 3、4 和 5(头衔、名字、姓氏)。
我将如何实现?
{
"17443":{
"sid":"17443",
"nid":"7728",
"submitted":"1436175407",
"data":{
"3":{
"value":[
"Mr"
]
},
"4":{
"value":[
"Jack"
]
},
"5":{
"value":[
"Cawles"
]
}
} },
"17444":{
"sid":"17444",
"nid":"7728",
"submitted":"1436891400",
"data":{
"3":{
"value":[
"Miss"
]
},
"4":{
"value":[
"Charlotte"
]
},
"5":{
"value":[
"Tann"
]
}
}
},
"17445":{
"sid":"17445",
"nid":"7728",
"submitted":"1437142325",
"data":{
"3":{
"value":[
"Mr"
]
},
"4":{
"value":[
"John"
]
},
"5":{
"value":[
"Brokland"
]
}
}
}
}
我可以使用下面的代码访问数据,但我想避免输入 17443、17444 等
$data = ConvertFrom-Json $json
foreach ($i in $data.17443)
{
foreach ($t in $i.data.3)
{
Write-Host $t.value
}
foreach ($t in $i.data.4)
{
Write-Host $t.value
}
foreach ($t in $i.data.5)
{
Write-Host $t.value
}
}
这是一个简单的基于正则表达式的解决方案。假设 $sRawJson
包含您的 JSON 输入:
$oRegex = [Regex]'(?:(?<="[345]":\{"value"\:\["))[^"]+'
$cParts = $oRegex.Matches(($sRawJson -replace '\s')) | Select-Object -ExpandProperty "Value"
连接零件以获得全名:
for ($i = 0; $i -lt $cParts.Count / 3; $i++) { $cParts[($i * 3)..($i * 3 + 2)] -join ' ' }
PowerShell 3.0+
在 PowerShell 3.0 及更高版本中(参见:Determine installed PowerShell version),您可以使用 ConvertFrom-Json
cmdlet 将 JSON 字符串转换为 PowerShell 数据结构。
这既方便又不幸 - 方便,因为它很容易消耗 JSON,不幸的是,因为 ConvertFrom-Json
给你 PSCustomObjects,而且它们很难迭代作为键值对。
当您知道密钥时,就没有什么可迭代的了——您只需直接访问它们,例如$result.thisKey.then.thatKey.array[1]
,大功告成。
但是在这个特定的 JSON 中,密钥似乎是 dynamic/not 提前知道的,例如 "17443"
或 "17444"
。这意味着我们需要可以将 PSCustomObject
转换为 foreach
可以理解的键值列表的东西。
# helper to turn PSCustomObject into a list of key/value pairs
function Get-ObjectMember {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[PSCustomObject]$obj
)
$obj | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
[PSCustomObject]@{Key = $key; Value = $obj."$key"}
}
}
现在我们可以遍历对象图并生成包含 Title
、FirstName
和 LastName
的输出对象列表
$json = '{"17443": {"17444": {"sid": "17444","nid": "7728","submitted": "1436891400","data": {"3": {"value": ["Miss"]},"4": {"value": ["Charlotte"]},"5": {"value": ["Tann"]}}},"17445": {"sid": "17445","nid": "7728","submitted": "1437142325","data": {"3": {"value": ["Mr"]},"4": {"value": ["John"]},"5": {"value": ["Brokland"]}}},"sid": "17443","nid": "7728","submitted": "1436175407","data": {"3": {"value": ["Mr"]},"4": {"value": ["Jack"]},"5": {"value": ["Cawles"]}}}}'
$json | ConvertFrom-Json | Get-ObjectMember | foreach {
$_.Value | Get-ObjectMember | where Key -match "^\d+$" | foreach {
[PSCustomObject]@{
Title = $_.value.data."3".value | select -First 1
FirstName = $_.Value.data."4".value | select -First 1
LastName = $_.Value.data."5".value | select -First 1
}
}
}
输出
Title FirstName LastName
----- --------- --------
Miss Charlotte Tann
Mr John Brokland
PowerShell 2.0/替代方法
另一种适用于 PowerShell 2.0(不支持上述某些构造)的替代方法涉及使用 .NET JavaScriptSerializer class 来处理 JSON:
Add-Type -AssemblyName System.Web.Extensions
$JS = New-Object System.Web.Script.Serialization.JavaScriptSerializer
现在我们可以做一个非常相似的操作——甚至比上面更简单一点,因为 JavaScriptSerializer 给你正则 Dictionaries,它很容易通过 GetEnumerator()
作为键值对迭代方法:
$json = '{"17443": {"17444": {"sid": "17444","nid": "7728","submitted": "1436891400","data": {"3": {"value": ["Miss"]},"4": {"value": ["Charlotte"]},"5": {"value": ["Tann"]}}},"17445": {"sid": "17445","nid": "7728","submitted": "1437142325","data": {"3": {"value": ["Mr"]},"4": {"value": ["John"]},"5": {"value": ["Brokland"]}}},"sid": "17443","nid": "7728","submitted": "1436175407","data": {"3": {"value": ["Mr"]},"4": {"value": ["Jack"]},"5": {"value": ["Cawles"]}}}}'
$data = $JS.DeserializeObject($json)
$data.GetEnumerator() | foreach {
$_.Value.GetEnumerator() | where { $_.Key -match "^\d+$" } | foreach {
New-Object PSObject -Property @{
Title = $_.Value.data."3".value | select -First 1
FirstName = $_.Value.data."4".value | select -First 1
LastName = $_.Value.data."5".value | select -First 1
}
}
}
输出相同:
Title FirstName LastName
----- --------- --------
Miss Charlotte Tann
Mr John Brokland
如果 JSON 大于 4 MB,请相应地设置 JavaScriptSerializer.MaxJsonLength
property。
从文件中读取 JSON
如果您从文件中读取,请使用 Get-Content -Raw -Encoding UTF-8
。
-Raw
因为否则 Get-Content
returns 一组单独的行和 JavaScriptSerializer.DeserializeObject
无法处理。最近的 Powershell 版本似乎改进了 .NET 函数参数的类型转换,因此它可能不会在您的系统上出错,但如果出错(或者为了安全起见),请使用 -Raw
.
-Encoding
因为在阅读时指定文本文件的编码是明智的,而 UTF-8
是 JSON 文件最可能的值。
备注
- 当您构建包含具有不可预测键的项目的 JSON 时,更喜欢像
{items: [{key: 'A', value: 0}, {key: 'B', value: 1}]}
这样的数组结构而不是 {'A': 0, 'B': 1}
。后者似乎更直观,但更难生成和使用。
ConvertFrom-Json()
为您提供了一个 PowerShell 自定义对象 (PSCustomObject
),它反映了 JSON 字符串中的数据。
- 您可以使用
Get-Member -type NoteProperty
遍历自定义对象的属性
- 您可以使用
$object."$propName"
语法或 $object."$(some PS expression)"
. 动态访问对象的属性
- 您可以创建自己的自定义对象并使用
New-Object PSObject -Property @{...}
的一组属性对其进行初始化,或者 [PSCustomObject]@{ .. }
`
这个问题经常出现。在这种情况下,我们必须遍历属性两次。这是我目前的答案。使对象更容易使用。顶层和数据属性都变成了“名称”和“值”的数组。您可以使用 select-object 计算属性以任何您想要的方式呈现它。似乎在 JSON 中你更经常获得随机属性,而不是相同属性的数组。
$a = cat file.json | convertfrom-json
$a = $a.psobject.properties | select name,value
$a | foreach { $_.value.data =
$_.value.data.psobject.properties | select name,value }
$a.value.data.value
value
-----
{Mr}
{Jack}
{Cawles}
{Miss}
{Charlotte}
{Tann}
{Mr}
{John}
{Brokland}
尝试使用 jq 进行类似操作:
'{"prop1":1, "prop2":2, "prop3":3}' | jq to_entries | convertfrom-json
key value
--- -----
prop1 1
prop2 2
prop3 3
此外,Powershell 7 中的 convertFrom-Json 有一个 -AsHashTable 参数,它为您提供键和值属性。
$a = '{"name":"joe","address":"here"}' | ConvertFrom-Json -AsHashtable
$a
Name Value
---- -----
name joe
address here
$a.keys
name
address
$a.values
joe
here
首先,我们将使用 ConvertFrom-Json
cmdlet 将 JSON 字符串转换为 PowerShell 数据结构。
然后,为了说明如何循环遍历嵌套的 PowerShell 数据结构,我们将通过一个简化示例进行演示。
给定
$response = [PSCustomObject] @{
prediction = [PSCustomObject] @{
cat = 0.6576587659
dog = 0.3423412341
}
}
我们的目标是遍历预测中的键值对(即猫和狗)并将它们的值缩短到小数点后 3 位。
求解
$response.prediction | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
[PSCustomObject]@{Key = $key; Value = "{0:N3}" -f $response.prediction.$key}
}
我们首先遍历预测的所有成员,然后为每个成员分配一个新的成员键和一个 3 位小数位值。
输出
Key Value
--- -----
cat 0.658
dog 0.342
如上评论所述,无需建造火箭即可解决此任务 =)
要遍历 json 文件,我们可以引用一个 powershell 对象变量(我们考虑 json)
看看有什么$data.PsObject.Properties那里有很多有趣的东西=)
决定:
$data = ConvertFrom-Json $json
foreach ($elem in $data.PsObject.Properties.Value)
{
Write-Host "Title:" $elem.data.3.value
Write-Host "First Name:" $elem.data.4.value
Write-Host "Surname:" $elem.data.5.value
}
我正在尝试在 PowerShell 中遍历以下 JSON 文件。
没有特别命名顶级标签(例如 17443 和 17444),因为我事先不知道它们,所以我找不到循环数据的方法。
我想为所有记录输出标签 3、4 和 5(头衔、名字、姓氏)。
我将如何实现?
{
"17443":{
"sid":"17443",
"nid":"7728",
"submitted":"1436175407",
"data":{
"3":{
"value":[
"Mr"
]
},
"4":{
"value":[
"Jack"
]
},
"5":{
"value":[
"Cawles"
]
}
} },
"17444":{
"sid":"17444",
"nid":"7728",
"submitted":"1436891400",
"data":{
"3":{
"value":[
"Miss"
]
},
"4":{
"value":[
"Charlotte"
]
},
"5":{
"value":[
"Tann"
]
}
}
},
"17445":{
"sid":"17445",
"nid":"7728",
"submitted":"1437142325",
"data":{
"3":{
"value":[
"Mr"
]
},
"4":{
"value":[
"John"
]
},
"5":{
"value":[
"Brokland"
]
}
}
}
}
我可以使用下面的代码访问数据,但我想避免输入 17443、17444 等
$data = ConvertFrom-Json $json
foreach ($i in $data.17443)
{
foreach ($t in $i.data.3)
{
Write-Host $t.value
}
foreach ($t in $i.data.4)
{
Write-Host $t.value
}
foreach ($t in $i.data.5)
{
Write-Host $t.value
}
}
这是一个简单的基于正则表达式的解决方案。假设 $sRawJson
包含您的 JSON 输入:
$oRegex = [Regex]'(?:(?<="[345]":\{"value"\:\["))[^"]+'
$cParts = $oRegex.Matches(($sRawJson -replace '\s')) | Select-Object -ExpandProperty "Value"
连接零件以获得全名:
for ($i = 0; $i -lt $cParts.Count / 3; $i++) { $cParts[($i * 3)..($i * 3 + 2)] -join ' ' }
PowerShell 3.0+
在 PowerShell 3.0 及更高版本中(参见:Determine installed PowerShell version),您可以使用 ConvertFrom-Json
cmdlet 将 JSON 字符串转换为 PowerShell 数据结构。
这既方便又不幸 - 方便,因为它很容易消耗 JSON,不幸的是,因为 ConvertFrom-Json
给你 PSCustomObjects,而且它们很难迭代作为键值对。
当您知道密钥时,就没有什么可迭代的了——您只需直接访问它们,例如$result.thisKey.then.thatKey.array[1]
,大功告成。
但是在这个特定的 JSON 中,密钥似乎是 dynamic/not 提前知道的,例如 "17443"
或 "17444"
。这意味着我们需要可以将 PSCustomObject
转换为 foreach
可以理解的键值列表的东西。
# helper to turn PSCustomObject into a list of key/value pairs
function Get-ObjectMember {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[PSCustomObject]$obj
)
$obj | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
[PSCustomObject]@{Key = $key; Value = $obj."$key"}
}
}
现在我们可以遍历对象图并生成包含 Title
、FirstName
和 LastName
$json = '{"17443": {"17444": {"sid": "17444","nid": "7728","submitted": "1436891400","data": {"3": {"value": ["Miss"]},"4": {"value": ["Charlotte"]},"5": {"value": ["Tann"]}}},"17445": {"sid": "17445","nid": "7728","submitted": "1437142325","data": {"3": {"value": ["Mr"]},"4": {"value": ["John"]},"5": {"value": ["Brokland"]}}},"sid": "17443","nid": "7728","submitted": "1436175407","data": {"3": {"value": ["Mr"]},"4": {"value": ["Jack"]},"5": {"value": ["Cawles"]}}}}'
$json | ConvertFrom-Json | Get-ObjectMember | foreach {
$_.Value | Get-ObjectMember | where Key -match "^\d+$" | foreach {
[PSCustomObject]@{
Title = $_.value.data."3".value | select -First 1
FirstName = $_.Value.data."4".value | select -First 1
LastName = $_.Value.data."5".value | select -First 1
}
}
}
输出
Title FirstName LastName ----- --------- -------- Miss Charlotte Tann Mr John Brokland
PowerShell 2.0/替代方法
另一种适用于 PowerShell 2.0(不支持上述某些构造)的替代方法涉及使用 .NET JavaScriptSerializer class 来处理 JSON:
Add-Type -AssemblyName System.Web.Extensions
$JS = New-Object System.Web.Script.Serialization.JavaScriptSerializer
现在我们可以做一个非常相似的操作——甚至比上面更简单一点,因为 JavaScriptSerializer 给你正则 Dictionaries,它很容易通过 GetEnumerator()
作为键值对迭代方法:
$json = '{"17443": {"17444": {"sid": "17444","nid": "7728","submitted": "1436891400","data": {"3": {"value": ["Miss"]},"4": {"value": ["Charlotte"]},"5": {"value": ["Tann"]}}},"17445": {"sid": "17445","nid": "7728","submitted": "1437142325","data": {"3": {"value": ["Mr"]},"4": {"value": ["John"]},"5": {"value": ["Brokland"]}}},"sid": "17443","nid": "7728","submitted": "1436175407","data": {"3": {"value": ["Mr"]},"4": {"value": ["Jack"]},"5": {"value": ["Cawles"]}}}}'
$data = $JS.DeserializeObject($json)
$data.GetEnumerator() | foreach {
$_.Value.GetEnumerator() | where { $_.Key -match "^\d+$" } | foreach {
New-Object PSObject -Property @{
Title = $_.Value.data."3".value | select -First 1
FirstName = $_.Value.data."4".value | select -First 1
LastName = $_.Value.data."5".value | select -First 1
}
}
}
输出相同:
Title FirstName LastName ----- --------- -------- Miss Charlotte Tann Mr John Brokland
如果 JSON 大于 4 MB,请相应地设置 JavaScriptSerializer.MaxJsonLength
property。
从文件中读取 JSON
如果您从文件中读取,请使用 Get-Content -Raw -Encoding UTF-8
。
-Raw
因为否则Get-Content
returns 一组单独的行和JavaScriptSerializer.DeserializeObject
无法处理。最近的 Powershell 版本似乎改进了 .NET 函数参数的类型转换,因此它可能不会在您的系统上出错,但如果出错(或者为了安全起见),请使用-Raw
.-Encoding
因为在阅读时指定文本文件的编码是明智的,而UTF-8
是 JSON 文件最可能的值。
备注
- 当您构建包含具有不可预测键的项目的 JSON 时,更喜欢像
{items: [{key: 'A', value: 0}, {key: 'B', value: 1}]}
这样的数组结构而不是{'A': 0, 'B': 1}
。后者似乎更直观,但更难生成和使用。 ConvertFrom-Json()
为您提供了一个 PowerShell 自定义对象 (PSCustomObject
),它反映了 JSON 字符串中的数据。- 您可以使用
Get-Member -type NoteProperty
遍历自定义对象的属性
- 您可以使用
$object."$propName"
语法或$object."$(some PS expression)"
. 动态访问对象的属性
- 您可以创建自己的自定义对象并使用
New-Object PSObject -Property @{...}
的一组属性对其进行初始化,或者[PSCustomObject]@{ .. }
`
这个问题经常出现。在这种情况下,我们必须遍历属性两次。这是我目前的答案。使对象更容易使用。顶层和数据属性都变成了“名称”和“值”的数组。您可以使用 select-object 计算属性以任何您想要的方式呈现它。似乎在 JSON 中你更经常获得随机属性,而不是相同属性的数组。
$a = cat file.json | convertfrom-json
$a = $a.psobject.properties | select name,value
$a | foreach { $_.value.data =
$_.value.data.psobject.properties | select name,value }
$a.value.data.value
value
-----
{Mr}
{Jack}
{Cawles}
{Miss}
{Charlotte}
{Tann}
{Mr}
{John}
{Brokland}
尝试使用 jq 进行类似操作:
'{"prop1":1, "prop2":2, "prop3":3}' | jq to_entries | convertfrom-json
key value
--- -----
prop1 1
prop2 2
prop3 3
此外,Powershell 7 中的 convertFrom-Json 有一个 -AsHashTable 参数,它为您提供键和值属性。
$a = '{"name":"joe","address":"here"}' | ConvertFrom-Json -AsHashtable
$a
Name Value
---- -----
name joe
address here
$a.keys
name
address
$a.values
joe
here
首先,我们将使用 ConvertFrom-Json
cmdlet 将 JSON 字符串转换为 PowerShell 数据结构。
然后,为了说明如何循环遍历嵌套的 PowerShell 数据结构,我们将通过一个简化示例进行演示。
给定
$response = [PSCustomObject] @{
prediction = [PSCustomObject] @{
cat = 0.6576587659
dog = 0.3423412341
}
}
我们的目标是遍历预测中的键值对(即猫和狗)并将它们的值缩短到小数点后 3 位。
求解
$response.prediction | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
[PSCustomObject]@{Key = $key; Value = "{0:N3}" -f $response.prediction.$key}
}
我们首先遍历预测的所有成员,然后为每个成员分配一个新的成员键和一个 3 位小数位值。
输出
Key Value
--- -----
cat 0.658
dog 0.342
如上评论所述,无需建造火箭即可解决此任务 =)
要遍历 json 文件,我们可以引用一个 powershell 对象变量(我们考虑 json)
看看有什么$data.PsObject.Properties那里有很多有趣的东西=)
决定:
$data = ConvertFrom-Json $json
foreach ($elem in $data.PsObject.Properties.Value)
{
Write-Host "Title:" $elem.data.3.value
Write-Host "First Name:" $elem.data.4.value
Write-Host "Surname:" $elem.data.5.value
}