I am currently struggling to convert a JSON-string into an array of objects and to GENERALLY handle the properties/attributes of each object.
Here is a simple demo, that shows that e.g. the attribute "address" seems to be a bit special:
cls
$json = '[{"id":"1","address":"1"},{"id":"2","address":"2"}]'
$list = $json | ConvertFrom-Json
$list.id # OK
$list.address # gives a weired result - is this a bug?
$list.GetEnumerator().address # that works
This is the output:
1
2
OverloadDefinitions
-------------------
System.Object&, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Address(int )
1
2
As you can see, I need to add ".GetEnumerator()" to get the correct "address"-values.
Is this the expected? Should I ALWAYS use the ".GetEnumerator()" to be safe?
Since $list is an array (collection), using something like $list.id performs member-access enumeration; that is, the .id property is automatically accessed on each element, and the resulting values are returned as an array ([object[]])[1].
However, if the array / collection type itself has a member by that name (a property or method), it takes precedence, which is what happened in your case: .NET arrays have an .Address method (that is added by the runtime - see this answer), and that is what preempted accessing the elements' .Address property.
(What you saw was PowerShell's representation of the method's signature (overload), which is what you get when you access a method by name only, without actually calling it by appending ((...)); try 'foo'.ToUpper, fo instance.)
That PowerShell provides no distinct operator syntax to distinguish between direct member access and member-access enumeration is the subject of [GitHub issue #7445](https://github.com/PowerShell/PowerShell/issues/7445), but the existing behavior is unlikely to change.
The most efficient way to work around that problem is to use the PSv4+ .ForEach() array method, which always targets the collection's elements:
$list = '[{"id":"1","address":"1"},{"id":"2","address":"2"}]' | ConvertFrom-Json
$list.ForEach('address')
Caveat: If you're running Windows PowerShell and $list can situationally just result in a single pscustomobject rather than an array, you must enclose $list in #(...), the array-subexpression operator, to ensure that the .ForEach() method is available. This is a [pscustomobject]-specific bug (given that even single objects should consistently have a .ForEach() method), which has been fixed in PowerShell (Core) 6+.
# #(...) is necessary in Windows PowerShell only.
#($list).ForEach('address')
Note:
Technically, this returns a [System.Collections.ObjectModel.Collection[PSObject]] collection, but in most cases you can use it like a regular [object[]] PowerShell array.
Unlike with member-access enumeration, a collection instance is also returned if there's only one element in the input collection (rather than returning that one element's property value as-is, the way member-access enumeration does).
In PSv3- you can use the ForEach-Object cmdlet, or Select-Object -ExpandProperty:
$list | ForEach-Object address # PSv2: | ForEach-Object { $_.address }
# OR
$list | Select-Object -ExpandProperty address
[1] If there's only one element in the collection, its property value is returned as-is, not wrapped in a (single-element) array, which is the same logic that is applied when collecting pipeline output in a variable. That this logic may be unexpected in the context of member-access enumeration, which is an expression context, is being discussed in GitHub issue #6802.
Related
Why do I get unexpected ConvertTo-Json results, why do I get values like System.Collections.Hashtable and/or why does a round-trip ($Json | ConvertFrom-Json | ConvertTo-Json) fail?
Meta issue
Stackoverflow has a good mechanism to prevent duplicate questions but as far as I can see there is no mechanism to prevent questions that have a duplicate cause. Take this question as a an example: almost every week a new question comes in with the same cause, yet it is often difficult to define it as a duplicate because the question itself is just a slightly different.
Nevertheless, I wouldn't be surprised if this question/answer itself ends up as a duplicate (or off-topic) but unfortunately stackoverflow has no possibility to write an article to prevent other programmers from continuing writing questions caused by this “known” pitfall.
Duplicates
A few examples of similar questions with the same common cause:
PowerShell ConvertTo-Json does not convert Array as expected
(yesterday)
Powershell ConvertTo-json with embedded hashtable
powershell “ConvertTo-Json” has messed json format output
Nested arrays and ConvertTo-Json
Powershell ConvertTo-JSON missing nested level
How to save a JSON object to a file using Powershell?
Cannot convert PSCustomObjects within array back to JSON correctly
ConvertTo-Json flattens arrays over 3 levels deep
Add an array of objects to a PSObject at once
Why does ConvertTo-Json drop values
How to round-trip this JSON to PSObject and back in Powershell
…
Different
So, were does this “self-answered” question differ from the above duplicates?
It has the common cause in the title and with that it might better prevent repeating questions due to the same cause.
Answer
ConvertTo-Json has a -Depth parameter:
Specifies how many levels of contained objects are included in the
JSON representation.
The default value is 2.
Example
To do a full round-trip with a JSON file you need to increase the -Depth for the ConvertTo-Json cmdlet:
$Json | ConvertFrom-Json | ConvertTo-Json -Depth 9
TL;DR
Probably because ConvertTo-Json terminates branches that are deeper than the default -Depth (2) with a (.Net) full type name, programmers assume a bug or a cmdlet limitation and do not read the help or about.
Personally, I think a string with a simple ellipsis (three dots: …) at the end of the cut off branch, would have a clearer meaning (see also: Github issue: 8381)
Why?
This issue often ends up in another discussion as well: Why is the depth limited at all?
Some objects have circular references, meaning that a child object could refer to a parent (or one of its grandparents) causing a infinitive loop if it would be serialized to JSON.
Take for example the following hash table with a parent property that refers to the object itself:
$Test = #{Guid = New-Guid}
$Test.Parent = $Test
If you execute: $Test | ConvertTo-Json it will conveniently stop at a depth level of 2 by default:
{
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": {
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": {
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": "System.Collections.Hashtable"
}
}
}
This is why it is not a good idea to automatically set the -Depth to a large amount.
Update: PowerShell 7.1 introduced a warning when truncation occurs. While that is better than the previous quiet truncation, the solution suggested below seems much preferable to me.
Your helpful question and answer clearly illustrate how much of a pain point the current default ConvertTo-Json behavior is.
As for the justification of the behavior:
While -Depth can be useful to intentionally truncate an input object tree whose full depth you don't need, -Depth defaulting to 2 and quietly truncating the output amounts to quiet de-facto failure of the serialization from the unsuspecting user's perspective - failure that may not be discovered until later.
The seemingly arbitrary and quiet truncation is surprising to most users, and having to account for it in every ConvertTo-Json call is an unnecessary burden.
I've created GitHub issue #8393 containing a proposal to change the current behavior, specifically as follows:
Ignore -Depth for [pscustomobject] object graphs (a hierarchy of what are conceptually DTOs (data-transfer objects, "property bags"), such as returned from Convert*From*-Json), specifically.
By contrast, it does make sense to have an automatic depth limit for arbitrary .NET types, as they can be object graphs of excessive depths and may even contain circular references; e.g., Get-ChildItem | ConvertTo-Json can get quickly out of hand, with -Depth values as low as 4. That said, it is generally ill-advised to use arbitrary .NET types with JSON serialization: JSON is not designed to be a general-purpose serialization format for a given platform's types; instead, it is focused on DTOs, comprising properties only, with a limited set set of data types.
Note that nested collections, including hashtables, are not themselves subject to the depth limit only their (scalar) elements.
This distinction between DTOs and other types is, in fact, employed by PowerShell itself behind the scenes, namely in the context of serialization for remoting and background jobs.
Use of -Depth is then only needed to intentionally truncate the input object tree at the specified depth (or, mostly hypothetically, in order to serialize to a deeper level than the internal maximum-depth limit, 100).
Why do I get unexpected ConvertTo-Json results, why do I get values like System.Collections.Hashtable and/or why does a round-trip ($Json | ConvertFrom-Json | ConvertTo-Json) fail?
Meta issue
Stackoverflow has a good mechanism to prevent duplicate questions but as far as I can see there is no mechanism to prevent questions that have a duplicate cause. Take this question as a an example: almost every week a new question comes in with the same cause, yet it is often difficult to define it as a duplicate because the question itself is just a slightly different.
Nevertheless, I wouldn't be surprised if this question/answer itself ends up as a duplicate (or off-topic) but unfortunately stackoverflow has no possibility to write an article to prevent other programmers from continuing writing questions caused by this “known” pitfall.
Duplicates
A few examples of similar questions with the same common cause:
PowerShell ConvertTo-Json does not convert Array as expected
(yesterday)
Powershell ConvertTo-json with embedded hashtable
powershell “ConvertTo-Json” has messed json format output
Nested arrays and ConvertTo-Json
Powershell ConvertTo-JSON missing nested level
How to save a JSON object to a file using Powershell?
Cannot convert PSCustomObjects within array back to JSON correctly
ConvertTo-Json flattens arrays over 3 levels deep
Add an array of objects to a PSObject at once
Why does ConvertTo-Json drop values
How to round-trip this JSON to PSObject and back in Powershell
…
Different
So, were does this “self-answered” question differ from the above duplicates?
It has the common cause in the title and with that it might better prevent repeating questions due to the same cause.
Answer
ConvertTo-Json has a -Depth parameter:
Specifies how many levels of contained objects are included in the
JSON representation.
The default value is 2.
Example
To do a full round-trip with a JSON file you need to increase the -Depth for the ConvertTo-Json cmdlet:
$Json | ConvertFrom-Json | ConvertTo-Json -Depth 9
TL;DR
Probably because ConvertTo-Json terminates branches that are deeper than the default -Depth (2) with a (.Net) full type name, programmers assume a bug or a cmdlet limitation and do not read the help or about.
Personally, I think a string with a simple ellipsis (three dots: …) at the end of the cut off branch, would have a clearer meaning (see also: Github issue: 8381)
Why?
This issue often ends up in another discussion as well: Why is the depth limited at all?
Some objects have circular references, meaning that a child object could refer to a parent (or one of its grandparents) causing a infinitive loop if it would be serialized to JSON.
Take for example the following hash table with a parent property that refers to the object itself:
$Test = #{Guid = New-Guid}
$Test.Parent = $Test
If you execute: $Test | ConvertTo-Json it will conveniently stop at a depth level of 2 by default:
{
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": {
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": {
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": "System.Collections.Hashtable"
}
}
}
This is why it is not a good idea to automatically set the -Depth to a large amount.
Update: PowerShell 7.1 introduced a warning when truncation occurs. While that is better than the previous quiet truncation, the solution suggested below seems much preferable to me.
Your helpful question and answer clearly illustrate how much of a pain point the current default ConvertTo-Json behavior is.
As for the justification of the behavior:
While -Depth can be useful to intentionally truncate an input object tree whose full depth you don't need, -Depth defaulting to 2 and quietly truncating the output amounts to quiet de-facto failure of the serialization from the unsuspecting user's perspective - failure that may not be discovered until later.
The seemingly arbitrary and quiet truncation is surprising to most users, and having to account for it in every ConvertTo-Json call is an unnecessary burden.
I've created GitHub issue #8393 containing a proposal to change the current behavior, specifically as follows:
Ignore -Depth for [pscustomobject] object graphs (a hierarchy of what are conceptually DTOs (data-transfer objects, "property bags"), such as returned from Convert*From*-Json), specifically.
By contrast, it does make sense to have an automatic depth limit for arbitrary .NET types, as they can be object graphs of excessive depths and may even contain circular references; e.g., Get-ChildItem | ConvertTo-Json can get quickly out of hand, with -Depth values as low as 4. That said, it is generally ill-advised to use arbitrary .NET types with JSON serialization: JSON is not designed to be a general-purpose serialization format for a given platform's types; instead, it is focused on DTOs, comprising properties only, with a limited set set of data types.
Note that nested collections, including hashtables, are not themselves subject to the depth limit only their (scalar) elements.
This distinction between DTOs and other types is, in fact, employed by PowerShell itself behind the scenes, namely in the context of serialization for remoting and background jobs.
Use of -Depth is then only needed to intentionally truncate the input object tree at the specified depth (or, mostly hypothetically, in order to serialize to a deeper level than the internal maximum-depth limit, 100).
Why do I get unexpected ConvertTo-Json results, why do I get values like System.Collections.Hashtable and/or why does a round-trip ($Json | ConvertFrom-Json | ConvertTo-Json) fail?
Meta issue
Stackoverflow has a good mechanism to prevent duplicate questions but as far as I can see there is no mechanism to prevent questions that have a duplicate cause. Take this question as a an example: almost every week a new question comes in with the same cause, yet it is often difficult to define it as a duplicate because the question itself is just a slightly different.
Nevertheless, I wouldn't be surprised if this question/answer itself ends up as a duplicate (or off-topic) but unfortunately stackoverflow has no possibility to write an article to prevent other programmers from continuing writing questions caused by this “known” pitfall.
Duplicates
A few examples of similar questions with the same common cause:
PowerShell ConvertTo-Json does not convert Array as expected
(yesterday)
Powershell ConvertTo-json with embedded hashtable
powershell “ConvertTo-Json” has messed json format output
Nested arrays and ConvertTo-Json
Powershell ConvertTo-JSON missing nested level
How to save a JSON object to a file using Powershell?
Cannot convert PSCustomObjects within array back to JSON correctly
ConvertTo-Json flattens arrays over 3 levels deep
Add an array of objects to a PSObject at once
Why does ConvertTo-Json drop values
How to round-trip this JSON to PSObject and back in Powershell
…
Different
So, were does this “self-answered” question differ from the above duplicates?
It has the common cause in the title and with that it might better prevent repeating questions due to the same cause.
Answer
ConvertTo-Json has a -Depth parameter:
Specifies how many levels of contained objects are included in the
JSON representation.
The default value is 2.
Example
To do a full round-trip with a JSON file you need to increase the -Depth for the ConvertTo-Json cmdlet:
$Json | ConvertFrom-Json | ConvertTo-Json -Depth 9
TL;DR
Probably because ConvertTo-Json terminates branches that are deeper than the default -Depth (2) with a (.Net) full type name, programmers assume a bug or a cmdlet limitation and do not read the help or about.
Personally, I think a string with a simple ellipsis (three dots: …) at the end of the cut off branch, would have a clearer meaning (see also: Github issue: 8381)
Why?
This issue often ends up in another discussion as well: Why is the depth limited at all?
Some objects have circular references, meaning that a child object could refer to a parent (or one of its grandparents) causing a infinitive loop if it would be serialized to JSON.
Take for example the following hash table with a parent property that refers to the object itself:
$Test = #{Guid = New-Guid}
$Test.Parent = $Test
If you execute: $Test | ConvertTo-Json it will conveniently stop at a depth level of 2 by default:
{
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": {
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": {
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": "System.Collections.Hashtable"
}
}
}
This is why it is not a good idea to automatically set the -Depth to a large amount.
Update: PowerShell 7.1 introduced a warning when truncation occurs. While that is better than the previous quiet truncation, the solution suggested below seems much preferable to me.
Your helpful question and answer clearly illustrate how much of a pain point the current default ConvertTo-Json behavior is.
As for the justification of the behavior:
While -Depth can be useful to intentionally truncate an input object tree whose full depth you don't need, -Depth defaulting to 2 and quietly truncating the output amounts to quiet de-facto failure of the serialization from the unsuspecting user's perspective - failure that may not be discovered until later.
The seemingly arbitrary and quiet truncation is surprising to most users, and having to account for it in every ConvertTo-Json call is an unnecessary burden.
I've created GitHub issue #8393 containing a proposal to change the current behavior, specifically as follows:
Ignore -Depth for [pscustomobject] object graphs (a hierarchy of what are conceptually DTOs (data-transfer objects, "property bags"), such as returned from Convert*From*-Json), specifically.
By contrast, it does make sense to have an automatic depth limit for arbitrary .NET types, as they can be object graphs of excessive depths and may even contain circular references; e.g., Get-ChildItem | ConvertTo-Json can get quickly out of hand, with -Depth values as low as 4. That said, it is generally ill-advised to use arbitrary .NET types with JSON serialization: JSON is not designed to be a general-purpose serialization format for a given platform's types; instead, it is focused on DTOs, comprising properties only, with a limited set set of data types.
Note that nested collections, including hashtables, are not themselves subject to the depth limit only their (scalar) elements.
This distinction between DTOs and other types is, in fact, employed by PowerShell itself behind the scenes, namely in the context of serialization for remoting and background jobs.
Use of -Depth is then only needed to intentionally truncate the input object tree at the specified depth (or, mostly hypothetically, in order to serialize to a deeper level than the internal maximum-depth limit, 100).
Why do I get unexpected ConvertTo-Json results, why do I get values like System.Collections.Hashtable and/or why does a round-trip ($Json | ConvertFrom-Json | ConvertTo-Json) fail?
Meta issue
Stackoverflow has a good mechanism to prevent duplicate questions but as far as I can see there is no mechanism to prevent questions that have a duplicate cause. Take this question as a an example: almost every week a new question comes in with the same cause, yet it is often difficult to define it as a duplicate because the question itself is just a slightly different.
Nevertheless, I wouldn't be surprised if this question/answer itself ends up as a duplicate (or off-topic) but unfortunately stackoverflow has no possibility to write an article to prevent other programmers from continuing writing questions caused by this “known” pitfall.
Duplicates
A few examples of similar questions with the same common cause:
PowerShell ConvertTo-Json does not convert Array as expected
(yesterday)
Powershell ConvertTo-json with embedded hashtable
powershell “ConvertTo-Json” has messed json format output
Nested arrays and ConvertTo-Json
Powershell ConvertTo-JSON missing nested level
How to save a JSON object to a file using Powershell?
Cannot convert PSCustomObjects within array back to JSON correctly
ConvertTo-Json flattens arrays over 3 levels deep
Add an array of objects to a PSObject at once
Why does ConvertTo-Json drop values
How to round-trip this JSON to PSObject and back in Powershell
…
Different
So, were does this “self-answered” question differ from the above duplicates?
It has the common cause in the title and with that it might better prevent repeating questions due to the same cause.
Answer
ConvertTo-Json has a -Depth parameter:
Specifies how many levels of contained objects are included in the
JSON representation.
The default value is 2.
Example
To do a full round-trip with a JSON file you need to increase the -Depth for the ConvertTo-Json cmdlet:
$Json | ConvertFrom-Json | ConvertTo-Json -Depth 9
TL;DR
Probably because ConvertTo-Json terminates branches that are deeper than the default -Depth (2) with a (.Net) full type name, programmers assume a bug or a cmdlet limitation and do not read the help or about.
Personally, I think a string with a simple ellipsis (three dots: …) at the end of the cut off branch, would have a clearer meaning (see also: Github issue: 8381)
Why?
This issue often ends up in another discussion as well: Why is the depth limited at all?
Some objects have circular references, meaning that a child object could refer to a parent (or one of its grandparents) causing a infinitive loop if it would be serialized to JSON.
Take for example the following hash table with a parent property that refers to the object itself:
$Test = #{Guid = New-Guid}
$Test.Parent = $Test
If you execute: $Test | ConvertTo-Json it will conveniently stop at a depth level of 2 by default:
{
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": {
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": {
"Guid": "a274d017-5188-4d91-b960-023c06159dcc",
"Parent": "System.Collections.Hashtable"
}
}
}
This is why it is not a good idea to automatically set the -Depth to a large amount.
Update: PowerShell 7.1 introduced a warning when truncation occurs. While that is better than the previous quiet truncation, the solution suggested below seems much preferable to me.
Your helpful question and answer clearly illustrate how much of a pain point the current default ConvertTo-Json behavior is.
As for the justification of the behavior:
While -Depth can be useful to intentionally truncate an input object tree whose full depth you don't need, -Depth defaulting to 2 and quietly truncating the output amounts to quiet de-facto failure of the serialization from the unsuspecting user's perspective - failure that may not be discovered until later.
The seemingly arbitrary and quiet truncation is surprising to most users, and having to account for it in every ConvertTo-Json call is an unnecessary burden.
I've created GitHub issue #8393 containing a proposal to change the current behavior, specifically as follows:
Ignore -Depth for [pscustomobject] object graphs (a hierarchy of what are conceptually DTOs (data-transfer objects, "property bags"), such as returned from Convert*From*-Json), specifically.
By contrast, it does make sense to have an automatic depth limit for arbitrary .NET types, as they can be object graphs of excessive depths and may even contain circular references; e.g., Get-ChildItem | ConvertTo-Json can get quickly out of hand, with -Depth values as low as 4. That said, it is generally ill-advised to use arbitrary .NET types with JSON serialization: JSON is not designed to be a general-purpose serialization format for a given platform's types; instead, it is focused on DTOs, comprising properties only, with a limited set set of data types.
Note that nested collections, including hashtables, are not themselves subject to the depth limit only their (scalar) elements.
This distinction between DTOs and other types is, in fact, employed by PowerShell itself behind the scenes, namely in the context of serialization for remoting and background jobs.
Use of -Depth is then only needed to intentionally truncate the input object tree at the specified depth (or, mostly hypothetically, in order to serialize to a deeper level than the internal maximum-depth limit, 100).
I making a foray into the world of JSON parsing and NewtonSoft and I'm confused, to say the least.
Take the below PowerShell script:
$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"
}
]
}
"#
#The newtonSoft way
$nsObj = [Newtonsoft.Json.JsonConvert]::DeserializeObject($json, [Newtonsoft.Json.Linq.JObject])
$nsObj.GetType().fullname #Type = Newtonsoft.Json.Linq.JObject
$nsObj[0] #Returns nothing. Why?
$nsObj.Array1 #Again nothing. Maybe because it contains no key:value pairs?
$nsObj.Array2 #This does return, maybe because has object with kv pairs
$nsObj.Array2[0].Array2Object1Str1 #Returns nothing. Why? but...
$nsObj.Array2[0].Array2Object1Str1.ToString() #Cool. I get the string this way.
$nsObj.Array2[0] #1st object has a Path property of "Array2[0].Array2Object1Str1" Great!
foreach( $o in $nsObj.Array2[0].GetEnumerator() ){
"Path is: $($o.Path)"
"Parent is: $($o.Parent)"
} #??? Why can't I see the Path property like when just output $nsObj.Array2[0] ???
#How can I find out what the root parent (Array2) is for a property? Is property even the right word?
I'd like to be able to find the name of the root parent for any given position. So above, I'd like to know that the item I'm looking at (Array2Object1Str1) belongs to the Array2 root parent.
I think I'm not understanding some fundamentals here. Is it possible to determine the root parent? Also, any help in understanding my comments in the script would be great. Namely why I can't return things like path or parent, but can see it when I debug in VSCode.
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.
You have a few separate questions here:
$nsObj[0] #Returns nothing. Why?
This is because nsObj corresponds to a JSON object, and, as explained in this answer to How to get first key from JObject?, JObject does not directly support accessing properties by integer index (rather than property name).
JObject does, however, implement IList<JToken> explicitly so if you could upcast nsObj to such a list you could access properties by index -- but apparently it's not straightforward in PowerShell to call an explicitly implemented method. As explained in the answers to How can I call explicitly implemented interface method from PowerShell? it's necessary to do this via reflection.
First, define the following function:
Function ChildAt([Newtonsoft.Json.Linq.JContainer]$arg1, [int]$arg2)
{
$property = [System.Collections.Generic.IList[Newtonsoft.Json.Linq.JToken]].GetProperty("Item")
$item = $property.GetValue($nsObj, #([System.Object]$arg2))
return $item
}
And then you can do:
$firstItem = ChildAt $nsObj 0
Try it online here.
#??? Why can't I see the Path property like when just output $nsObj.Array2[0] ???
The problem here is that JObject.GetEnumerator() does not return what you think it does. Your code assumes it returns the JToken children of the object, when in fact it is declared as
public IEnumerator<KeyValuePair<string, JToken>> GetEnumerator()
Since KeyValuePair<string, JToken> doesn't have the properties Path or Parent your output method fails.
JObject does implement interfaces like IList<JToken> and IEnumerable<JToken>, but it does so explicitly, and as mentioned above calling the relevant GetEnumerator() methods would require reflection.
Instead, use the base class method JContainer.Children(). This method works for both JArray and JObject and returns the immediate children in document order:
foreach( $o in $nsObj.Array2[0].Children() ){
"Path is: $($o.Path)"
"Parent is: $($o.Parent)"
}
Try it online here.
$nsObj.Array1 #Again nothing. Maybe because it contains no key:value pairs?
Actually this does return the value of Array1, if I do
$nsObj.Array1.ToString()
the JSON corresponding to the value of Array1 is displayed. The real issue seems to be that PowerShell doesn't know how to automatically print a JArray with JValue contents -- or even a simple, standalone JValue. If I do:
$jvalue = New-Object Newtonsoft.Json.Linq.JValue 'my jvalue value'
'$jvalue' #Nothing output
$jvalue
'$jvalue.ToString()' #my jvalue value
$jvalue.ToString()
Then the output is:
$jvalue
$jvalue.ToString()
my jvalue value
Try it online here and, relatedly, here.
Thus the lesson is: when printing a JToken hierarchy in PowerShell, always use ToString().
As to why printing a JObject produces some output while printing a JArray does not, I can only speculate. JToken implements the interface IDynamicMetaObjectProvider which is also implemented by PSObject; possibly something about the details of how this is implemented for JObject but not JValue or JArray are compatible with PowerShell's information printing code.