PowerShellのオブジェクトをjsonにシリアル化し、PSデスクトップとコアで同じ結果を得るにはどうすればよいですか?

マーク

プロローグ

私の場合、オブジェクトのソースを理解することが重要であることがわかりました。これは、RESTAPI応答からのJSONペイロードです。残念ながら、JSON->オブジェクト変換では、PSデスクトップとPSコアで異なる結果が生成されます。デスクトップでは、数値はInt32型に逆シリアル化されますが、コアでは-型に逆シリアル化さInt64ます。その結果Export-CliXml、オブジェクトのバイナリレイアウトが異なるため、を使用できなくなります。

主な質問

実際の結果と期待される結果を比較する必要がある単体テストがあります。期待される結果はjsonファイルに保存されるため、手順は次のとおりです。

  1. 実際の結果をjson文字列に変換します
  2. ディスクから文字列に期待される結果を読み取る
  3. 文字列として実際と期待を比較します

残念ながら、PSデスクトップConvertTo-JsonとPSコアConvertTo-Jsonは同じ結果を生成しないため、このスキームは機能しませんしたがって、期待される結果がデスクトップに保存され、テストがコアで実行される場合(ブーム、失敗)。およびその逆。

1つの方法は、jsonの2つのバージョンを保持することです。もう1つの方法は、ライブラリを使用してjsonを作成することです。

最初にNewtonsoft-JsonPowerShellモジュールを試しましたが、機能しません問題は、使用するC#ライブラリが何であれ、PSCustomObjectそれらを認識して同様に扱い、特別に処理する必要があることだと思いますしたがって、C#JSONライブラリだけを取得することはできません。

この時点で、2つのjsonが残っています。PSエディションごとに1つですが、これはちょっと悲しいことです。

より良いオプションはありますか?

編集1

私はいつでもjsonを読み取り、オブジェクトに変換してから、再びjsonに戻ることができると思います。それは最悪だ。

編集2

使ってみましたConvertTo-Json -Compressこれにより間隔の違いはなくなりますが、問題は、何らかの理由でデスクトップバージョンがすべての非文字を\u000...表現に変換することです。コアバージョンはそれを行いません。

注意してください:

デスクトップ

C:\> @{ x = "'a'" } |ConvertTo-Json -Compress
{"x":"\u0027a\u0027"}
C:\>

C:\>  @{ x = "'a'" } |ConvertTo-Json -Compress
{"x":"'a'"}
C:\>

現在、コアバージョンにはフラグが付いている-EscapeHandlingため、次のようになります。

C:\>  @{ x = "'a'" } |ConvertTo-Json -Compress -EscapeHandling EscapeHtml
{"x":"\u0027a\u0027"}
C:\>

ビンゴ!同じ結果。ただし、このコードは、このフラグがないデスクトップバージョンでは実行されません。より多くのマッサージが必要です。それが唯一の問題かどうかを確認します。

編集3

高価な後処理なしに、コアバージョンとデスクトップバージョンの違いを調整することは不可能です。注意してください:

デスクトップ

C:\> @{ x = '"a"';y = "'b'" } |ConvertTo-Json -Compress
{"y":"\u0027b\u0027","x":"\"a\""}
C:\>

C:\> @{ x = '"a"';y = "'b'" } |ConvertTo-Json -Compress -EscapeHandling EscapeHtml
{"y":"\u0027b\u0027","x":"\u0022a\u0022"}
C:\> @{ x = '"a"';y = "'b'" } |ConvertTo-Json -Compress
{"y":"'b'","x":"\"a\""}
C:\>

jsonアプローチを救済する方法について何か提案はありますか?

編集4

Export-CliXmlPSのバージョンが異なるため、このアプローチも機能しません。

デスクトップ

C:\> ('{a:1}' | ConvertFrom-Json).a.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType


C:\>

C:\> ('{a:1}' | ConvertFrom-Json).a.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int64                                    System.ValueType

C:\>

したがって、同じJSONはInt32、デスクトップとInt64コア異なる数値タイプを使用して表されますそれは、を使用するオプションをベッドに置きますExport-CliXml

私が何かを逃していない限り。

他に選択肢はないと思いますが、二重変換-json- >オブジェクト-> jsonを実行すると、同じPSエディションで2つのjsonが作成されます。それは大変な時間です。

mklement0
  • 変換するから、元のJSON、使用するサードパーティNewtonsoft.Json PowerShellのラッパーConvertFrom-JsonNewtonsoftコマンドレットを-これは、内蔵のクロス版との互換性(確認する必要がありますConvertFrom-JsonないWindows PowerShellは一方で、カスタムパーサーを使用するためのPowerShellエディション間で保証を、 PowerShell [Core] v6 +は、少なくともv7.1までのNewtonsoft.jsonを使用しますが、新しい(ish).NET Core System.Text.JsonAPIへの移行が予定されています)。

    • 重要組み込みが出力するネストされたグラフとは異なり、ネストさConvertFrom-JsonNewtonsoftれた順序付きハッシュテーブル[ordered] @{ ... }System.Collections.Specialized.OrderedDictionary)を返します(の配列)同様に、入力としてハッシュテーブル(辞書)のみ(の配列)を期待し、特に自分で学んだようにインスタンスをサポートしませ[pscustomobject]ConvertFrom-JsonConvertTo-JsonNewtonsoft[pscustomobject]

      • 警告:この記事の執筆時点では、ラッパーモジュールは2019年5月に最後に更新されており、基になるバンドルNewtonsoft.Json.dllアセンブリのバージョンはかなり古いものです(ただし8.0この記事の執筆時点で12.0最新です)。モジュールのソースコードを参照してください
    • 順番にという注意JSONを解析するRESTfulなWebサービスから取得し、手動では、あなたが使用してはいけませんInvoke-RestMethod、それはとして、暗黙的に解析し、リターンが[pscustomobject]グラフオブジェクト。代わりに、返された応答のプロパティを使用Invoke-WebRequestしてアクセスし.Contentます。

  • 変換するには、ディスクに保存するのに適した形式で、2つのオプションがあります。

    • (A)シリアル化された形式もJSONにする必要がある[pscustomobject]ConvertTo-JsonNewtonsoft場合は、すべてのグラフをに渡す前に、すべてのグラフを(順序付けられた)ハッシュテーブル変換する必要あります

      • それを行う関数については、以下を参照してくださいConvertTo-OrderedHashTable
    • (B)特定のシリアル化形式が重要でない場合は、すべてその事項はフォーマットは比較のためのPowerShellエディション間で同一であることである場合、すなわち、余分な作業が必要ありません:ビルトイン使用Export-Clixmlされ、コマンドレット扱うことができる任意のCLIXMLと呼ばれるPowerShellのネイティブのXMLベースのシリアル化形式(特にPowerShellリモーティングで使用される)を入力して生成します。これは、クロスエディション互換である必要があります(少なくともWindowsPowerShell側のv5.1およびPowerShell [Core] v7以降) .1、の1.1.0.1報告によると、どちらも同じバージョンのシリアル化プロトコルを使用します$PSVersionTable.SerializationVersion

      • あなたは可能性がありますが、そのようなaはにファイルを永続再変換するオブジェクトImport-Clixmlタイプの忠実性の潜在的な損失のデシリアライゼーションには比較になりシリアライズお勧め(CLIXML)表現を。

      • また、PowerShell v7.1の時点では、メモリ内のCLIXML表現を作成するコマンドレットベースの方法がないため、現時点ではPowerShellAPIを直接使用する必要があることに注意してくださいSystem.Management.Automation.PSSerializer.Serializeしかし、にメモリ内の対応を提供するImport-CliXml/Export-CliXmlの形式でConvertFrom-CliXml/ConvertTo-CliXmlコマンドレットことであった緑色点灯将来の拡張として


Re(A):これは、他のタイプを通過させながら(ネストされている可能性のある)オブジェクトを順序付けられたハッシュテーブルに変換する関数ConvertTo-OrderedHashtable[pscustomobject]です。したがって、次のようにパイプラインに挿入するだけで済みます。

# CAVEAT: ConvertTo-JsonNewtonSoft only accepts a *single* input object.
[pscustomobject] @{ foo = 1 }, [pscustomobject] @{ foo = 2 } | 
  ConvertTo-OrderedHashtable |
    ForEach-Object { ConvertTo-JsonNewtonSoft $_ }
function ConvertTo-OrderedHashtable {
<#
.SYNOPSIS
Converts custom objects to ordered hashtables.

.DESCRIPTION
Converts PowerShell custom objects (instances of [pscustomobject]) to
ordered hashtables (instances of [System.Collections.Specialized.OrderedDictionary]),
which is useful for to-JSON serialization via the Newtonsoft.JSON library.

Note: 
 * Custom objects are processed recursively.
 * Any scalar non-custom objects are passed through as-is.
 * Any (non-dictionary) collections in property values are converted to 
  [object[]] arrays.

.EXAMPLE
1, [pscustomobject] @{ foo = [pscustomobject] @{ bar = 'none' }; other = 2 } | ConvertTo-OrderedHashtable

Passes integer 1 through, and converts the custom object to a nested ordered
hashtable.
#>
  [CmdletBinding()]
  param(
    [Parameter(ValueFromPipeline)] $InputObject
  )

  begin {

    # Recursive helper function
    function convert($obj) {
      if ($obj -is [System.Management.Automation.PSCustomObject]) {
        # a custom object: recurse on its properties
        $oht = [ordered] @{ }
        foreach ($prop in $obj.psobject.Properties) {
          $oht.Add($prop.Name, (convert $prop.Value))
        }
        return $oht
      }
      elseif ($obj -isnot [string] -and $obj -is [System.Collections.IEnumerable] -and $obj -isnot [System.Collections.IDictionary]) {
        # A collection of sorts (other than a string or dictionary (hash table)), recurse on its elements.
        return @(foreach ($el in $obj) { convert $el })
      }
      else { 
        # a non-custom object, including .NET primitives and strings: use as-is.
        return $obj
      }
    }

  }

  process {

    convert $InputObject

  }

}

Re(B):アプローチのデモンストレーションExport-CliXml(このコードはどちらのPSエディションからでも実行できます):

$sb = {

  Install-Module -Scope CurrentUser Newtonsoft.json
  if (-not $IsCoreClr) { 
    # Workaround for PS Core's $env:PSModulePath overriding WinPS'
    Import-Module $HOME\Documents\WindowsPowerShell\Modules\newtonsoft.json
  }
  @'
  {
      "results": {
          "users": [
              {
                  "userId": 1,
                  "emailAddress": "[email protected]",
                  "date": "2020-10-05T08:08:43.743741-04:00",
                  "attributes": {
                      "height": 165,
                      "weight": 60
                  }
              },
              {
                  "userId": 2,
                  "emailAddress": "[email protected]",
                  "date": "2020-10-06T08:08:43.743741-04:00",
                  "attributes": {
                      "height": 180,
                      "weight": 72
                  }
              }
          ]
      }
  }
'@ | ConvertFrom-JsonNewtonsoft | Export-CliXml "temp-$($PSVersionTable.PSEdition).xml"
  
}
  
# Execute the script block in both editions

Write-Verbose -vb 'Running in Windows PowerShell...'
powershell -noprofile $sb

Write-Verbose -vb 'Running in PowerShell Core...'
pwsh -noprofile $sb
  
# Compare the resulting CLIXML files.
Write-Verbose -vb "Comparing the resulting files: This should produce NO output,`n         indicating that the files have identical content."
Compare-Object (Get-Content 'temp-Core.xml') (Get-Content 'temp-Desktop.xml')

Write-Verbose -vb 'Cleaning up...'
Remove-Item 'temp-Core.xml', 'temp-Desktop.xml'

次の詳細な出力が表示されます。

VERBOSE: Running in Windows PowerShell...
VERBOSE: Running in PowerShell Core...
VERBOSE: Comparing the resulting files: This should produce NO output, 
         indicating that the files have identical content.
VERBOSE: Cleaning up...

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ