Home > Enterprise >  Understanding Powershell: example - Convert JSON to CSV
Understanding Powershell: example - Convert JSON to CSV

Time:01-05

I've read several posts (like Convert JSON to CSV using PowerShell) regarding using PowerShell to CSV. I have also read that it is relatively poor form to use the pipe syntax in scripts -- that it's really meant for command line and can create a hassle for developers to maintain over time.

Using this sample JSON file...

[
    {
        "a":  "Value 1",
        "b":  20,
        "g":  "Arizona"
    },
    {
        "a":  "Value 2",
        "b":  40
    },
    {
        "a":  "Value 3"
    },
    {
        "a":  "Value 4",
        "b":  60
    }
]

...this code...

((Get-Content -Path $pathToInputFile -Raw) | ConvertFrom-Json) | Export-CSV $pathToOutputFile -NoTypeInformation

...creates a file containing CSV as expected.

"a","b","g"
"Value 1","20","Arizona"
"Value 2","40",
"Value 3",,
"Value 4","60",

This code...

$content = Get-Content -Path $pathToInputFile -Raw
$psObj = ConvertFrom-Json -InputObject $content
Export-Csv -InputObject $psObj -LiteralPath $pathToOutputFile -NoTypeInformation

...creates a file containing nonsense:

"Count","Length","LongLength","Rank","SyncRoot","IsReadOnly","IsFixedSize","IsSynchronized"
"4","4","4","1","System.Object[]","False","True","False"

It looks like maybe an object definition(?).
What is the difference? What PowerShell nuance did I miss when converting the code?

The answer to Powershell - Export a List of Objects to CSV says the problem is from the -InputObject option causing the object, not it's contents, to be sent to Export-Csv, but doesn't state how to remedy the problem without using the pipe syntax. I'm thinking something like -InputObject $psObj.contents. I realize that's not a real thing, but I Get-Members doesn't show me anything that looks like it will solve this.

CodePudding user response:

Get-Members doesn't show me anything that looks like it will solve this

Get-Member

It's because how you pass values has different behavior.

The pipeline enumerates values, it's almost like a foreach($item in $pipeline). Passing by Parameter skips that

Here I have an array of 3 letters.

$Letters = 'a'..'c'

I'm getting different types

Get-Member -InputObject $Letters
# [Object[]]

# [char]
$letters | Get-Member

Processed for each item

$letters | ForEach-Object { 
    "iteration: $_"
}
iteration: a
iteration: b
iteration: c

Compare to

ForEach-Object -InputObject $Letters { 
    "iteration: $_"
}
iteration: a b c

Detecting types

Here's a few ways to inspect objects.

using ClassExplorer

PS> ($Letters).GetType().FullName
PS> ($Letters[0]).GetType().FullName  # first child

    System.Object[]
    System.Char

PS> $Letters.count
PS> $Letters[0].Count

    3
    1

$Letters.pstypenames -join ', '
$Letters[0].pstypenames -join ', '

    System.Object[], System.Array, System.Object
    System.Char, System.ValueType, System.Object

Tip: $null.count always returns 0. It does not throw an error.

if($neverExisted.count -gt 1) { ... }

Misc

I have also read that it is relatively poor form to use the pipe syntax in scripts

This is not true, Powershell is designed around piping objects.

Tip: $null.count always returns 0. It does not throw an error.

Maybe They were talking about

Example2: slow operations

Some cases when you need something fast, the overhead to Foreach-Object over a foreach can be an issue. It makes it so you have to use some extra syntax.

If you really need speed, you should probably be calling dotnet methods anyway.

Example1: Piping when you could use a parameter

I'm guessing they meant piping a variable in cases where you can pass parameters?

$text = "hi-world"

# then
$text | Write-Host 

# vs
Write-Host -InputObject $Text

CodePudding user response:

This is not meant as an answer but just to give you a vague representation of what ConvertTo-Csv and Export-Csv are doing and to help you undertand why -InputObject is meant to be bound from the pipeline and should not be used manually.

function ConvertTo-Csv2 {
param(
    [parameter(ValueFromPipeline)]
    [Object]$InputObject
)
    begin
    {
        $isFirstObject = $true
        $csv = [System.Text.StringBuilder]::new()
    }
    process
    {
        if($isFirstObject)
        {
            $headers = $InputObject.PSObject.Properties.Name
            $isFirstObject = $false
            $line = [string]::Format('"{0}"',($headers -join '","'))
            $null = $csv.AppendLine($line)
        }

        $values = foreach($prop in $headers)
        {
            $InputObject.$prop
        }
        $line = [string]::Format('"{0}"',($values -join '","'))
        $null = $csv.AppendLine($line)
    }
    end
    {
        $csv.ToString()
    }
}
  •  Tags:  
  • Related