JSON and REST-Friendly Objects
Work with nested JSON safely for REST APIs, CI payloads, and configuration while understanding depth, arrays, dates, and round-trip limits.
Learning objectives
- Parse nested JSON into PowerShell objects and serialize objects back to JSON.
- Choose serialization depth deliberately.
- Preserve singleton-array shape when required.
- Handle JSON key edge cases with ordered hashtables.
- Reason about timestamp conversion and PowerShell version behavior.
- Validate an API-style payload before use.
1. JSON represents nested values, objects, and arrays
JSON (JavaScript Object Notation) is a text format for structured data. Unlike CSV, JSON naturally represents nested objects and arrays. That makes it common for REST APIs, CI metadata, cloud configuration, and application settings.
The important engineering habit is to convert JSON into PowerShell objects early, manipulate objects, then serialize only at the boundary.
$json = @'
{
"service": "api",
"enabled": true,
"replicas": 3,
"owners": ["platform", "security"],
"limits": { "cpu": 2, "memoryMb": 1024 },
"maintenance": null
}
'@
$config = $json | ConvertFrom-Json
$config.service
$config.limits.memoryMb
$config.owners.Count2. JSON values map to PowerShell-friendly values—but not perfect original types
JSON has strings, numbers, booleans, null, arrays, and objects. PowerShell reconstructs suitable values, but JSON does not carry arbitrary .NET type identity or methods. A serialized object is data, not a live process handle, stream, or class instance.
$config.enabled.GetType().FullName
$config.replicas.GetType().FullName
$null -eq $config.maintenance3. ConvertTo-Json and depth
ConvertTo-Json serializes objects. Its -Depth controls how many nested levels are included; the default is intentionally shallow for general .NET objects. Current PowerShell warns when input exceeds the requested depth. For a known configuration schema, choose a depth that comfortably covers the structure instead of blindly using an extreme value.
$payload = [pscustomobject]@{
name = 'api'
deployment = [pscustomobject]@{
strategy = 'rolling'
limits = [pscustomobject]@{ cpu = 2; memoryMb = 1024 }
}
}
$payload | ConvertTo-Json -Depth 54. ConvertFrom-Json, objects, and hashtables
By default, ConvertFrom-Json returns a PSCustomObject for JSON objects. -AsHashtable is useful when keys are awkward as PowerShell property names or differ only by case. In current PowerShell, it creates an ordered hashtable, preserving input key order.
$raw = '{ "Name":"api", "name":"shadow" }'
$map = $raw | ConvertFrom-Json -AsHashtable
$map['Name']
$map['name']5. Singleton arrays and -NoEnumerate
PowerShell pipelines normally enumerate collections. A one-element JSON array can therefore collapse to a scalar during a round trip. ConvertFrom-Json -NoEnumerate preserves the array as one output object when exact array shape matters.
('[1]' | ConvertFrom-Json | ConvertTo-Json -Compress)
('[1]' | ConvertFrom-Json -NoEnumerate | ConvertTo-Json -Compress)6. Dates are a schema decision, not a formatting accident
JSON itself has no DateTime type; timestamps are strings by convention. Current PowerShell can parse timestamp-shaped strings, and PowerShell 7.5 added -DateKind so callers can choose Local, Utc, Offset, String, or default behavior. For APIs where preserving the original offset matters, DateKind Offset or String can be safer than an implicit local conversion.
$raw = '{ "generatedAt": "2026-08-11T18:30:00+03:30" }'
$parsed = $raw | ConvertFrom-Json -DateKind Offset
$parsed.generatedAt.GetType().FullName
$parsed.generatedAt.Offset8. Readable JSON versus compact machine output
Humans usually prefer indented JSON in source control. Compact JSON is useful for environment variables, single-line CI outputs, or HTTP payloads where whitespace has no semantic value. -Compress removes unnecessary whitespace; it does not change the data model.
$payload | ConvertTo-Json -Depth 5
$payload | ConvertTo-Json -Depth 5 -Compress9. PowerShell can perform lightweight schema-like checks
A full JSON Schema validator is a separate dependency. For small local automation, explicit checks can still defend the boundary: required properties, allowed values, types, and ranges. Keep these checks near deserialization so invalid configuration does not travel deep into the script.
function Test-AppConfig {
param([Parameter(Mandatory)]$Config)
if ([string]::IsNullOrWhiteSpace($Config.service)) { throw 'service is required' }
if ($Config.replicas -notis [int] -or $Config.replicas -lt 1 -or $Config.replicas -gt 50) {
throw 'replicas must be an integer from 1 through 50'
}
if ($Config.environment -notin 'dev','staging','prod') { throw 'invalid environment' }
$true
}10. Lab: build and validate an API-style payload
$raw = @'
{
"service": "orders",
"environment": "staging",
"replicas": 3,
"features": ["metrics", "tracing"],
"limits": { "cpu": 2, "memoryMb": 1024 }
}
'@
$config = $raw | ConvertFrom-Json
if ($config.environment -notin 'dev','staging','prod') { throw 'Invalid environment' }
if ($config.replicas -notis [int] -or $config.replicas -lt 1) { throw 'Invalid replicas' }
if ($config.limits.memoryMb -lt 256) { throw 'memoryMb too small' }
$payload = [pscustomobject]@{
service = $config.service
environment = $config.environment
replicas = $config.replicas
features = @($config.features)
limits = $config.limits
requestedBy = 'chapter11-lab'
}
$payload | ConvertTo-Json -Depth 5
$payload | ConvertTo-Json -Depth 5 -CompressExpected observations
- The readable and
-CompressJSON outputs represent the same data with different whitespace. featuresremains an array with two elements, andreplicasremains numeric in JSON.- Validation completes before serialization; changing the environment to an unsupported value should make the lab fail clearly.
11. Verification checklist
- I can explain
-Depth,-NoEnumerate,-AsHashtable, and-DateKindat a beginner level. - I can distinguish readable JSON from compact JSON without treating whitespace as data.
- I can reject invalid configuration before creating the final payload.
- No network service or credentials were required.
12. Common mistakes
- Using the default JSON depth without checking a nested object's shape.
- Assuming JSON round trips arbitrary .NET methods and exact types.
- Relying on one-element arrays to remain arrays without considering enumeration.
- Assuming comments accepted by PowerShell are accepted by every JSON consumer.
- Parsing timestamps without deciding whether local time, UTC, original offset, or raw string is the real contract.
13. Knowledge check
Question 1. Why is JSON usually a better fit than CSV for nested configuration?
Question 2. What problem does ConvertTo-Json -Depth address?
Question 3. When is -AsHashtable useful?
Question 4. Why can -NoEnumerate matter?
Question 5. Are comments accepted by current PowerShell guaranteed to be valid for every JSON consumer?
14. Summary and next bridge
JSON is the natural bridge between PowerShell objects, APIs, and nested configuration. Make depth, array shape, key handling, dates, and validation explicit. Next, XML shows a more verbose but still important enterprise format where elements, attributes, and namespaces matter.
15. Authoritative references
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.
7. Comments are accepted by current PowerShell, but they are not portable JSON
Strict JSON does not define comments. Windows PowerShell 5.1 rejects JSON comments, while PowerShell 6+ accepts them and discards the comments during conversion. Treat comments as a PowerShell parser convenience, not as a guarantee that every API or JSON tool will accept the document.