PowerShell ConvertTo-Json Depth: Fix Truncated JSON Output

PowerShell ConvertTo-Json Depth: Fix Truncated JSON Output

PowerShell Tips Editor 4 min read
PowerShell ConvertTo-Json Depth: Fix Truncated JSON Output

Your PowerShell object converts to JSON fine — until you inspect the output and find deeply nested properties replaced by @{}. The culprit is PowerShell ConvertTo-Json depth: the default is 2, meaning any object more than two levels deep is silently flattened into a string representation. This post explains the depth parameter, when it matters, how to set the right value, and the trade-offs of going too deep.

Quick Answer / TL;DR

Add -Depth 10 (or whatever value your object requires) to ConvertTo-Json. Objects beyond the default depth of 2 are serialized as "@{}" strings rather than nested JSON objects — data loss you may not notice until parsing.

Default -Depth 2 Behavior

The default depth of 2 means the root object is level 1, its immediate properties are level 2, and anything inside those properties is truncated. This catches many admins by surprise because level 1 and 2 convert correctly — the truncation only appears at level 3 and beyond.

# Object with 3 levels of nesting
$config = @{
    App = @{
        Database = @{
            Server   = 'sql01'
            Port     = 1433
            Name     = 'AppDB'
        }
    }
}

# Default depth 2 — Database object is truncated
$config | ConvertTo-Json
{
  "App": {
    "Database": "@{Server=sql01; Port=1433; Name=AppDB}"
  }
}

Setting Higher Depth Values

Pass -Depth with a value that exceeds the deepest nesting in your object. For most configuration objects and API payloads, depth 5–10 is sufficient. Use a higher value (20–50) for deeply nested structures like complex AD group membership trees or recursive data models.

# Correct serialization with explicit depth
$config | ConvertTo-Json -Depth 5
{
  "App": {
    "Database": {
      "Server": "sql01",
      "Port": 1433,
      "Name": "AppDB"
    }
  }
}

Circular Reference Errors

Setting very high depth values on objects that contain circular references (where an object property points back to a parent) causes ConvertTo-Json to recurse until it hits a stack overflow. PowerShell raises “Serialization depth limit exceeded” or runs out of stack space. Identify circular references with Get-Member and break the cycle before serializing.

# Circular reference example — will cause serialization error at high depth
$parent = @{ Name = 'Parent' }
$child  = @{ Name = 'Child'; Parent = $parent }
$parent['Child'] = $child   # circular!

# This will throw or produce incorrect output at high depths
try {
    $parent | ConvertTo-Json -Depth 20 -ErrorAction Stop
} catch {
    Write-Warning "Circular reference detected: $($_.Exception.Message)"
    # Fix: serialize without the back-reference
    $safe = @{ Name = $parent.Name; Child = @{ Name = $child.Name } }
    $safe | ConvertTo-Json -Depth 5
}

Testing Your JSON Output

After serializing, always round-trip test by converting back with ConvertFrom-Json and verifying deep properties survive. This immediately catches truncation issues during development.

$original = @{
    Level1 = @{
        Level2 = @{
            Level3 = @{
                Value = 'deep data'
            }
        }
    }
}

$json   = $original | ConvertTo-Json -Depth 10
$parsed = $json | ConvertFrom-Json

# Verify deep value survived the round-trip
if ($parsed.Level1.Level2.Level3.Value -eq 'deep data') {
    Write-Host 'Round-trip success — depth is sufficient' -ForegroundColor Green
} else {
    Write-Warning 'Data lost during serialization — increase -Depth'
}

Compress JSON with -Compress

The -Compress switch removes all whitespace from the JSON output, producing a single-line string. This reduces payload size for API calls and log entries. Combine with a high -Depth value for complete, compact JSON.

# Compact single-line JSON for API payloads
$payload = @{
    user   = @{ name = 'jsmith'; email = '[email protected]' }
    action = 'create'
} | ConvertTo-Json -Depth 5 -Compress

# $payload is now a single compact string
Write-Host "Payload length: $($payload.Length) chars"
Write-Host $payload

Depth vs Object Size Trade-Off

Higher depth increases memory usage and processing time proportionally with object size. For large objects with moderate nesting (e.g., a list of 10,000 users), depth 5 is usually safe and fast. Avoid using -Depth 100 as a catch-all — it provides no protection against circular references and wastes processing time on objects that are only 3–4 levels deep. Choose a depth value that matches your actual object structure.

# Measure impact of different depth values on a large object
$data = 1..1000 | ForEach-Object { @{ Id = $_; Nested = @{ Value = "item$_" } } }

$t2  = (Measure-Command { $data | ConvertTo-Json -Depth 2  }).TotalMilliseconds
$t10 = (Measure-Command { $data | ConvertTo-Json -Depth 10 }).TotalMilliseconds

Write-Host "Depth 2:  $([math]::Round($t2,1)) ms"
Write-Host "Depth 10: $([math]::Round($t10,1)) ms"

Common Errors and Fixes

  • Objects beyond depth appear as @{} instead of their values. The truncated representation "@{Key=Value}" is a PowerShell string representation of a hashtable, not valid JSON. When you see this in your JSON output, increase -Depth until the nested objects serialize correctly.
  • Very deep depth on large objects causes memory issues. A list of complex objects serialized with -Depth 100 may consume gigabytes of memory. Profile with Measure-Command and a realistic data sample. Use the minimum depth that covers all your data rather than an arbitrary large number.

Related Cmdlets / See Also

Wrapping Up

-Depth 2 is a silent data-loss trap for anyone working with nested objects. Set an explicit depth that matches your object structure, round-trip test with ConvertFrom-Json during development, and use -Compress for API payloads. Avoid very high depth values on large datasets to prevent memory pressure.

Send-Item -To