处理从 Invoke-RestMethod 返回的嵌套 JSON

问题描述 投票:0回答:1

我正在尝试通过他们的 API 从 WhatConverts 提取数据,然后将其上传到 SQL 表中。 当我更深入地研究底层数据的本质时,有几种数据类型作为对象返回;我只知道外部数据元素类型将是一个对象,但嵌套元素名称和嵌套对象数组中的类型是完全可变的。 返回的数据是json。

我的计划是将类型化数据导入到 SQL 中,对于嵌套数据,尝试将嵌套转换为 XML 并将其作为 XML 数据导入到 SQL 中,以便将来根据最终用户的要求进行处理。 在 SQL 导入之前,我将其从 Invoke-RestMethod 对象整理到 DataTable 中。 我无法处理嵌套数据,这就是我所在的位置:

#NOTE $importedData is an array of PSCustomObject returned from Invoke-RestMethod
$manipulatedData = New-Object System.Data.DataTable
$importedData | ForEach-Object {
    $newRow = $manipulatedData.NewRow()
    foreach ($prop in $_.PSObject.Properties) {
        # nested object
        if ($prop.Value -is [System.Management.Automation.PSCustomObject]) { 
            $tempPropname = $prop.Name
            if (-not ($manipulatedData.Columns.Contains($tempPropname))) {
                $newCol = New-Object System.Data.DataColumn($tempPropname)
                $manipulatedData.Columns.Add($newCol, "System.String" ) | Out-Null
            }
            #$prop | Select-Object Name,Value | ConvertTo-Xml -As String
            $newRow.$tempPropname = $prop | Select-Object Name,Value | ConvertTo-Xml -As String
        }
        # primitive value
        else {
            $tempPropname = $prop.Name 
            if (-not ($manipulatedData.Columns.Contains(($tempPropname)))) {
                $newCol = New-Object System.Data.DataColumn($tempPropname)
                $manipulatedData.Columns.Add($newCol, $prop.TypeNameOfValue ) | Out-Null
            }
            if ($prop.Value -ne '{}') {
                $newRow.$tempPropname = $prop.Value
            }
        }
    }
    $manipulatedData.Rows.Add($newRow)
}

对于这个练习,我相信我需要迭代每个嵌套的属性元素(我只关心 1 层深度),但后来我陷入困境,不得不从头开始构建 XML 文档,所以我改变了方向并尝试仅导出整个对象到 XML。

结果是垃圾,这是输出:

SetValueInvocationException: 
Line |
  90 |              $newRow.$tempPropname = $prop | Select-Object Name,Value  …
     |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Exception setting "additional_fields": "Type of value has a mismatch with column typeCouldn't store <<?xml version="1.0" encoding="utf-8"?> <Objects>   <Object Type="System.Management.Automation.PSCustomObject">     <Property Name="Name"
     | Type="System.String">additional_fields</Property>     <Property Name="Value" Type="System.Management.Automation.PSCustomObject">       <Property Type="System.String">@{Junk?=No; New Client?=Not Set; Service-Based?=Not Set}</Property>       <Property Name="Junk?"
     | Type="System.Management.Automation.PSNoteProperty">No</Property>       <Property Name="New Client?" Type="System.Management.Automation.PSNoteProperty">Not Set</Property>       <Property Name="Service-Based?" Type="System.Management.Automation.PSNoteProperty">Not
     | Set</Property>     </Property>   </Object> </Objects>> in additional_fields Column.  Expected type is Object[]."
json powershell invoke-restmethod
1个回答
0
投票

您的症状意味着您的输入 JSON 格式不规则:其中一个列最初是基于第一个输入对象从array创建的,而后续输入对象中同一列(属性)的值恰好是单个

[pscustomobject]
实例。

这是重现您的问题的示例 JSON:

[
  {
    "Primitive": 42,
    "Array": [ "foo", "bar" ]
  },
  {
    "Primitive": 43,
    "Array": {
      "CustomObjectScalar": "foo"
    }
  }
]

通过上述输入,属性

Array
的列最初基于第一个输入对象创建为
[object[]]
(即作为元素不受类型约束的数组,这就是
ConvertFrom-Json
转换数组值的方式) JSON 属性),并且当尝试将
[string]
实例分配为为第二个输入对象创建的行的列值时,会出现您看到的 类型不匹配 错误。


这是解决问题的一个方法,但需满足以下限制:

如果遇到

[pscustomobject]
实例且列数据类型为:

  • [object[]]
    ,只需将实例的 XML 字符串表示形式包装在单元素数组中,即可避免类型不匹配。

  • [string]
    ,像以前一样按原样分配 XML 字符串表示形式。

  • 否则,抛出(报告脚本终止错误)

在下面的代码中查找

# !!
注释。

#NOTE $importedData is an array of PSCustomObject returned from Invoke-RestMethod
$manipulatedData = New-Object System.Data.DataTable
$importedData | ForEach-Object {
  $newRow = $manipulatedData.NewRow()
  foreach ($prop in $_.PSObject.Properties) {
    # nested object
    if ($prop.Value -is [System.Management.Automation.PSCustomObject]) { 
      $tempPropname = $prop.Name
      $value = $prop | Select-Object Name, Value | ConvertTo-Xml -As String
      if (-not ($manipulatedData.Columns.Contains($tempPropname))) {
        $newCol = New-Object System.Data.DataColumn($tempPropname)
        $manipulatedData.Columns.Add($newCol, 'System.String' ) | Out-Null
        $dataType = [string] # !! Store the assigned type.
      }
      else {
        # !! Determine the preexisting data type.
        $dataType = $manipulatedData.Columns[$tempPropname].DataType
      }
      # !! Assign a column value based on the column's data type.
      switch ($dataType) {
        ([Object[]]) {
          $newRow.$tempPropname = @($value) # !! Wrap in single-element array
        }
        ([string]) {
          $newRow.$tempPropname = $value # !! Assign a string as-is, as before.
        }
        default {
          # !! Report an error, if the column has any other data type.
          throw "Unexpected preexisting data type of column ${tempPropertyName}: [$dataType]"
        }
      }
    }
    # primitive value
    else {
      $tempPropname = $prop.Name 
      if (-not ($manipulatedData.Columns.Contains(($tempPropname)))) {
        $newCol = New-Object System.Data.DataColumn($tempPropname)
        $manipulatedData.Columns.Add($newCol, $prop.TypeNameOfValue ) | Out-Null
      }
      if ($null -ne $prop.Value) {
        $newRow.$tempPropname = $prop.Value
      }
    }
  }
  $manipulatedData.Rows.Add($newRow)

}

© www.soinside.com 2019 - 2024. All rights reserved.