3

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?

0

2 Answers 2

4

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

Sign up to request clarification or add additional context in comments.

Comments

3

I suspect that PowerShell is not resolving the correct JObject constructor for some reason, though I do not know why. I played with your example for a while and was unable to coax PowerShell to do the right thing.

I would suggest rewriting your script so that you are creating your JObjects with the empty constructor, and then using the Add method to add properties to them. I was able to get your desired output in this way:

$emptyVal = New-Object JValue ""
$innerObject = New-Object JObject
$innerObject.Add("PropA1", $emptyVal)
$myJObject = New-Object JObject
$myJObject.Add("PropA", $innerObject)

Write-Host $myJObject.ToString()

1 Comment

Thanks for trying it. I'm glad you saw the same thing I did because there are times I think I'm going crazy! Another work around I had found was to add both so it would get the correct OverLoad, and then go back and delete the second property so the end result is what I wanted.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.