将嵌套的 JSON 数组转换为 CSV 文件中的单独列
Posted
技术标签:
【中文标题】将嵌套的 JSON 数组转换为 CSV 文件中的单独列【英文标题】:Convert nested JSON array into separate columns in CSV file 【发布时间】:2018-01-31 10:55:53 【问题描述】:我有一个如下所示的 JSON 文件:
"id": 10011,
"title": "Test procedure",
"slug": "slug",
"url": "http://test.test",
"email": "test@test.com",
"link": "http://test.er",
"subject": "testing",
"level": 1,
"disciplines": [
"discipline_a",
"discipline_b",
"discipline_c"
],
"areas": [
"area_a",
"area_b"
]
,
我尝试使用以下命令将其转换为 CSV 文件:
(Get-Content "PATH_TO\test.json" -Raw | ConvertFrom-Json)| Convertto-CSV -NoTypeInformation | Set-Content "PATH_TO\test.csv"
但是,对于学科和领域,我在生成的 CSV 文件中获取 System.Object[]。
有没有办法将所有这些嵌套值作为单独的列放在 CSV 文件中,例如 area_1、area_2 等。学科也是如此。
【问题讨论】:
也许这个链接会有所帮助:***.com/questions/30485004/… 在您的示例中,该记录的“discipline_a”或“area_a”列中会显示什么值? @andyb 这些只是指定不同学科和领域的字符串。因此,例如,区域数组可能包含“数学”、“化学”。所以我想要在生成的 CSV 中包含“数学”的列“area_1”和包含“化学”的列“area_2”。 'area_'的数量。列应由特定对象可能具有的最大区域数确定 【参考方案1】:2017-11-20,完全重写功能以提高性能并添加 -ArrayBase
的功能并支持 PSStandardMembers 和分组对象。
扁平化对象
递归地展平包含数组、哈希表和(自定义)对象的对象。提供的 objects will be aligned 的所有添加属性以及其余对象。
需要 PowerShell 版本 2 或更高版本。
Cmdlet
Function Flatten-Object # Version 00.02.12, by iRon
[CmdletBinding()]Param (
[Parameter(ValueFromPipeLine = $True)][Object[]]$Objects,
[String]$Separator = ".", [ValidateSet("", 0, 1)]$Base = 1, [Int]$Depth = 5, [Int]$Uncut = 1,
[String[]]$ToString = ([String], [DateTime], [TimeSpan]), [String[]]$Path = @()
)
$PipeLine = $Input | ForEach $_; If ($PipeLine) $Objects = $PipeLine
If (@(Get-PSCallStack)[1].Command -eq $MyInvocation.MyCommand.Name -or @(Get-PSCallStack)[1].Command -eq "<position>")
$Object = @($Objects)[0]; $Iterate = New-Object System.Collections.Specialized.OrderedDictionary
If ($ToString | Where $Object -is $_) $Object = $Object.ToString()
ElseIf ($Depth) $Depth--
If ($Object.GetEnumerator.OverloadDefinitions -match "[\W]IDictionaryEnumerator[\W]")
$Iterate = $Object
ElseIf ($Object.GetEnumerator.OverloadDefinitions -match "[\W]IEnumerator[\W]")
$Object.GetEnumerator() | ForEach -Begin $i = $Base $Iterate.($i) = $_; $i += 1
Else
$Names = If ($Uncut) $Uncut-- Else $Object.PSStandardMembers.DefaultDisplayPropertySet.ReferencedPropertyNames
If (!$Names) $Names = $Object.PSObject.Properties | Where $_.IsGettable | Select -Expand Name
If ($Names) $Names | ForEach $Iterate.$_ = $Object.$_
If (@($Iterate.Keys).Count)
$Iterate.Keys | ForEach
Flatten-Object @(,$Iterate.$_) $Separator $Base $Depth $Uncut $ToString ($Path + $_)
Else $Property.(($Path | Where $_) -Join $Separator) = $Object
ElseIf ($Objects -ne $Null)
@($Objects) | ForEach -Begin $Output = @(); $Names = @()
New-Variable -Force -Option AllScope -Name Property -Value (New-Object System.Collections.Specialized.OrderedDictionary)
Flatten-Object @(,$_) $Separator $Base $Depth $Uncut $ToString $Path
$Output += New-Object PSObject -Property $Property
$Names += $Output[-1].PSObject.Properties | Select -Expand Name
$Output | Select ([String[]]($Names | Select -Unique))
; Set-Alias Flatten Flatten-Object
语法
<Object[]> Flatten-Object [-Separator <String>] [-Base "" | 0 | 1] [-Depth <Int>] [-Uncut<Int>] [ToString <Type[]>]
或:
Flatten-Object <Object[]> [[-Separator] <String>] [[-Base] "" | 0 | 1] [[-Depth] <Int>] [[-Uncut] <Int>] [[ToString] <Type[]>]
参数
-Object[] <Object[]>
要展平的对象(或多个对象)。
-Separator <String>
(默认:.
)
递归属性名称之间使用的分隔符。 .
-Depth <Int>
(默认:5
)
展平递归属性的最大深度。任何负值都将导致无限深度,并可能导致不定式循环。
-Uncut <Int>
(默认:1
)object 迭代的数量将被限制为仅DefaultDisplayPropertySet
。任何负值都会显示所有对象的所有属性。
-Base "" | 0 | 1
(默认:1
)
嵌入数组的第一个索引名称:
1
,数组将基于 1:<Parent>.1
,<Parent>.2
,<Parent>.3
,...
0
,数组将从 0 开始:<Parent>.0
、<Parent>.1
、<Parent>.2
、...
""
,数组中的第一项将是未命名的,然后是 1:<Parent>
,<Parent>.1
,<Parent>.2
,...
-ToString <Type[]= [String], [DateTime], [TimeSpan]>
将转换为字符串而不是进一步扁平化的值类型列表(默认为[String], [DateTime], [TimeSpan]
)。例如。 [DateTime]
可以使用 Date
、Day
、DayOfWeek
等其他属性进行展平,但将转换为单个 (String
) 属性。
注意:
参数 -Path
供内部使用,但也可以用作属性名称的前缀。
示例
回答具体问题:
(Get-Content "PATH_TO\test.json" -Raw | ConvertFrom-Json) | Flatten-Object | Convertto-CSV -NoTypeInformation | Set-Content "PATH_TO\test.csv"
结果:
"url": "http://test.test",
"slug": "slug",
"id": 10011,
"link": "http://test.er",
"level": 1,
"areas.2": "area_b",
"areas.1": "area_a",
"disciplines.3": "discipline_c",
"disciplines.2": "discipline_b",
"disciplines.1": "discipline_a",
"subject": "testing",
"title": "Test procedure",
"email": "test@test.com"
压力测试更复杂的自定义对象:
New-Object PSObject @
String = [String]"Text"
Char = [Char]65
Byte = [Byte]66
Int = [Int]67
Long = [Long]68
Null = $Null
Booleans = $False, $True
Decimal = [Decimal]69
Single = [Single]70
Double = [Double]71
Array = @("One", "Two", @("Three", "Four"), "Five")
HashTable = @city="New York"; currency="Dollar"; postalCode=10021; Etc = @("Three", "Four", "Five")
Object = New-Object PSObject -Property @Name = "One"; Value = 1; Text = @("First", "1st")
| Flatten
结果:
Double : 71
Decimal : 69
Long : 68
Array.1 : One
Array.2 : Two
Array.3.1 : Three
Array.3.2 : Four
Array.4 : Five
Object.Name : One
Object.Value : 1
Object.Text.1 : First
Object.Text.2 : 1st
Int : 67
Byte : 66
HashTable.postalCode : 10021
HashTable.currency : Dollar
HashTable.Etc.1 : Three
HashTable.Etc.2 : Four
HashTable.Etc.3 : Five
HashTable.city : New York
Booleans.1 : False
Booleans.2 : True
String : Text
Char : A
Single : 70
Null :
扁平化分组对象:
$csv | Group Name | Flatten | Format-Table
# https://***.com/a/47409634/1701026
扁平化常见对象:
(Get-Process)[0] | Flatten-Object
或对象列表(数组):
Get-Service | Flatten-Object -Depth 3 | Export-CSV Service.csv
请注意,以下命令需要数小时才能计算:
Get-Process | Flatten-Object | Export-CSV Process.csv
为什么? 因为它会生成一个包含几百行和几千列的表。因此,如果您想将其用于展平过程,最好限制行数(使用Where-Object
cmdlet)或列数(使用Select-Object
cmdlet)。
最新的Flatten-Object
版本见:https://powersnippets.com/flatten-object/
【讨论】:
@ste_irl:从技术上讲,是的,尽管您会丢失一些细节,因为您无法区分扁平格式的对象和哈希表,此外,数组@("one", "two")
和像这样的哈希表/对象:@'1' = 'one', '2' = 'two'
以及像 asp.net
这样的 key 将在 asp = @net = @...
中展开。无论如何,您可以试一试,如果遇到问题,请创建一个新问题寻求帮助..【参考方案2】:
CSV 转换/导出 cmdlet 无法“展平”对象,我可能会遗漏一些东西,但我知道无法使用内置 cmdlet 或功能来做到这一点。
如果您可以保证 disciplines
和 areas
将始终具有相同数量的元素,则可以通过使用带有派生属性的 Select-Object
来简化它:
$properties=@('id','title','slug','url','email','link','subject','level',
@Name='discipline_1';Expression=$_.disciplines[0]
@Name='discipline_2';Expression=$_.disciplines[1]
@Name='discipline_3';Expression=$_.disciplines[2]
@Name='area_1';Expression=$_.areas[0]
@Name='area_2';Expression=$_.areas[1]
)
(Get-Content 'PATH_TO\test.json' -Raw | ConvertFrom-Json)| Select-Object -Property $properties | Export-CSV -NoTypeInformation -Path 'PATH_TO\test.csv'
但是,我假设 disciplines
和 areas
对于每条记录都是可变长度的。在这种情况下,您将不得不循环输入并提取学科和领域的最高计数值,然后动态构建属性数组:
$inputData = Get-Content 'PATH_TO\test.json' -Raw | ConvertFrom-Json
$counts = $inputData | Select-Object -Property @Name='disciplineCount';Expression=$_.disciplines.Count,@Name='areaCount';Expression=$_.areas.count
$maxDisciplines = $counts | Measure-Object -Maximum -Property disciplineCount | Select-Object -ExpandProperty Maximum
$maxAreas = $counts | Measure-Object -Maximum -Property areaCount | Select-Object -ExpandProperty Maximum
$properties=@('id','title','slug','url','email','link','subject','level')
1..$maxDisciplines | %
$properties += @Name="discipline_$_";Expression=[scriptblock]::create("`$_.disciplines[$($_ - 1)]")
1..$maxAreas | %
$properties += @Name="area_$_";Expression=[scriptblock]::create("`$_.areas[$($_ - 1)]")
$inputData | Select-Object -Property $properties | Export-CSV -NoTypeInformation -Path 'PATH_TO\test.csv'
此代码尚未经过全面测试,因此可能需要进行一些调整才能 100% 工作,但我相信这些想法是可靠的 =)
【讨论】:
非常感谢!它像魔术一样工作。正是我需要的!我使用了您提供的第二段代码。以上是关于将嵌套的 JSON 数组转换为 CSV 文件中的单独列的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Jersey 将嵌套列表编组为 JSON?我得到一个空数组或一个包含数组的单元素字典数组