在PowerShell中了解NewtonSoft

时间:2019-09-27 20:42:19

标签: json powershell 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?

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

我认为我对这里的一些基本知识不了解。是否可以确定根父级?另外,对理解我在脚本中的注释的任何帮助都将非常有用。这就是为什么我不能返回诸如path或parent之类的东西,但是当我在VSCode中进行调试时却可以看到它的原因。

2 个答案:

答案 0 :(得分:4)

dbc's answer包含有用的背景信息,并且很清楚,从PowerShell调用NewtonSoft Json.NET库很麻烦。

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

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

直接在PowerShell中使用NewtonSoft的Json.NET是尴尬,但如果您观察到一些规则,则是可管理的

  • 缺少可见的输出并不一定 表示没有任何输出

    • 由于bug in 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]对象默认显示其 elements 实例属性的列表(暗示Format-List应用于枚举);您可以使用Format-* cmdlet来调整输出形状;例如$nsObj | Format-Table Path, Type

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

    • $nsObj[<n>]实际上应该工作,因为与C#不同,PowerShell将通过接口实现的成员公开为可直接调用的类型成员,因此 JObject通过IList<JToken>接口实现的>数字索引器应该可以访问,但由于this bug(由于PowerShell Core 7.0.0-preview)而无法访问。 4)。

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

    • 请注意,可能再次需要基于.Value的其他访问权限,以打印结果(或提取强类型属性 value )。

  • 通常,不要使用.GetEnumerator()方法 [JObject][JArray]实例可以直接 枚举

    • 请记住, PowerShell可能会自动枚举此类实例,在您不期望的情况下,尤其是在管道中;值得注意的是,当您将[JObject]发送到管道时,是由其构成的[JProperty]单独发送的。
  • 使用类似@($nsObj.Array1).Value的东西来提取原始 JSON值(字符串,数字等)数组的 values -即,[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())"
}

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

答案 1 :(得分:3)

您在这里有几个单独的问题:

  1. $nsObj[0] #Returns nothing. Why?

    这是因为nsObj对应于JSON 对象,并且,如this answer How to get first key from JObject? 所述,{{ 3}}不直接支持通过整数索引(而不是属性名称)访问属性。

    JObject确实实现了IList<JToken> 显式,因此,如果您可以将nsObj上载到这样的列表,则可以按索引访问属性-但显然在PowerShell中调用显式实现的方法并不简单。如 JObject 的答案中所述,有必要通过反射来实现。

    首先,定义以下功能:

    Function ChildAt([Newtonsoft.Json.Linq.JContainer]$arg1, [int]$arg2) 
    {
        $property = [System.Collections.Generic.IList[Newtonsoft.Json.Linq.JToken]].GetProperty("Item")
        $item =  $property.GetValue($nsObj, @([System.Object]$arg2))
    
        return $item
    }
    

    然后您可以这样做:

    $firstItem = ChildAt $nsObj 0
    

    How can I call explicitly implemented interface method from PowerShell?在线试用。

  2. #??? Why can't I see the Path property like when just output $nsObj.Array2[0] ???

    这里的问题是here不会返回您认为的结果。您的代码假定它返回了对象的JToken子对象,而实际上它被声明为

    public IEnumerator<KeyValuePair<string, JToken>> GetEnumerator()
    

    由于KeyValuePair<string, JToken>不具有属性PathParent,因此您的输出方法将失败。

    JObject确实实现了IList<JToken>IEnumerable<JToken>之类的接口,但是它是显式实现的,并且如上所述,调用相关的GetEnumerator()方法需要反思。

    请使用基类方法JObject.GetEnumerator()。此方法适用于JContainer.Children()JObject并按文档顺序返回直接子级:

    foreach( $o in $nsObj.Array2[0].Children() ){
        "Path is: $($o.Path)"
        "Parent is: $($o.Parent)"
    }
    

    JArray在线试用。

  3. $nsObj.Array1 #Again nothing. Maybe because it contains no key:value pairs?

    实际上,如果我这样做的话,确实会返回Array1的值

    $nsObj.Array1.ToString() 
    

    显示与Array1的值相对应的JSON。真正的问题似乎在于,PowerShell不知道如何自动打印带有here内容的JArray甚至是简单的独立JValue。如果我这样做:

    $jvalue = New-Object Newtonsoft.Json.Linq.JValue 'my jvalue value'
    '$jvalue'             #Nothing output
    $jvalue
    '$jvalue.ToString()'  #my jvalue value
    $jvalue.ToString()
    

    然后输出为:

    $jvalue
    $jvalue.ToString()
    my jvalue value
    

    JValue和相关的here在线试用。

    因此,教训是:在PowerShell中打印JToken层次结构时,请始终使用ToString()

    关于为什么打印JObject会产生一些输出,而打印JArray却不会,我只能推测。 here实现了JToken接口,该接口也由IDynamicMetaObjectProvider实现;可能有关JObject而不是JValueJArray的实现方式的细节与PowerShell的信息打印代码兼容。