Convierta la matriz JSON anidada en columnas separadas en el archivo CSV
powershell data-conversion (2)
Tengo un archivo JSON que se ve así:
{
"id": 10011,
"title": "Test procedure",
"slug": "slug",
"url": "http://test.test",
"email": "[email protected]",
"link": "http://test.er",
"subject": "testing",
"level": 1,
"disciplines": [
"discipline_a",
"discipline_b",
"discipline_c"
],
"areas": [
"area_a",
"area_b"
]
},
Intenté usar el siguiente comando para convertirlo en el archivo CSV:
(Get-Content "PATH_TO/test.json" -Raw | ConvertFrom-Json)| Convertto-CSV -NoTypeInformation | Set-Content "PATH_TO/test.csv"
Sin embargo, para disciplinas y áreas obtengo System.Object [] en el archivo CSV resultante.
¿Hay alguna manera de poner todos esos valores anidados como columnas separadas en un archivo CSV como area_1, area_2, etc. Y lo mismo para las disciplinas.
2017-11-20, Función completamente reescrita para mejorar el rendimiento y agregar características como
-ArrayBase
y soporte para PSStandardMembers y objetos agrupados.
Acoplar-Objeto
Recurrentemente aplana los objetos que contienen matrices, tablas hash y objetos (personalizados). Todas las propiedades agregadas de los objetos suministrados se alinearán con el resto de los objetos.
Requiere PowerShell versión 2 o superior.
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
Sintaxis
<Object[]> Flatten-Object [-Separator <String>] [-Base "" | 0 | 1] [-Depth <Int>] [-Uncut<Int>] [ToString <Type[]>]
o:
Flatten-Object <Object[]> [[-Separator] <String>] [[-Base] "" | 0 | 1] [[-Depth] <Int>] [[-Uncut] <Int>] [[ToString] <Type[]>]
Parámetros
-Object[] <Object[]>
El objeto (u objetos) a aplanar.
-Separator <String>
(Predeterminado:.)
El separador utilizado entre los nombres de propiedad recursiva.
.
-Depth <Int>
(Predeterminado:
5
)
La profundidad máxima de aplanar una propiedad recursiva.
Cualquier valor negativo dará como resultado una profundidad ilimitada y podría causar un bucle infinitivo.
-Uncut <Int>
(Predeterminado:
1
)
El número de iteraciones de
objetos
que dejarán sin cortar otras propiedades del objeto se limitará solo a
DefaultDisplayPropertySet
.
Cualquier valor negativo revelará todas las propiedades de todos los objetos.
-Base "" | 0 | 1
-Base "" | 0 | 1
(predeterminado:
1
)
El primer nombre de índice de una matriz incrustada:
-
1
, las matrices estarán basadas en 1:<Parent>.1
,<Parent>.2
,<Parent>.3
, ... -
0
, las matrices estarán basadas en 0:<Parent>.0
,<Parent>.1
,<Parent>.2
, ... -
""
, el primer elemento de una matriz no tendrá nombre y se seguirá con 1:<Parent>
,<Parent>.1
,<Parent>.2
, ...
-ToString <Type[]= [String], [DateTime], [TimeSpan]>
Una lista de tipos de valores (por defecto
[String], [DateTime], [TimeSpan]
) que se convertirán en string en lugar de aplanarse aún más.
Por ejemplo, un
[DateTime]
podría aplanarse con propiedades adicionales como
Date
,
Day
,
DayOfWeek
, etc., pero se convertirá en una sola propiedad (
String
).
Nota:
El parámetro
-Path
es para uso interno, pero podría usarse para prefijar nombres de propiedades.
Ejemplos
Respondiendo la pregunta específica:
(Get-Content "PATH_TO/test.json" -Raw | ConvertFrom-Json) | Flatten-Object | Convertto-CSV -NoTypeInformation | Set-Content "PATH_TO/test.csv"
Resultado:
{
"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": "[email protected]"
}
Prueba de tensión de un objeto personalizado más complejo:
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
Resultado:
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 :
Aplanar objetos agrupados:
$csv | Group Name | Flatten | Format-Table
$csv | Group Name | Flatten | Format-Table
# https://.com/a/47409634/1701026
Alisar objetos comunes:
(Get-Process)[0] | Flatten-Object
O una lista (matriz) de objetos:
Get-Service | Flatten-Object -Depth 3 | Export-CSV Service.csv
Tenga en cuenta que un comando como el siguiente toma horas para calcular:
Get-Process | Flatten-Object | Export-CSV Process.csv
¿Por qué?
porque da como resultado una tabla con unos cientos de filas y varios miles de columnas.
Entonces, si quisiera usar esto para el proceso de aplanamiento, mejor limite el número de filas (usando el cmdlet
Where-Object
) o el número de columnas (usando el cmdlet
Select-Object
).
Para obtener la última versión de
Flatten-Object
, consulte:
https://powersnippets.com/flatten-object/
Los cmdlets de conversión / exportación CSV no tienen forma de "aplanar" un objeto, y es posible que me falte algo, pero no conozco ninguna forma de hacerlo con un cmdlet o función incorporados.
Si puede garantizar que las
disciplines
y las
areas
siempre tendrán el mismo número de elementos, puede trivializarlo utilizando
Select-Object
con propiedades derivadas para hacer esto:
$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''
Sin embargo, supongo que las
disciplines
y
areas
serán de longitud variable para cada registro.
En ese caso, tendrá que recorrer la entrada y extraer el valor de conteo más alto para ambas disciplinas y áreas, luego construir dinámicamente la matriz de propiedades:
$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''
Este código no se ha probado completamente, por lo que puede necesitar algunos ajustes para funcionar al 100%, pero creo que las ideas son sólidas =)