Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with Newtonsoft Json Linq in Powershell vs .NET?

I have the following code in a C# .NET App....

 JObject myJObject = new JObject();
 JArray myJArray = new JArray();

 myJObject.Add(
                (new JProperty("PropA", (new JObject(new JProperty("PropA1", "")))))
              );

 Console.WriteLine(myJObject.ToString());

It works as expected and the output I get is

{ "PropA": { "PropA1": "" } }

However, when I do the exact same code in Power shell (but translated)...

using namespace Newtonsoft.Json.Linq
Add-Type -Path "C:\Temp\Newtonsoft.Json.dll" 

[JObject] $myJObject = New-Object JObject
[JArray]  $myJArray = New-Object JArray

$myJObject.Add(
                (New-Object JProperty("PropA",(New-Object JObject((New-Object JProperty("PropA1",""))))))
              ) 

write-host $myJObject.ToString()       

It blows up and I get the error..

New-Object : Exception calling ".ctor" with "1" argument(s): "Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject."

Interesting enough, if I use that same code but add 2 properties it works...

  $myJObject.Add(
                 (New-Object JProperty("PropA",(New-Object JObject((New-Object JProperty("PropA1","")), (New-Object JProperty("PropA2",""))))))
                 ) 

but of course I get this...

{ "PropA": { "PropA1": "", "PropA2": "" } }

What am I doing wrong?

like image 868
da_jokker Avatar asked Dec 11 '25 07:12

da_jokker


1 Answers

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).

like image 194
mklement0 Avatar answered Dec 14 '25 08:12

mklement0



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!