在 PowerShell 中理解 NewtonSoft

Scu*_*Dan 2 powershell json json.net

我涉足 JSON 解析和 NewtonSoft 的世界,至少可以说我很困惑。

使用以下 PowerShell 脚本:

$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?
Run Code Online (Sandbox Code Playgroud)

我希望能够找到任何给定位置的根父级的名称。所以在上面,我想知道我正在查看的项目 (Array2Object1Str1) 属于 Array2 根父级。

我想我不了解这里的一些基本原理。是否可以确定根父级?此外,任何有助于理解我在脚本中的评论的帮助都会很棒。即为什么我不能返回诸如路径或父级之类的东西,但在 VSCode 中调试时可以看到它。

mkl*_*nt0 7

dbc 的回答包含有用的背景信息,并明确说明从 PowerShell 调用 NewtonSoft Json.NET 库很麻烦。

鉴于PowerShell的内置的JSON解析的支持-通过ConvertFrom-JsonConvertTo-Jsoncmdlet的-通常没有理由诉诸第三方库(直接[1] ),以下情况:

  • 性能是最重要的。
  • 当必须克服PowerShell 的 JSON 解析限制时(缺乏对空键名和仅字母大小写不同的键的支持)。
  • 当您需要使用 Json.NET 类型及其方法而不是使用无方法的“属性包”[pscustomobject]实例ConvertFrom-Json构造时。

虽然直接在 PowerShell 中使用 NewtonSoft 的 Json.NET 很尴尬,但它是可以管理的,如果您遵守一些规则

  • 缺乏可见输出并不一定意味着根本没有任何输出

    • 由于PowerShell 中错误(从 v7.0.0-preview.4 开始),[JValue]实例和[JProperty]包含它们的实例默认产生可见输出改为访问他们的(强类型).Value属性(例如,$nsObj.Array1[0].Value$nsProp.Value.Value(原文如此))

    • 输出的字符串表示一个的[JObject]/ [JArray]/ [JProperty]/[JValue]例如,不依赖原样输出(例如$nsObj),使用具有明确的字串.ToString()(例如,$nsObj.ToString()); 虽然字符串插值(例如,"$nsObj"通常可以工作,但[JValue]由于上述错误,它不适用于实例。

    • [JObject][JArray]对象默认显示其元素的实例属性列表(暗示Format-List应用于对象的枚举);您可以使用Format-*cmdlet 来调整输出;例如,$nsObj | Format-Table Path, Type

      • 由于另一个错误(可能具有相同的根本原因),从 PowerShell Core 7.0.0-preview.4 开始,在输入 JSON 包含数组(打印 error )的情况下,[JObject]实例的默认输出实际上已损坏format-default : Target type System.Collections.IEnumerator is not a value type or a non-abstract class. (Parameter 'targetType')
  • 要对[JObject]实例进行数字索引,即索引而不是按名称访问属性,请使用以下习语:@($nsObj)[<n>],其中<n>是感兴趣的数字索引。

    • $nsObj[<n>]实际上应该可以工作,因为与 C# 不同,PowerShell 将通过接口实现的成员公开为可直接调用的类型成员,因此通过接口实现的数字索引器应该可以访问,但不能访问,大概是由于这个错误(从 PowerShell Core 7.0 开始) .0-preview.4)。JObjectIList<JToken>

    • 基于@(...)PowerShell 的数组子表达式运算符的解决方法强制枚举[JObject]实例以生成其[JProperty]成员的数组,然后可以通过索引访问该数组;请注意,这种方法很简单,但效率不高,因为 aux 的枚举和构造。数组发生;然而,鉴于单个 JSON 对象(与数组相反)通常没有大量属性,这在实践中不太可能重要。
      访问IList<JToken>接口的数字索引器的基于反射的解决方案是可能的,但可能更慢。

    • 请注意,.Value可能再次需要额外的基于访问的访问来打印结果(或提取强类型属性)。

  • 一般不使用该.GetEnumerator()方法[JObject][JArray]实例是直接可枚举的

    • 请记住,PowerShell 可能会在您不期望的上下文中自动枚举此类实例,尤其是在管道中;值得注意的是,当您将 a 发送[JObject]到管道时,它的组成部分[JProperty]s 被单独发送
  • 使用类似@($nsObj.Array1).Value提取的数组的原始JSON值(字符串,数字,...) -即[JValue]实例-数组。

下面在上下文中演示了这些技术:

$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())"
}
Run Code Online (Sandbox Code Playgroud)

[1] PowerShell Core - 但不是Windows PowerShell - 当前 (v7) 实际上在幕后使用 NewtonSoft 的 Json.NET。