0

I have a problem with a function:

Function Get-Data()
{
    $data = @{}
    $data.Add("Critical",1)
    $data.Add("Warning",2)
    $data.Add("Information",3)
    $data.Add("Summary","test")
    Write-Host "function"
    $data | ConvertTo-Json
    return $data
}

Write-Host "main"
$test = Get-Data
$test | ConvertTo-Json

here the false result:

[
    "{\r\n    \"Summary\":  \"test\",\r\n    \"Critical\":  1,\r\n    \"Warning\":  2,\r\n    \"Information\":  3\r\n}",
    {
        "Summary":  "test",
        "Critical":  1,
        "Warning":  2,
        "Information":  3
    }
]

what it should be:

{
    "Summary":  "test",
    "Critical":  1,
    "Warning":  2,
    "Information":  3
}
zett42
  • 25,437
  • 3
  • 35
  • 72

2 Answers2

0

You are currently converting to JSON inside of the function, which is propably intended as debug output only. Due to PowerShell's implicit output behaviour, both of these statement actually add to the output of the function:

$data | ConvertTo-Json   # implict output
return $data             # like $data; return

After calling the function Get-Data, the variable $test will be an array:

  • $test[ 0 ] = $data converted to JSON string
  • $test[ 1 ] = the unconverted $data (hashtable)

After converting $test to JSON, you can see that array, indicated by the JSON beginning with [.

What you actually want is to prevent the JSON from inside the function to "leak" into the success stream of the function. E. g.:

Function Get-Data()
{
    $data = @{}
    $data.Add("Critical",1)
    $data.Add("Warning",2)
    $data.Add("Information",3)
    $data.Add("Summary","test")
    Write-Host "function"
    $data | ConvertTo-Json | Out-Host   # Output to host instead of implict success stream
    $data                               # Implicit output, no "return" statement needed
}

Now we are outputting the JSON to the host stream, which will just be printed to the console, but will be separate from the actual function output.

While the host stream is OK for prototyping or code that will be logged in any case, a better choice might be the verbose stream, which will not be written to by default:

Function Get-Data()
{
    [Cmdletbinding()] 
    Param()

    $data = @{}
    $data.Add("Critical",1)
    $data.Add("Warning",2)
    $data.Add("Information",3)
    $data.Add("Summary","test")
    Write-Verbose "function"
    $data | ConvertTo-Json | Write-Verbose  # Write to verbose stream
    $data                               # Implicit output, no "return" statement needed
}

Now users of your function would have to explicitly switch on verbose logging to actually see the JSON output of the function:

Get-Data -Verbose

To enable this common parameter, I had to convert the function into an advanced function by inserting [Cmdletbinding()] and Param() at the top.


Further information about PowerShell's implict output behaviour. It is aimed at a different question, but much of it is relevant to the current question as well.

zett42
  • 25,437
  • 3
  • 35
  • 72
0

With line $data | ConvertTo-Json your function already returns the Hashtable converted to JSON. The variable $data is not altered by this, so if you do a return $data after that, then the Hashtable is also output.

$test = Get-Data therefore receives TWO different outputs which get collected as array (Object[])

You can test that with

$test = Get-Data
$test.GetType()

$test[0] will contain the JSON as string

{
    "Summary":  "test",
    "Critical":  1,
    "Warning":  2,
    "Information":  3
}

and $test[1] contains the Hashtable

Name                           Value                                                                                                                                              
----                           -----                                                                                                                                              
Summary                        test                                                                                                                                               
Critical                       1                                                                                                                                                  
Warning                        2                                                                                                                                                  
Information                    3

If after that you are performing yet another $test | ConvertTo-Json, you'll end up with the conversion of whatever is in the array $test.

If your intention is to always return a JSON string, then remove return $data.
If the meaning of the function is to always return a Hashtable, then remove $data | ConvertTo-Json

If you want to be able to use both options, then you could adjust your function to become something like

function Get-Data {
    [CmdletBinding()]
    Param(
        [ValidateSet('Hashtable','PsCustomObject','Json')]
        [string]$As = 'Json'  # you can set a default output type
    )
    $data = @{}
    $data["Critical"] = 1
    $data["Warning"] = 2
    $data["Information"] = 3
    $data["Summary"] = "test"
    switch ($As) {
        'Hashtable'      { $data }
        'PsCustomObject' { [PsCustomObject]$data }
        default          { $data | ConvertTo-Json }
    }
}

Now you can choose what type it returns using the -As parameter.

Theo
  • 57,719
  • 8
  • 24
  • 41