ConvertTo-Json defaults to a depth of 2. Deeply nested objects (common in Azure API responses) are truncated at depth 2 with "@{...}" text. Always specify depth explicitly: ConvertTo-Json -Depth 10 for complex objects. Truncated JSON is one of the most common silent data-loss bugs when serializing Azure resource objects for storage or transmission.
Working with Objects and JSON
Create custom objects with [PSCustomObject], shape pipeline output with Select-Object computed properties, convert data to and from JSON, and call REST APIs with Invoke-RestMethod to integrate PowerShell with Azure and third-party services.
🧒 Simple Explanation (ELI5)
A PowerShell object is like a form with labeled fields: Name, Status, CPU. Some forms come pre-filled (Get-Process), and some you create from scratch and fill yourself. JSON is how computers share those forms online—it is a structured text format that websites and APIs use to send and receive data. Once you know how to create objects and parse JSON in PowerShell, you can talk to any API and shape the results any way you need.
🔧 Why Do We Need It?
- Structured output: returning [PSCustomObject] from functions makes your script output pipeable, filterable, and exportable to CSV or JSON.
- API integration: Azure REST APIs, Slack webhooks, GitHub Actions, and Kubernetes API all communicate in JSON—PowerShell's ConvertTo-Json and ConvertFrom-Json make integration straightforward.
- Configuration files: reading appsettings.json or pipeline config files and using their values inside scripts reduces hardcoded constants.
- Data transformation: combining data from multiple sources into one unified object for reporting or further processing.
⚙️ Technical Explanation
The [PSCustomObject] accelerator creates lightweight custom objects from a hashtable. Properties are immediately accessible and the object flows through the pipeline like any built-in type. Use Add-Member to add properties after creation, though inline creation is preferred.
Select-Object with calculated properties (hashtable with n=name and e=expression) lets you transform, rename, or compute new properties inline in the pipeline without creating a separate step.
ConvertTo-Json serializes any .NET object or hashtable to a JSON string. ConvertFrom-Json deserializes a JSON string to a PowerShell object with dot-notation access. Invoke-RestMethod calls a REST API and automatically deserializes JSON responses.
Use Invoke-RestMethod when the API returns JSON—it automatically deserializes the response so you get an object with dot-notation access. Use Invoke-WebRequest when you need the raw HTTP response (headers, status code, binary content). For 99% of API integration work, Invoke-RestMethod is the right choice.
📊 Visual Representation
⌨️ Commands / Syntax
# Create a custom object
$server = [PSCustomObject]@{
Name = "web01"
Environment = "prod"
Status = "Healthy"
CPU = 12.5
MemoryGB = 3.8
}
$server.Name # "web01"
# Build a list of custom objects
$report = foreach ($svc in Get-Service) {
[PSCustomObject]@{
Name = $svc.Name
Status = $svc.Status
IsRunning = $svc.Status -eq 'Running'
}
}
$report | Where-Object IsRunning -eq $false
# Calculated property in Select-Object
Get-Process |
Select-Object Name, Id,
@{n="MemoryMB"; e={[math]::Round($_.WorkingSet64/1MB, 1)}},
@{n="CPURounded"; e={[math]::Round($_.CPU, 2)}} |
Sort-Object MemoryMB -Descending |
Select-Object -First 10
# Convert to JSON
$config = @{
environment = "prod"
replicas = 3
tags = @{ team = "platform"; cost = "infra" }
}
$json = $config | ConvertTo-Json -Depth 5
Write-Output $json
# Parse JSON from a file
$appConfig = Get-Content "appsettings.json" -Raw | ConvertFrom-Json
Write-Host "DB connection: $($appConfig.ConnectionStrings.Default)"
# Call a REST API
$response = Invoke-RestMethod -Uri "https://api.github.com/repos/microsoft/powershell" `
-Headers @{ "User-Agent" = "PowerShell" }
Write-Host "Stars: $($response.stargazers_count)"
# Call Azure REST API with authentication header
$token = (Get-AzAccessToken).Token
$headers = @{
Authorization = "Bearer $token"
"Content-Type" = "application/json"
}
$apps = Invoke-RestMethod `
-Uri "https://management.azure.com/subscriptions/$subId/providers/Microsoft.Web/sites?api-version=2022-03-01" `
-Headers $headers
$apps.value | Select-Object name, location, @{n="state";e={$_.properties.state}}
💼 Example (Real-world Use Case)
An on-call engineer needs a dashboard of all Azure App Services across subscriptions showing name, resource group, state, and last modified. Using Invoke-RestMethod against the Azure management API and ConvertFrom-Json, they build a custom object per service, add an IsHealthy boolean, and pipe to Export-Csv to share with the team. The whole script is 30 lines and runs in one minute against 200 services.
🧪 Hands-on
- Create a [PSCustomObject] for a server with Name, IP, and Status properties.
- Use Select-Object with a calculated property to convert a process's WorkingSet64 from bytes to megabytes.
- Convert a hashtable to JSON with ConvertTo-Json -Depth 5 and inspect the output.
- Fetch a public API response:
Invoke-RestMethod "https://httpbin.org/json"and access a nested property. - Read a local JSON config file with Get-Content -Raw | ConvertFrom-Json and access one property.
Call the GitHub API to get the latest release of PowerShell: Invoke-RestMethod "https://api.github.com/repos/PowerShell/PowerShell/releases/latest" -Headers @{"User-Agent"="PS"}. From the response, extract: tag_name, published_at, and the download URL of the .msi asset. Build a [PSCustomObject] with those three properties and output it as JSON with ConvertTo-Json.
🐛 Debugging Scenario
Problem: you convert an Azure resource object to JSON using ConvertTo-Json, save it to a file, then ConvertFrom-Json the file and find nested properties missing or showing as "@{...}" strings.
- Cause: ConvertTo-Json truncated the object at the default depth of 2. Nested objects beyond depth 2 are serialized as their .ToString() representation rather than full JSON.
- Diagnose: check the JSON file for
@{...}fragments—these are .NET object representations, not valid JSON values. - Fix: add
-Depth 10(or higher if needed):$obj | ConvertTo-Json -Depth 10. Always use -Depth when serializing complex API response objects.
🎯 Interview Questions
Beginner
Use [PSCustomObject]@{ ... } with a hashtable of name-value pairs. Example: [PSCustomObject]@{ Name = "web01"; Status = "Running" }. The result is a full PowerShell object with properties accessible via dot notation and pipeable to Export-Csv, ConvertTo-Json, and other cmdlets.
A hashtable with n (name) and e (expression) keys: @{n="MemMB"; e={$_.WorkingSet64/1MB}}. It creates a new property on the output object computed from an expression, letting you reshape data inline without a separate step.
ConvertTo-Json produces JSON—a standard, cross-platform format readable by any language. Export-Clixml produces a PowerShell-specific XML format that preserves .NET types exactly and can be reimported with Import-Clixml. Use ConvertTo-Json for interoperability with APIs and other tools; use Clixml only for PowerShell-to-PowerShell data persistence.
Scenario-based
Add -Depth to ConvertTo-Json: $response | ConvertTo-Json -Depth 10. The default depth of 2 truncates deeper objects. Always check the output for "@{" fragments, which indicate truncation. For Azure management API responses, depth 10 is typically sufficient.
Wrap Invoke-RestMethod in a try/catch, catch [System.Net.WebException] and [System.TimeoutException] specifically, implement a retry loop with exponential backoff (Start-Sleep increases per attempt), set a -TimeoutSec parameter on the call, and after a maximum number of retries, throw a clear error with the endpoint URL and the number of attempts made.
🌐 Real-world Usage
Platform teams use Invoke-RestMethod to call Azure Monitor REST APIs to pull metric data, push alerts to Slack webhooks, and trigger Azure DevOps pipeline runs from maintenance scripts. [PSCustomObject] is the standard way to produce structured, pipeable output from reporting functions. JSON configuration files are read at script startup to avoid hard-coded environment variables in pipeline definitions.
📝 Summary
[PSCustomObject] creates structured objects ideal for pipeline output and export. Calculated properties in Select-Object transform data inline. ConvertTo-Json and ConvertFrom-Json serialize/deserialize data—always specify -Depth when the object is complex. Invoke-RestMethod calls REST APIs and auto-deserializes JSON. Invoke-WebRequest gives you raw HTTP access. These tools together make PowerShell capable of integrating with any API-based service.