Note:
If $val
is either the empty array, a scalar, or an array, use , @($val) | ConvertTo-Json
to ensure that it gets serialized as an array:
if (-not $IsCoreCLR) { # Workaround for Windows PowerShell
# Only needed once per session.
Remove-TypeData -ErrorAction Ignore System.Array
}
# Send an empty array, a single object, and an array...
@(), 1, (1, 2) | ForEach-Object {
# ... and ensure that each input ($_) serializes as a JSON *array*.
, @($_) | ConvertTo-Json
}
Note:
The need for the Windows PowerShell workaround is explained in this answer.
,
, the array-construction operator, is used here in its unary form to provide an auxiliary, single-element wrapper array in order to send the array as a whole (as a single object) through the pipeline; by default, sending an array (enumerable) to the pipeline sends its elements one by one; note that this is fundamental pipeline behavior, irrespective of the cmdlets involved.
@(...)
, the "array-guarantor" operator (array-subexpression operator), ensures that $_
is an array, that is, it wraps the operand in an array, unless it already is one (loosely speaking[1]); this is necessary to cover the case of $_
containing only a single object (scalar; 1
in this case).
A general caveat: ConvertTo-Json
quietly limits its serialization depth to 2
by default, which results in quiet data loss with more deeply nested input; use the -Depth
parameter as needed.
This SO post discusses the problem.
GiHub issue #8393 asked for the treacherous default behavior to be changed; while that didn't happen, PowerShell 7+ now at least omits a warning then truncation occurs.
The above yields the following - note how each input was serialized as an array:
[]
[
1
]
[
1,
2
]
Alternatively, you can pass the inputs as arguments to ConvertTo-Json
with @($val)
:
# Same output as above.
@(), 1, (1,2) | ForEach-Object { ConvertTo-Json @($_) }
A positional argument implicitly binds to the -InputObject
parameter, which does not enumerate its argument and therefore binds arrays as-is.
Therefore you only need the "array guarantor" @()
in this case (not also a wrapper array with ,
).
PowerShell Core now offers an -AsArray
switch, which directly ensures that the input is serialized as an array, even if there's only a single input object:
PS> 1 | ConvertTo-Json -AsArray
[
1
]
However, given that empty arrays result in no data being sent through the pipeline, you still need a wrapper array if the input is the empty array and you then mustn't use -AsArray
:
# Note:
# @() | ConvertTo-Json -AsArray
# would result in NO output.
# Use `, ` to wrap the empty array to ensure it gets sent through
# the pipeline and do NOT use -AsArray
PS> , @() | ConvertTo-Json -Compress
[]
Alternatively, again pass the empty array as an argument:
PS> ConvertTo-Json @() -Compress # Do NOT use -AsArray
[]
The problem is that -AsArray
unconditionally wraps its input in a JSON array, so that something that already is an array is wrapped again:
PS> ConvertTo-Json -AsArray @() -Compress
[[]] # *nested* empty arrays
That -AsArray
does not act as an array "guarantor" the way that @(...)
does is discussed in GitHub issue #10952.
[1] If the operand is a scalar (single object), it is wrapped in a single-element [object[]]
; if the operand already is an array or is an enumerable, the elements are enumerated and captured in a new [object[]]
array.