dbc's answer contains helpful background information, and makes it clear that calling the NewtonSoft Json.NET library from PowerShell is cumbersome.
Given PowerShell's built-in support for JSON parsing - via the ConvertFrom-Json
and ConvertTo-Json
cmdlets - there is usually no reason to resort to third-party libraries (directly[1]), except in the following cases:
- When performance is paramount.
- When the limitations of PowerShell's JSON parsing must be overcome (lack of support for empty key names and keys that differ in letter case only).
- When you need to work with the Json.NET types and their methods rather than with the method-less "property-bag"
[pscustomobject]
instances ConvertFrom-Json
constructs.
While working with NewtonSoft's Json.NET directly in PowerShell is awkward, it is manageable, if you observe a few rules:
Lack of visible output doesn't necessarily mean that there isn't any output at all:
Due to a bug in PowerShell (as of v7.0.0-preview.4), [JValue]
instances and [JProperty]
instances containing them produce no visible output by default; access their (strongly typed) .Value
property instead (e.g., $nsObj.Array1[0].Value
or $nsProp.Value.Value
(sic))
To output the string representation of a [JObject]
/ [JArray]
/ [JProperty]
/ [JValue]
instance, do not rely on output as-is (e.g, $nsObj
), use explicit stringification with .ToString()
(e.g., $nsObj.ToString()
); while string interpolation (e.g., "$nsObj"
) does generally work, it doesn't with [JValue]
instances, due to the above-mentioned bug.
[JObject]
and [JArray]
objects by default show a list of their elements' instance properties (implied Format-List
applied to the enumeration of the objects); you can use the Format-*
cmdlets to shape output; e.g., $nsObj | Format-Table Path, Type
.
- Due to another bug (which may have the same root cause), as of PowerShell Core 7.0.0-preview.4, default output for
[JObject]
instances is actually broken in cases where the input JSON contains an array (prints error format-default : Target type System.Collections.IEnumerator is not a value type or a non-abstract class. (Parameter 'targetType')
).
To numerically index into a [JObject]
instance, i.e. to access properties by index rather than by name, use the following idiom: @($nsObj)[<n>]
, where <n>
is the numerical index of interest.
$nsObj[<n>]
actually should work, because, unlike C#, PowerShell exposes members implemented via interfaces as directly callable type members, so the numeric indexer that JObject
implements via the IList<JToken>
interface should be accessible, but isn't, presumably due to this bug (as of PowerShell Core 7.0.0-preview.4).
The workaround based on @(...)
, PowerShell's array-subexpression operator, forces enumeration of a [JObject]
instance to yield an array of its [JProperty]
members, which can then be accessed by index; note that this approach is simple, but not efficient, because enumeration and construction of an aux. array occurs; however, given that a single JSON object (as opposed to an array) typically doesn't have large numbers of properties, this is unlikely to matter in practice.
A reflection-based solution that accesses the IList<JToken>
interface's numeric indexer is possible, but may even be slower.
Note that additional .Value
-based access may again be needed to print the result (or to extract the strongly typed property value).
Generally, do not use the .GetEnumerator()
method; [JObject]
and [JArray]
instances are directly enumerable.
- Keep in mind that PowerShell may automatically enumerate such instances in contexts where you don't expect it, notably in the pipeline; notably, when you send a
[JObject]
to the pipeline, it is its constituent [JProperty]
s that are sent instead, individually.
Use something like @($nsObj.Array1).Value
to extract the values of an array of primitive JSON values (strings, numbers, ...) - i.e, [JValue]
instances - as an array.
The following demonstrates these techniques in context:
$json = @"
{
"Array1": [
"I am string 1 from array1",
"I am string 2 from array1",
],
"Array2": [
{
"Array2Object1Str1": "Object in list, string 1",
"Array2Object1Str2": "Object in list, string 2"
}
]
}
"@
# Deserialize the JSON text into a hierarchy of nested objects.
# Note: You can omit the target type to let Newtonsoft.Json infer a suitable one.
$nsObj = [Newtonsoft.Json.JsonConvert]::DeserializeObject($json)
# Alternatively, you could more simply use:
# $nsObj = [Newtonsoft.Json.Linq.JObject]::Parse($json)
# Access the 1st property *as a whole* by *index* (index 0).
@($nsObj)[0].ToString()
# Ditto, with (the typically used) access by property *name*.
$nsObj.Array1.ToString()
# Access a property *value* by name.
$nsObj.Array1[0].Value
# Get an *array* of the *values* in .Array1.
# Note: This assumes that the array elements are JSON primitives ([JValue] instances.
@($nsObj.Array1).Value
# Access a property value of the object contained in .Array2's first element by name:
$nsObj.Array2[0].Array2Object1Str1.Value
# Enumerate the properties of the object contained in .Array2's first element
# Do NOT use .GetEnumerator() here - enumerate the array *itself*
foreach($o in $nsObj.Array2[0]){
"Path is: $($o.Path)"
"Parent is: $($o.Parent.ToString())"
}
[1] PowerShell Core - but not Windows PowerShell - currently (v7) actually uses NewtonSoft's Json.NET behind the scenes.