One of the more frustrating PowerShell warnings you’ll eventually encounter is:

WARNING: Resulting JSON is truncated as serialization has exceeded the set depth of 2.

At first glance, the solution seems simple: increase the -Depth parameter.

$obj | ConvertTo-Json -Depth 10

But how deep does your object really go?

If you’re working with dynamic data from APIs such as Microsoft Graph, Azure, REST APIs, or custom applications, hardcoding a depth value is often little more than a guess. Set it too low and data gets truncated. Set it unnecessarily high and your code becomes harder to maintain.

A better approach is to calculate the actual depth of the object before serializing it.

The Problem with ConvertTo-Json

By default, PowerShell only serializes nested objects to a depth of 2.

Consider the following JSON:

{
  "user": {
    "profile": {
      "address": {
        "city": "Amersfoort"
      }
    }
  }
}

After converting it into a PowerShell object:

$obj = $json | ConvertFrom-Json

Using the default serialization:

$obj | ConvertTo-Json

Produces a warning and incomplete output because the object hierarchy exceeds the default depth.

This becomes particularly problematic when:

  • Consuming APIs
  • Storing API responses
  • Building automation workflows
  • Creating backup/export functionality
  • Working with Microsoft Graph responses

Introducing Get-JsonDepth

To solve this problem, I created the Get-JsonDepth function.

The function recursively traverses:

  • PSCustomObjects
  • Hashtables
  • Dictionaries
  • Arrays and collections

And returns the maximum nesting depth it encounters.

Example:

$depth = Get-JsonDepth -InputObject $obj

$depth

Output:

5

You can then safely use that value during serialization:

$obj | ConvertTo-Json -Depth $depth

No guessing required.

The Function

function Get-JsonDepth {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [AllowNull()]
        [object]$InputObject
    )

    begin {
        function Get-DepthInternal {
            param(
                [AllowNull()]
                [object]$Object,

                [int]$CurrentDepth = 1
            )

            if ($null -eq $Object) {
                return $CurrentDepth
            }

            if (
                $Object -is [string] -or
                $Object.GetType().IsPrimitive -or
                $Object -is [datetime] -or
                $Object -is [decimal] -or
                $Object -is [guid]
            ) {
                return $CurrentDepth
            }

            $childValues = @()

            if ($Object -is [System.Collections.IDictionary]) {
                $childValues = $Object.Values
            } elseif ($Object -is [System.Collections.IEnumerable] -and $Object -isnot [string]) {
                $childValues = @($Object)
            } elseif ($Object -is [psobject]) {
                $childValues = @(
                    $Object.PSObject.Properties |
                        ForEach-Object { $_.Value }
                )
            } else {
                return $CurrentDepth
            }

            if ($childValues.Count -eq 0) {
                return $CurrentDepth
            }

            $maxDepth = $CurrentDepth

            foreach ($child in $childValues) {
                $childDepth = Get-DepthInternal -Object $child -CurrentDepth $($CurrentDepth + 1)

                $maxDepth = [Math]::Max($maxDepth, $childDepth)
            }

            return $maxDepth
        }
    }

    process {
        Get-DepthInternal -Object $InputObject -CurrentDepth 1
    }
}

How It Works

The function evaluates the object type and inspects its children.

Dictionaries

if ($Object -is [System.Collections.IDictionary]) {
    $childValues = $Object.Values
}

Collections

elseif ($Object -is [System.Collections.IEnumerable] -and $Object -isnot [string]) {
    $childValues = @($Object)
}

PSCustomObjects

elseif ($Object -is [psobject]) {
    $childValues = @(
        $Object.PSObject.Properties |
            ForEach-Object { $_.Value }
    )
}

For each child, the function calls itself recursively while incrementing the depth counter.

$childDepth = Get-DepthInternal -Object $child -CurrentDepth $($CurrentDepth + 1)

The highest depth discovered becomes the result.

Real-World Example

Imagine you’re consuming Microsoft Graph:

$response = Invoke-RestMethod -Uri $Uri -Headers $Headers

$response | ConvertTo-Json

PowerShell may warn about truncation:

Resulting JSON is truncated as serialization has exceeded the set depth of 2.

Instead:

$depth = Get-JsonDepth $response

$response | ConvertTo-Json -Depth $depth

The exported JSON now accurately reflects the complete object hierarchy.

Why Not Just Use -Depth 100?

I’ve seen this recommendation countless times:

$obj | ConvertTo-Json -Depth 100

While it generally works, it has a few drawbacks.

1. Avoid Magic Numbers

Using a hardcoded value such as 100 raises immediate questions:

  • Why 100?
  • Why not 20, 50, or 500?
  • What assumption is this based on?

Without additional context, future maintainers have no way of knowing whether the value was carefully chosen or simply guessed.

2. Consider Performance

Traversing deeply nested object graphs can become increasingly expensive as recursion depth grows. By using a realistic maximum depth, you limit unnecessary processing, reduce resource consumption, and make the function’s behavior more predictable.

3. Self-Documenting Code

Compare:

$obj | ConvertTo-Json -Depth 100

With:

$depth = Get-JsonDepth $obj
$obj | ConvertTo-Json -Depth $depth

The second version immediately communicates intent.

Example Usage

$json = Get-Content .\input.json -Raw

$obj = $json | ConvertFrom-Json

$depth = Get-JsonDepth -InputObject $obj

Write-Host "Object depth: $depth"

$obj | ConvertTo-Json -Depth $depth

Potential Improvements

Depending on your use case, you could extend the function to:

  • Return both minimum and maximum depth
  • Display the deepest property path
  • Exclude specific property names
  • Detect circular references
  • Produce depth statistics for complex API responses

These enhancements can be useful when debugging large automation workflows or Graph API responses.

Conclusion

PowerShell’s JSON serialization is excellent, but the default depth of 2 often catches administrators and developers off guard when working with modern APIs and complex object structures.

Rather than guessing a depth value or defaulting to -Depth 100, consider calculating the required depth dynamically.

By determining the maximum nesting level of your object before serialization, you can:

  • Eliminate truncation warnings
  • Avoid arbitrary depth values
  • Improve maintainability
  • Make your scripts self-documenting
  • Produce more predictable JSON output

Get-JsonDepth is a small utility function, but it can remove a surprising amount of frustration when working with Microsoft Graph, Azure, REST APIs, and deeply nested JSON payloads.

Leave a Reply

Your email address will not be published. Required fields are marked *