10

Observe:

C:\> [array]@(1,2) | ConvertTo-Json
[
    1,
    2
]
C:\> [array]@(1) | ConvertTo-Json
1
C:\> [array]@() | ConvertTo-Json
C:\>

(I expect [1] and [] from the last two cases respectively)

So, if I want to use the standard ConvertTo-Json method, how do I do it reliably, even when the array contains 1 or 0 elements?

Note, that post-processing the result is not feasible when the array is part of a complex object converted to json.

EDIT 1

C:\> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.17763.592
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.17763.592
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1


C:\> [array]@(1) | ConvertTo-Json -AsArray
ConvertTo-Json : A parameter cannot be found that matches parameter name 'AsArray'.
At line:1 char:30
+ [array]@(1) | ConvertTo-Json -AsArray
+                              ~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [ConvertTo-Json], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.ConvertToJsonCommand

C:\>
2
  • 2
    Use the -AsArray switch. Commented Oct 30, 2019 at 0:52
  • Good pointer, @AdminOfThings, but worth pointing out that -AsArray is only supported in PowerShell Core; Also note that it doesn't work as one might expect with an empty array: e.g., ConvertTo-Json -AsArray @() -Compress yields [[]]. Commented Oct 30, 2019 at 13:52

2 Answers 2

8

Note:

  • The answer below discusses providing input to ConvertTo-Json via the pipeline and the pitfall of using an empty or a single-element array as input, as well as workarounds.

    • As js2010's helpful answer shows, this pitfall can most easily be avoided if you pass such an array as an argument (e.g. ConvertTo-Json (@1)), also explained below.

    • For pipeline input, PowerShell (Core) 7+ now offers the convenient -AsArray switch as a way to solve the problem, discussed in the bottom section.


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.

Sign up to request clarification or add additional context in comments.

Comments

7

Finally, a use for -InputObject:

convertto-json -InputObject @(1)
[
  1
]

convertto-json -InputObject @() 
[]

1 Comment

Yes, binding to -InputObject prevents the array enumeration that the pipeline performs by default; note that -InputObject in this case binds positionally, so you needn't specify -InputObject (which is why I omitted in in my answer); e.g., ConvertTo-Json @() will do.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.