在Powershell vs .NET中,Newtonsoft Json Linq是否存在问题?

时间:2018-11-08 18:54:00

标签: powershell json.net

我在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());

它按预期工作,我得到的输出是

{   “ PropA”:{     “ PropA1”:“”   } }

但是,当我在Power Shell中执行完全相同的代码(但已翻译)...

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

它炸毁,我得到了错误。

新对象:使用“ 1”参数调用“ .ctor”的异常:“无法将Newtonsoft.Json.Linq.JValue添加到Newtonsoft.Json.Linq.JObject。”

足够有趣,如果我使用相同的代码,但添加2个属性,它将起作用...

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

但是我当然知道了...

{   “ PropA”:{     “ PropA1”:“”,     “ PropA2”:“”   } }

我在做什么错了?

2 个答案:

答案 0 :(得分: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() 

替代,使用调用构造函数的类型上可用的 PSv5 +静态::new()方法

[JObject] $myJObject = New-Object JObject

$myJObject.Add(
  [JProperty]::new("PropA",
    [JObject]::new(
      # No workaround needed.
      [JProperty]::new("PropA1", "")
    )
  )
)

$myJObject.ToString() 

Brian Rogers' answer在正确的轨道上:问题在于,内部的JObject构造函数不会收到JProperty实例作为参数传递 (当使用New-Object cmdlet调用时。

# *Seemingly* the equivalent of: new JObject(new JProperty("PropA1", ""))
New-Object JObject (New-Object JProperty "PropA1", "") # !! FAILS

请注意使用类似于shell的语法-空格分隔的参数,参数列表周围没有括号-因为这就是New-Object这样的cmdlet期望-这些是 not 方法调用;它们是argument mode中解析的PowerShell命令。

JProperty实例包装在 helper数组中可解决此问题

New-Object JObject (, (New-Object JProperty "PropA1", "")) # OK - wrapped in array

,是PowerShell的 array-construction操作符,因此, <expr>创建了一个包含object[]的结果的单元素<expr>数组。 [1]

但是,可以通过使用PSv5 +静态::new()方法进行构建来避免问题::new()适用于所有类型,并允许您使用方法语法

调用构造函数
[JObject]::new([JProperty]::new("PropA1", "")) # OK

关于为什么 标量 JProperty自变量不适用于PowerShell中的New-Object

由于JProperty类型实现了IEnumerable接口,因此PowerShell尝试枚举单个JProperty实例,而不是将其本身传递-ArgumentList参数(本身是object[]类型的参数)时。
之所以失败,是因为JObject构造函数随后会看到该枚举的结果,该枚举是一个表示JSON属性 value JValue实例,并构造了一个JObject实例如错误消息所示,不允许从JValue实例中访问-请参见Brian's answer here

将此类实例包装在数组中可以避免此问题:它可以防止JProperty实例的枚举,并可以安全地将其通过辅助object[]实例内部。

如果您要传递给New-Object JObject的是数组开头,例如您传递了两个 JProperty实例的示例,这个问题也可以避免。

还请注意,强制转换为[JObject]也可以,但只能使用 one 属性:

New-Object JObject ([JObject] (New-Object JProperty "PropA1", "")) # OK with 1 prop.

不是 Powershell具有内置的JSON支持,它可以方便地将哈希表和自定义对象转换为JSON并返回:

# 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}

因此,除非您有特殊要求和/或性能问题,否则PowerShell的内置功能可能就足够了,甚至可以通过JSON将自定义对象/哈希表转换为JObject / JToken实例字符串表示形式,尽管价格不菲:

PS> [JToken]::Parse((@{ PropA = @{ PropA1 = 42 } } | ConvertTo-Json -Compress)).ToString()
{
  "PropA": {
    "PropA1": 42
  }
}

[1]请注意,数组子表达式操作符@(...)在这种情况下不能正常运行,因为它也涉及到不必要的枚举JProperty实例,然后将结果包装在[object[]]数组中:

# !! 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)

奇怪的是,如果您直接将@(...)呼叫放在其中,您可以摆脱New-Object

# !! Happens to work, but is BEST AVOIDED.
# !! Use (, (New-Object JProperty "PropA1", "")) instead.
New-Object JObject @(New-Object JProperty "PropA1", "")

这次幸运事故的高度模糊原因-不应该依赖-使用 command 而不是 expression 会直接导致通常看不见的额外[psobject]包装器,在这种情况下是为了防止对JProperty实例进行枚举;有关一般背景信息,请参见this GitHub issue

答案 1 :(得分:3)

我怀疑PowerShell由于某种原因不能解析正确的JObject构造函数,尽管我不知道为什么。我玩了一段时间您的示例,却无法哄骗PowerShell做正确的事。

我建议重写脚本,以便使用空构造函数创建JObjects,然后使用Add方法向其添加属性。我能够通过这种方式获得所需的输出:

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

Write-Host $myJObject.ToString()