42

Why do I get unexpected ConvertTo-Json results, why do I get values like System.Collections.Hashtable and/or why does a round-trip ($Json | ConvertFrom-Json | ConvertTo-Json) fail?

Meta issue

Stackoverflow has a good mechanism to prevent duplicate questions but as far as I can see there is no mechanism to prevent questions that have a duplicate cause. Take this question as a an example: almost every week a new question comes in with the same cause, yet it is often difficult to define it as a duplicate because the question itself is just a slightly different. Nevertheless, I wouldn't be surprised if this question/answer itself ends up as a duplicate (or off-topic) but unfortunately stackoverflow has no possibility to write an article to prevent other programmers from continuing writing questions caused by this “known” pitfall.

Duplicates

A few examples of similar questions with the same common cause:

Different

So, were does this “self-answered” question differ from the above duplicates?
It has the common cause in the title and with that it might better prevent repeating questions due to the same cause.

iRon
  • 20,463
  • 10
  • 53
  • 79
  • I had the same issue. It got me asking though what GOOD comes out of the -Depth parameter? It seems to cause so much confusion... – Omglolyes Apr 21 '20 at 18:09
  • @Omglolyes This is adressed in the top rated answer, essentially objects can be self referencing, creating an infinite loop if you tried to go to "maximum" depth. – PMental Nov 13 '20 at 17:03
  • 1
    I think that you can add [my question](https://stackoverflow.com/questions/66123767/a-passing-problem-with-pscustomobject-when-passing-from-powershell-to-windows-po) to the list. – KUTlime Feb 16 '21 at 09:46
  • I get this error when I define a powershell custom object that has a child object, and I've forgotten the @ before that child object opening brace. – Christopher Jul 03 '22 at 06:12
  • 1
    @Chris, `@{...}` defines one or more key-value pairs, where `{...}` defines an expression which (when evaluated at runtime) might result in a pretty complex object depending on the content. – iRon Jul 03 '22 at 08:06

3 Answers3

42

Answer

ConvertTo-Json has a -Depth parameter:

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

Example

To do a full round-trip with a JSON file you need to increase the -Depth for the ConvertTo-Json cmdlet:

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

TL;DR

Probably because ConvertTo-Json terminates branches that are deeper than the default -Depth (2) with a (.Net) full type name, programmers assume a bug or a cmdlet limitation and do not read the help or about.
Personally, I think a string with a simple ellipsis (three dots: …) at the end of the cut off branch, would have a clearer meaning (see also: Github issue: 8381)

Why?

This issue often ends up in another discussion as well: Why is the depth limited at all?

Some objects have circular references, meaning that a child object could refer to a parent (or one of its grandparents) causing a infinitive loop if it would be serialized to JSON.

Take for example the following hash table with a parent property that refers to the object itself:

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

If you execute: $Test | ConvertTo-Json it will conveniently stop at a depth level of 2 by default:

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

This is why it is not a good idea to automatically set the -Depth to a large amount.

iRon
  • 20,463
  • 10
  • 53
  • 79
  • 22
    Although, since circular dependencies are not supported in JSON, writing a serializer that does not support circular dependencies seems the more natural choice over one that only walks to a certain depth. All major JSON serializers *other than* the one in PowerShell do this. I'd call it "poor design decision" instead of "necessity". – Tomalak Dec 02 '18 at 19:36
  • 4
    Powershell 7.1 will give a warning. "Emit warning if ConvertTo-Json exceeds -Depth value (#13692)" https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-71?view=powershell-7.1 – js2010 Nov 19 '20 at 16:29
16

Update: PowerShell 7.1 introduced a warning when truncation occurs. While that is better than the quiet truncation that occurred in earlier versions, the solution suggested below seems much preferable to me.


Your helpful question and answer clearly illustrate how much of a pain point the current default ConvertTo-Json behavior is.

As for the justification of the behavior:

While -Depth can be useful to intentionally truncate an input object tree whose full depth you don't need, -Depth defaulting to 2 and quietly truncating the output amounts to quiet de-facto failure of the serialization from the unsuspecting user's perspective - failure that may not be discovered until later.

The seemingly arbitrary and quiet truncation is surprising to most users, and having to account for it in every ConvertTo-Json call is an unnecessary burden.

I've created GitHub issue #8393 containing a proposal to change the current behavior, specifically as follows:

  • Ignore -Depth for [pscustomobject] object graphs (a hierarchy of what are conceptually DTOs (data-transfer objects, "property bags"), such as returned from Convert*From*-Json), specifically.

    • By contrast, it does make sense to have an automatic depth limit for arbitrary .NET types, as they can be object graphs of excessive depths and may even contain circular references; e.g., Get-ChildItem | ConvertTo-Json can get quickly out of hand, with -Depth values as low as 4. That said, it is generally ill-advised to use arbitrary .NET types with JSON serialization: JSON is not designed to be a general-purpose serialization format for a given platform's types; instead, it is focused on DTOs, comprising properties only, with a limited set set of data types.

    • Note that nested collections, including hashtables, are not themselves subject to the depth limit only their (scalar) elements.

    • This distinction between DTOs and other types is, in fact, employed by PowerShell itself behind the scenes, namely in the context of serialization for remoting and background jobs.

  • Use of -Depth is then only needed to intentionally truncate the input object tree at the specified depth (or, mostly hypothetically, in order to serialize to a deeper level than the internal maximum-depth limit, 100).

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 2
    This Depth parameter deserves the WTF of the year award. That GitHub issue is just completely sad. How can people argue for the sensibility of this behavior? It's OBVIOUSLY wrong and they "fixed" it with a warning. Haha! Optimizing for the edge case as per usual with powershell. – Skrymsli Nov 05 '21 at 23:31
  • I hear you, @Skrymsli. – mklement0 Nov 06 '21 at 00:48
-1

.. | ConvertTo-Json -Depth 100 | ..

The max depth is 100 in latest PowerShell (mine: 7.3.4, renamed from PowerShell Core).

yzorg
  • 4,224
  • 3
  • 39
  • 57
  • to convert JSON to pretty-printed JSON see https://stackoverflow.com/a/76133115/195755 (this answer used there) – yzorg Apr 28 '23 at 20:39