PowerShell Working with JSON: ConvertTo-Json and ConvertFrom-Json

REST APIs, configuration files, and inter-service communication all use JSON. PowerShell handles it natively without any external libraries — convert objects to JSON, parse JSON from API responses, read JSON config files, and write them back. This guide covers everything you need to work with PowerShell JSON: ConvertTo-Json, ConvertFrom-Json, depth handling, file I/O, and accessing nested properties.
Quick Answer / TL;DR
# Object to JSON
$obj = [PSCustomObject]@{ Name='web01'; Port=443 }
$obj | ConvertTo-Json
# JSON string to object
'{"Name":"web01","Port":443}' | ConvertFrom-Json
ConvertTo-Json: Objects to JSON
Pipe any PowerShell object to ConvertTo-Json to serialize it:
# Simple object
[PSCustomObject]@{
Server = 'web01.corp.local'
Port = 443
Enabled = $true
} | ConvertTo-Json
# Array of objects
@(
[PSCustomObject]@{ Name='web01'; Status='Running' },
[PSCustomObject]@{ Name='web02'; Status='Stopped' }
) | ConvertTo-Json
# Hashtable
@{ Timeout=30; Retries=3; Debug=$false } | ConvertTo-Json -Compress
{
"Server": "web01.corp.local",
"Port": 443,
"Enabled": true
}
{"Timeout":30,"Retries":3,"Debug":false}
-Compress produces minified single-line JSON, useful for API payloads and writing to files. Without it, the output is pretty-printed with indentation.
ConvertFrom-Json: Parse JSON String
Pass a JSON string to ConvertFrom-Json to get a PowerShell object:
$jsonString = '{"Server":"db01","Port":5432,"Databases":["AppDB","LogDB"]}'
$obj = $jsonString | ConvertFrom-Json
$obj.Server # db01
$obj.Port # 5432
$obj.Databases # AppDB, LogDB
$obj.Databases[0] # AppDB
# Type of the result
$obj.GetType().Name # PSCustomObject
db01
5432
AppDB
LogDB
AppDB
PSCustomObject
ConvertFrom-Json returns a PSCustomObject, not a hashtable. Properties are accessible with dot notation. JSON arrays become PowerShell arrays.
Controlling Depth with -Depth
The -Depth parameter controls how many levels of nested objects are serialized. The default is 2, which truncates deeply nested data:
$nested = [PSCustomObject]@{
Level1 = [PSCustomObject]@{
Level2 = [PSCustomObject]@{
Level3 = [PSCustomObject]@{
Value = 'Deep data'
}
}
}
}
# Default depth 2 — Level3 gets truncated
$nested | ConvertTo-Json
# Full depth
$nested | ConvertTo-Json -Depth 10
# Default depth=2:
{
"Level1": {
"Level2": "@{Level3=}" <-- truncated to string!
}
}
# With -Depth 10:
{
"Level1": {
"Level2": {
"Level3": {
"Value": "Deep data"
}
}
}
}
Always set -Depth explicitly when working with complex objects. The truncation is silent — you get a string representation of the nested object rather than an error, which is easy to miss.
Read JSON from a File
Combine Get-Content -Raw with ConvertFrom-Json:
# Read a JSON config file
$config = Get-Content 'C:\Config\appsettings.json' -Raw | ConvertFrom-Json
$config.Database.Server
$config.Database.Port
# Read a JSON array from file
$servers = Get-Content 'C:\Config\servers.json' -Raw | ConvertFrom-Json
$servers.Count
$servers | Where-Object { $_.Environment -eq 'Production' }
db01.corp.local
5432
5
The -Raw flag is essential — without it, Get-Content returns an array of strings (one per line) rather than a single JSON string, which causes ConvertFrom-Json to fail.
Write JSON to a File
$config = [PSCustomObject]@{
Database = [PSCustomObject]@{
Server = 'db01.corp.local'
Port = 5432
Database = 'Production'
}
Logging = [PSCustomObject]@{
Level = 'Info'
Path = 'C:\Logs\app.log'
}
}
# Write pretty-printed JSON
$config | ConvertTo-Json -Depth 5 |
Set-Content 'C:\Config\appsettings.json' -Encoding UTF8
# Write minified JSON
$config | ConvertTo-Json -Depth 5 -Compress |
Set-Content 'C:\Config\appsettings.min.json' -Encoding UTF8
Access Nested Properties
Navigate nested JSON structures with chained dot notation:
$apiResponse = '{
"status": "ok",
"data": {
"users": [
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "viewer"}
],
"total": 2
}
}' | ConvertFrom-Json
$apiResponse.status # ok
$apiResponse.data.total # 2
$apiResponse.data.users[0].name # Alice
# Filter nested array
$apiResponse.data.users |
Where-Object { $_.role -eq 'admin' } |
Select-Object name, role
ok
2
Alice
name role
---- ----
Alice admin
Common Errors and Fixes
-
Default -Depth 2 truncates nested objects: If your JSON output shows something like
"@{PropertyName=}"instead of nested JSON, the default depth of 2 was exceeded. Always set-Depth 10(or higher) when working with complex nested objects. -
JSON arrays return PSCustomObject not hashtable:
ConvertFrom-JsonreturnsPSCustomObjectinstances, not hashtables. You can’t use.ContainsKey()— useif ($obj.PSObject.Properties['key'])to check if a property exists on a PSCustomObject.
Related Cmdlets / See Also
Wrapping Up
PowerShell handles JSON natively with ConvertTo-Json and ConvertFrom-Json. Always use -Depth 10 for nested objects, Get-Content -Raw when reading JSON files, and -Encoding UTF8 when writing. Access properties with dot notation and filter arrays with Where-Object. Your next step: read a real API’s JSON response and filter it down to just the fields you need.


