意外的 ConvertTo-Json 结果?答案:它有一个默认的 -Depth 为 2

Unexpected ConvertTo-Json results? Answer: it has a default -Depth of 2

为什么我会得到意外的 ConvertTo-Json 结果,为什么我会得到像 System.Collections.Hashtable and/or 这样的值 为什么 round-trip ($Json | ConvertFrom-Json | ConvertTo-Json) 会失败?

元问题

Whosebug 有一个很好的机制来防止重复的 questions 但据我所知没有机制来防止有重复的 cause。以这个问题为例:几乎每周都会有一个新问题以相同的原因出现,但通常很难将其定义为重复问题,因为问题本身只是略有不同。 尽管如此,如果这个 question/answer 本身以重复(或 off-topic)结束,我不会感到惊讶,但不幸的是,Whosebug 没有可能 write an article 来阻止其他程序员继续编写问题被这个“已知”的陷阱。

重复

具有相同共同原因的类似问题的几个示例:

不同

那么,这个“self-answered”问题与上面的重复问题有什么不同吗?
它在标题中有共同的原因,因此它可能会更好地防止由于相同的原因而重复问题。

回答

ConvertTo-Json 有一个 -Depth 参数:

Specifies how many levels of contained objects are included in the JSON representation.
The default value is 2.

例子

要对 JSON 文件执行完整的 round-trip,您需要为 ConvertTo-Json cmdlet 增加 -Depth

$Json | ConvertFrom-Json | ConvertTo-Json -Depth 9

TL;DR

可能是因为 ConvertTo-Json 终止了比默认值 -Depth (2) 更深的分支,具有 (.Net) 完整类型名称,程序员假设错误或 cmdlet 限制,请勿阅读帮助或有关内容。
就个人而言,我认为一个简单的字符串 ellipsis (three dots: …) at the end of the cut off branch, would have a clearer meaning (see also: Github issue: 8381)

为什么?

这个问题也经常在另一个讨论中结束:为什么深度受限?

有些 object 有循环引用,这意味着 child object 可以引用 parent(或者它的 grandparent 之一) 如果序列化为 JSON.

会导致无限循环

以下面的散列 table 为例,其中 parent 属性 指的是 object 本身:

$Test = @{Guid = New-Guid}
$Test.Parent = $Test

如果你执行:$Test | ConvertTo-Json默认情况下它会方便地停在深度级别2:

{
    "Guid":  "a274d017-5188-4d91-b960-023c06159dcc",
    "Parent":  {
                   "Guid":  "a274d017-5188-4d91-b960-023c06159dcc",
                   "Parent":  {
                                  "Guid":  "a274d017-5188-4d91-b960-023c06159dcc",
                                  "Parent":  "System.Collections.Hashtable"
                              }
               }
}

这就是为什么将 -Depth 自动设置为较大值并不是一个好主意。

更新PowerShell 7.1 在发生截断 时引入了警告。虽然这比之前的 quiet 截断要好,但下面建议的解决方案对我来说似乎更可取。


您的问题和 清楚地说明了当前默认 ConvertTo-Json 行为的痛点有多大。

至于行为的理由

虽然 -Depth 可用于 有意地 截断不需要其完整深度的输入对象树,-Depth 默认2悄悄截断输出相当于安静的事实上的失败从毫无戒心的用户的角度来看的序列化 - 可能直到后来才发现的失败。

对于大多数用户来说,看似任意和安静的截断是令人惊讶的,并且在每次 ConvertTo-Json 调用中都要考虑它是一种不必要的负担。

我创建了GitHub issue #8393,其中包含一个提议来改变当前的行为,具体如下:

  • 忽略 -Depth[pscustomobject] 对象图(概念上 DTO 的层次结构(数据传输对象,“属性 包"),例如从 Convert*From*-Json) 返回的,具体来说。

    • 相比之下,确实任意 .NET 类型设置自动深度限制是有意义的,因为它们可能是深度过大的对象图,甚至可能包含循环引用;例如,Get-ChildItem | ConvertTo-Json 可能很快就会失控,-Depth 值低至 4。也就是说,通常不建议将任意 .NET 类型与 JSON 序列化 一起使用:JSON 是 而不是 设计为给定平台类型的通用序列化格式;相反,它专注于 DTO,仅包含 属性 ,具有 有限的一组数据类型

    • 请注意,嵌套的 集合 ,包括哈希表,本身不受深度限制,只有它们的(标量)元素.

    • DTO 和其他类型之间的这种区别实际上由 PowerShell 本身在幕后使用,即在远程处理后台作业.

      的序列化
  • 使用 -Depth 然后只需要 有意地 截断指定深度的输入对象树(或者,主要是假设,为了序列化到比内部最大深度限制更深的级别,100)。