json powershell csv data-conversion flatten

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 =)