tl;dr
using namespace Newtonsoft.Json.Linq
Add-Type -Path "C:\Temp\Newtonsoft.Json.dll"
[JObject] $myJObject = New-Object JObject
$myJObject.Add(
(New-Object JProperty "PropA",
(New-Object JObject (
# Note the , (...) around this New-Object call,
# which wraps the resulting JProperty instance in a
# single-element array.
, (New-Object JProperty "PropA1", "")
)
)
)
)
$myJObject.ToString()
Alternative, using the PSv5+ static ::new() method available on types for calling constructors:
[JObject] $myJObject = New-Object JObject
$myJObject.Add(
[JProperty]::new("PropA",
[JObject]::new(
# No workaround needed.
[JProperty]::new("PropA1", "")
)
)
)
$myJObject.ToString()
Brian Rogers' answer was on the right track: the problem is that the inner JObject constructor doesn't receive the JProperty instance being passed as an argument as such when New-Object cmdlet calls are used.
# *Seemingly* the equivalent of: new JObject(new JProperty("PropA1", ""))
New-Object JObject (New-Object JProperty "PropA1", "") # !! FAILS
Note the use of shell-like syntax - whitespace-separated arguments, no parentheses around the argument list - because that's what a cmdlet such as New-Object expects - these are not method calls; they are PowerShell commands parsed in argument mode.
Wrapping the JProperty instance in a helper array fixes the problem:
New-Object JObject (, (New-Object JProperty "PropA1", "")) # OK - wrapped in array
, is PowerShell's array-construction operator, so that , <expr> creates a single-element object[] array wrapping the result of <expr>.[1]
However, the problem can be avoided to begin with by using the PSv5+ static ::new() method for construction; ::new() is available on all types, and allows you to call constructors using method syntax:
[JObject]::new([JProperty]::new("PropA1", "")) # OK
As for why a scalar JProperty argument doesn't work with New-Object in PowerShell:
Because the JProperty type implements the IEnumerable interface, PowerShell tries to enumerate a single JProperty instance rather than passing it as itself when binding to the (implied) -ArgumentList parameter (which is itself object[]-typed).
This fails, because the JObject constructor then sees the result of that enumeration, which is a JValue instance representing the JSON property value, and constructing a JObject instance from a JValue instance isn't permitted, as reflected in the error message - see Brian's answer here.
Wrapping such an instance in an array bypasses the problem: it prevents the enumeration of the JProperty instance and safely passes it through inside the auxiliary object[] instance.
If what you're passing to New-Object JObject is an array to begin with, such as your example of passing two JProperty instances, the problem is also avoided.
Also note that casting to [JObject] works too, but only with one property:
New-Object JObject ([JObject] (New-Object JProperty "PropA1", "")) # OK with 1 prop.
Not that Powershell's has built-in JSON support, which allows convenient conversion of hashtables and custom objects to JSON and back:
# Convert a nested hashtable to JSON
PS> @{ PropA = @{ PropA1 = 42 } } | ConvertTo-Json -Compress
{"PropA":{"PropA1":42}}
# Convert JSON to a custom object [pscustomobject] with property
# .PropA whose value is another custom object, with property .PropA1
PS> '{"PropA":{"PropA1":42}}' | ConvertFrom-Json
PropA
-----
@{PropA1=42}
So, unless you have special requirements and/or performance matters, PowerShell's built-in features may suffice, and, you can even convert custom objects / hashtables to JObject / JToken instances via a JSON string representation, albeit not cheaply:
PS> [JToken]::Parse((@{ PropA = @{ PropA1 = 42 } } | ConvertTo-Json -Compress)).ToString()
{
"PropA": {
"PropA1": 42
}
}
[1] Note that @(...), the array-subexpression operator, does not work robustly in this case, because it too involves unwanted enumeration of the JProperty instance before wrapping the result in an [object[]] array:
# !! FAILS too:
# The JProperty instance is again enumerated, resulting in a single
# JValue instance, which is what @(...) then wraps in a
# single-element [object[]] array.
$var = New-Object JProperty "PropA1", ""
New-Object JObject @($var)
Curiously, you can get away with @(...) if you directly place the New-Object call inside it:
# !! Happens to work, but is OBSCURE.
# !! Equivalent of: (, (New-Object JProperty "PropA1", ""))
New-Object JObject @(New-Object JProperty "PropA1", "")
That @(...) prevents enumeration in this case is owed to the fact that command output (e.g., @(New-Object ...)) - as opposed to expression output (e.g., @($var)) - is not enumerated by @(...); that is, if the command outputs something that is enumerable / a collection as a whole - which is what New-Object does when instantiating such types - @(...) wraps it as-is in a single-element [object[]] array. (As an aside: $(...) would cause enumeration).