英文:
How to force array with one element to generate square brackets in json
问题
我试图使用以下代码创建一个JSON数组:
$bodyObject = @(
@{
'Username' = 'email0@email.com'
}
)
$body = $bodyObject | ConvertTo-Json
但是$body
对象不包含方括号:
{
"Username": "email0@email.com"
}
如果我添加另一个元素到数组,代码就可以正常工作:
$bodyObject = @(
@{
'Username' = 'email0@email.com'
},
@{
'Username' = 'email1@email.com'
}
)
$body = $bodyObject | ConvertTo-Json
输出将包含方括号:
[
{
"Username": "email0@email.com"
},
{
"Username": "email1@email.com"
}
]
如何使包含一个元素的数组生成包含方括号的JSON呢?
英文:
I'm trying to create a JSON array using:
$bodyObject = @(
@{
'Username' = 'email0@email.com'
}
)
$body = $bodyObject | ConvertTo-Json
But the $body
object doesn't contain the square brackets:
{
"Username": "email0@email.com"
}
If I add another element to the array, the code works perfectly:
$bodyObject = @(
@{
'Username' = 'email0@email.com'
},
@{
'Username' = 'email1@email.com'
}
)
$body = $bodyObject | ConvertTo-Json
<# Output:
[
{
"Username": "email0@email.com"
},
{
"Username": "email1@email.com"
}
]
#>
How can I get one element arrays to generate JSON containing the square brackets?
答案1
得分: 4
最简单的方法是通过位置传递数组,而不是通过管道传递:
$body = ConvertTo-Json $bodyObject
之所以在第一个示例中看不到数组,是因为管道逐个枚举。
英文:
The simplest way to do it is to pass the array positionally instead of through the pipeline:
$body = ConvertTo-Json $bodyObject
Reason why you don't see the array in the first example is because the pipeline enumerates.
答案2
得分: 4
为了补充Santiago的有益回答:
-
PowerShell的管道的枚举行为意味着接收命令从根本上无法区分是作为(a) 单个输入对象还是(b) 单元素数组提供的管道输入。
-
也就是说,以下两个命令都通过管道发送了一个单一的
[int]
实例:(42 | ForEach-Object GetType).Name
->Int32
(@(42) | ForEach-Object GetType).Name
->Int32
-
-
相比之下,当将输入作为参数传递时,目标命令可以进行这种区分 - 如果设计成这样的话 - 而
ConvertTo-Json
也可以。- 然而,命令很少进行这种区分 - 请参阅GitHub问题#4242以获取讨论。
作为将输入作为参数的替代方法,PowerShell (Core) 7+引入了-AsArray
开关,它请求即使是一个单一的输入对象(最初可能是一个单元素数组)也应该被视为其JSON表示中的一个_数组_。
# 仅适用于PS v7+;@(42)也适用作为输入。
42 | ConvertTo-Json -AsArray -Compress # -> '[42]'
正如iRon指出的,您可以通过确保给定的数组 - 即使只包含一个_元素_ - 作为一个整体通过pipeline发送来实现相同的结果,这也适用于_Windows PowerShell_。
- 注意:虽然使用
ConvertTo-Json
将数组作为_参数_传递更加简单,如Santiago的答案中所示,但下面的技巧可能对不支持传递数组值参数或仅支持管道输入的命令也很有兴趣。
# 在Windows PowerShell中也适用。
# “,”运算符的一元形式确保数组*作为一个整体*通过管道发送。
, @(42) | ConvertTo-Json -Compress # -> '[42]'
,
的一元形式,即数组构造器(“逗号”)运算符在这里充当一个_瞬时的辅助_数组:
- 它的唯一元素是输入数组。
- 当管道_枚举_这个数组时,它的唯一元素 - 感兴趣的数组 - 作为一个整体通过管道发送。
还有一种不太晦涩但不太高效的替代方法,使用带有其-NoEnumerate
开关的Write-Output
:
# 在Windows PowerShell中也适用。
# -NoEnumerate防止枚举输入数组
# 并将其作为一个整体通过管道发送。
Write-Output -NoEnumerate @(42) | ConvertTo-Json -Compress # -> '[42]'
注意:
-
虽然结果与v7+的
-AsArray
开关相同,但机制不同: -
使用辅助数组/非枚举技巧,
ConvertTo-Json
真正接收一个_数组_作为其唯一的输入对象。 -
使用v7+的
-AsArray
开关,当它接收到一个标量(非数组)作为其唯一的输入对象时,它仍然将其视为数组。 -
如果接收到_多个_输入对象,
-AsArray
是一个无操作,因为即使没有此开关,必须输出一个JSON数组,因为ConvertTo-Json
始终在前台收集其输入,然后为其输出一个_单个_JSON文档。 -
不要将
-AsArray
与_参数_(而不是管道输入)组合使用,因为这将导致_嵌套_的JSON数组,至少在此写作时是这样(PowerShell 7.3.4):ConvertTo-Json -AsArray -Compress @(42) # !! -> '[[42]]'
- GitHub问题#10952讨论了这种行为,这可能会发生变化。
英文:
To complement Santiago's helpful answer:
-
The enumeration behavior of PowerShell's pipeline means that a receiving command fundamentally cannot tell the difference between pipeline input that was provided as (a) a single input object or (b) as a single-element array.
-
That is, the following two commands both send a single
[int]
instance through the pipeline:(42 | ForEach-Object GetType).Name
->Int32
(@(42) | ForEach-Object GetType).Name
->Int32
-
-
By contrast, when passing input as an argument, the target command can make such a distinction - if designed to do so - and
ConvertTo-Json
does.- However, it is rare for cmdlets to make this distinction - see GitHub issue #4242 for a discussion.
As an alternative to passing input by argument, PowerShell (Core) 7+ introduced the <br>-AsArray
switch, which requests that even a single input object (which may have been a single-element array originally) be treated as an array in its JSON representation.
# PS v7+ only; ditto for @(42) as input.
42 | ConvertTo-Json -AsArray -Compress # -> '[42]'
As iRon points out, you can achieve the same outcome by ensuring that a given array - even if it contains just one element - is sent through the pipeline as a whole, which also works in Windows PowerShell.
- Note: While with
ConvertTo-Json
it's much simpler to pass an array as an argument toConvertTo-Json
, as shown in Santiago's answer, the techniques below may be of interest for commands that do not support passing array-valued arguments or support pipeline input only.
# Works in Windows PowerShell too.
# The unary form of the "," operator ensures that the array
# is sent *as a whole* through the pipeline.
, @(42) | ConvertTo-Json -Compress # -> '[42]'
The unary form of ,
, the array constructor ("comma") operator constructs what acts as a transient, auxiliary array here:
- Its one and only element is the input array.
- When the pipeline enumerates this array, its one and only element - the array of interest - is sent as a whole through the pipeline.
There's a less obscure - but less efficient - alternative, using Write-Output
with its -NoEnumerate
switch:
# Works in Windows PowerShell too.
# -NoEnumerate prevents enumeration of the input array
# and sends it through the pipeline as a whole.
Write-Output -NoEnumerate @(42) | ConvertTo-Json -Compress # -> '[42]'
Note:
-
While the result is the same as with the v7+
-AsArray
switch, the mechanism is different: -
With the auxiliary-array / non-enumeration technique,
ConvertTo-Json
truly receives an array as its one and only input object. -
With the v7+
-AsArray
switch, when it receives a scalar (non-array) as its only input object, it still treats it as an array. -
If multiple input objects are received,
-AsArray
is a no-op, because even without this switch a JSON array must of necessity be output, given thatConvertTo-Json
alway collects its input up front and then outputs a single JSON document for it. -
Do not use
-AsArray
in combination with an argument (as opposed to pipeline input), as that will result in a nested JSON array, at least as of this writing (PowerShell 7.3.4):ConvertTo-Json -AsArray -Compress @(42) # !! -> '[[42]]'
- GitHub issue #10952 discusses this behavior, which may change.
The design rationale behind PowerShell's enumeration behavior:
PowerShell is built around pipelines: data conduits through which objects stream, one object at a time.<sup>[1]</sup>
PowerShell commands output to the pipeline by default, and any command can write any number of objects, including none - and that number isn't known in advance, because it can vary depending on arguments and external state.
- E.g.,
Get-ChildItem *.txt
can situationally emit none, 1, or multiple objects.
Since the pipeline is just a stream of objects of unspecified count, there is no concept of an array in the pipeline itself, neither on input nor on output:
-
On input, arrays (and most enumerables)<sup>[2]</sup> are enumerated, i.e. the elements are sent one by one to the pipeline. Therefore, there is no difference between sending a scalar (single object) and sending a single-element array through the pipeline, as demonstrated above.
-
On output, multiple objects are simply output one at a time (though it is possible, but rare, to send an array (or other list-like type) as a whole, but it is then itself just another, single output object in the pipeline).
-
It is only when you collect a pipeline's output that arrays come into play, of necessity:
-
A single output object needs no container, and can just be received as itself.
-
Multiple objects need a container, and PowerShell automatically creates a
System.Object[]
array to collect the output objects in.
-
-
<sup>[1] You can introduce buffering of multiple objects with the common -OutBuffer parameter, but the next command in a pipeline still receives the buffered objects one by one.</sup>
<sup>[2] For details, see the bottom section of this answer.</sup>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论