命名って難しい

変数、関数、クラスなどなど実装より命名に毎回悩むタイプの人間による技術についてのメモ。

Powershellで編集可能なJSONを読み込む関数を作りました!&Pester環境を更新

以下のようなことをしたい状況がありました。

  • JSONを読み込む
  • Object型の要素に新しい値を加える
  • 編集した内容でJSONを保存する

しかし、PowerShell標準のコマンド ConvertFrom-Json で取得したオブジェクトは配列を除き編集できません。それを編集できるようにして取得するスクリプトを書きました。

前提環境

テスト環境の Pester は更新しました。

$PSVersionTable


Name                           Value
----                           -----
PSVersion                      5.1.19041.1320
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.1320
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Get-Module Pester


ModuleType Version    Name     ExportedCommands
---------- -------    ----     ----------------
Script     5.3.1      Pester   Add-ShouldOperator, AfterAll, AfterEach, Assert-MockCalled...}

標準搭載の ConvertFrom-Json について

どういう挙動か

標準搭載のConvertFrom-Jsonで取得した時、JSONのObject型の要素は PSCustomObject となり、新しい値を設定することはできません。

docs.microsoft.com

色々ググりましたが、PSCustomObject を編集するには色々方法が面倒なようです。

PSCustomObjectに対してしたいこと 方法
新しい要素の追加 Add-Member
要素の削除 Select-Object-ExcludeProperty して新しいオブジェクト生成

理想の挙動

以下のように動いたらいいですよね。

<# 前提環境を作るコード(ファイルがあるものだと思ってください。) #>
$samplejson = @"
{
    "id":1,
    "name":"Dareka San",
    "otherInfo":{
        "birthday":"2000-12-05"
    }
}
"@
Set-Content -Value $samplejson -Path 'sample.json'

<# こんな感じでできたらいいなぁ、のコード #>

# ①ファイル読み込み
$json = Get-Content 'Sample.json' -Encoding UTF8 | ConvertFrom-Json
# ②新しい要素追加
$json.otherInfo.bloodType = "RH +O"
# ③保存
$json | ConvertTo-Json | Set-Content 'sample.json' -Encoding UTF8 
<# 結果こうなっててほしいなぁのJSON
{
    "id":1,
    "name":"Dareka San",
    "otherInfo":{
        "birthday":"2000-12-05",
        "bloodType":"RH +O"
    }
}

#>

実現したこと

編集できるJSONを返す ConvertFrom-Json 、 ConvertFrom-JsonEditable を作りました。

これを使うと以下のように理想の挙動を実現できます。

$json = Get-Content 'Sample.json' -Encoding UTF8 | ConvertFrom-JsonEditable
$json.otherInfo.bloodType = "RH +O"
$json | ConvertTo-Json | Set-Content 'sample.json'

どのように実現しているか

ざっくりとした流れ

  • 標準のConvertFrom-Jsonで文字列からオブジェクトに変換する
  • オブジェクトの中で変換対象のものを変換していく(再帰的な処理)
    • PSCustomObject -> OrderedDictonary([Ordered]で変換するもの) に変換し、 深さを取得する関数の追加
    • Array -> List に変換し、深さを取得する関数のみ追加
    • プリミティブ型の変数は変換なし
    • 返却する
    • となります。

      ここで新出の 深さを取得する関数 とは ConvertTo-Json-Depthパラメーター用の関数です。 変換処理と同様、再帰的な処理を行い、JSONの階層の深さを取得します。

      ひっかかったこと

      ソースコードを一部引用してひっかかったことをメモります。

      Unboxing/Unpacking(?)の罠

      勝手に値を変換されてしまうアレで色々とひっかかり、開発ハマってました。

      ハマったこと

      • 要素1の配列を戻り値に渡したら配列の要素の型のオブジェクトが渡されてきた
      • 関数で受け渡した配列オブジェクトが再生成されていた
        • 配列オブジェクトには 深さを取得する関数 を追加しているので、それが消えた

      要素1の配列を戻り値に渡したら配列の要素の型のオブジェクトが渡されてきた

      下記のようなコードを実行すると挙動が分かります。

      function ReturnSingle {
          return @(1)
      }
      function ReturnDouble {
          return @(1,2)
      }
      
      $single = ReturnSingle
      Write-Host "Single:$($single.GetType())"
      
      $double = ReturnDouble
      Write-Host "Double:$($double.GetType())"
      

      結果はこうなります。

      Single:int
      Double:System.Object[]

      関数で受け渡した配列オブジェクトが再生成されていた

      以下のコードを実行すると・・・

      function GetSayHelloList {
          $list = [System.Collections.Generic.List[Object]]@()
          Add-Member -InputObject $list -Name SayHello -MemberType ScriptMethod -Value {
              $this | ForEach-Object { Write-Host "Hello, $_!" }
          }
          return $list
      }
      
      $list = GetSayHelloList
      $list.Add("Baki")
      $list.Add("Suedou")
      $list.Add("Katou")
      $list.SayHello()
      

      結果は以下のようにエラーです。

      null 値の式ではメソッドを呼び出せません。
      + $list.Add("Baki")
      + ~~~~~~~~~~~~~~~~~
          + CategoryInfo          : InvalidOperation: (:) []、RuntimeException
          + FullyQualifiedErrorId : InvokeMethodOnNull
      
      null 値の式ではメソッドを呼び出せません。
      + $list.Add("Suedou")
      + ~~~~~~~~~~~~~~~~~~~
          + CategoryInfo          : InvalidOperation: (:) []、RuntimeException
          + FullyQualifiedErrorId : InvokeMethodOnNull
      
      null 値の式ではメソッドを呼び出せません。
      + $list.Add("Katou")
      + ~~~~~~~~~~~~~~~~~~
          + CategoryInfo          : InvalidOperation: (:) []、RuntimeException
          + FullyQualifiedErrorId : InvokeMethodOnNull
      
      null 値の式ではメソッドを呼び出せません。
      + $list.SayHello()
      + ~~~~~~~~~~~~~~~~
          + CategoryInfo          : InvalidOperation: (:) []、RuntimeException
          + FullyQualifiedErrorId : InvokeMethodOnNull

      対応のコツはカンマの1文字

      どう変えれば直るか、分かりやすくWinMergeのスクショで説明すると変更点は以下のようになります。

      f:id:NotShown:20211205232127p:plain
      差分
      f:id:NotShown:20211205232301p:plain
      差分

      これはどのように動いているかというと

      • カンマをつける(要素1の配列になる)
      • return で要素1の配列が展開されて中身だけになる
      • 中身にあったオブジェクトが展開されず無傷の状態で返却される。

      という感じです。

      対策として配列をいちいち作っているのが解せませんが、これで想定の動作をします。

      ソースコード

      ソースコードは今回もGitHubに公開してみましたが、こちらにもコピペしておきます!

      github.com

      using namespace System.Management.Automation
      using namespace System.Collections
      using namespace System.Collections.Generic
      
      
      function ConvertValuesInObjectToHashtable {
          <#
          .SYNOPSIS
              オブジェクトの要素もしくはプロパティの値をハッシュテーブルに変換する。
          .DESCRIPTION
              引数の型によって動作を変える。
              - PSCustomObject型のオブジェクト
                  - 各プロパティ名をKey、値をValueとしてハッシュテーブルにする。
              - Array型のオブジェクト
                  - 各要素のインデックスをKey、値をValueとしてハッシュテーブルにする。
              - それ以外は空ハッシュテーブルとする。
          .PARAMETER InputObject
              Object型のオブジェクト
          .OUTPUTS
              Hashtable.
          #>
          [CmdletBinding()]
          param (
              [Parameter(Mandatory)]
              [object]
              $InputObject
          )
          $hash = [ordered]@{}
          if ($InputObject -is [IDictionary] ) {
              $InputObject.Keys | ForEach-Object { $hash.Add($_,$InputObject[$_])}
          }elseif ($InputObject -is [IEnumerable] -and $InputObject -isnot [string]) {
              for ($i = 0; $i -lt $InputObject.Count; $i++) {
                  $hash.Add($i, $InputObject[$i])
              }
          }
          elseif ($InputObject -is [PSCustomObject]) {
              $InputObject.PSObject.Properties | ForEach-Object { $hash.Add($_.Name, $_.Value) }
          }
          
          return $hash
      }
      
      function AddGetDepthMethod {
          param (
              [Parameter(Mandatory)]
              [object]
              $InputObject
          )
          if ($InputObject -is [ValueType] -or $InputObject -is [string]) {
              throw "対象外のオブジェクトです。"
          }
      
          # オブジェクトに深さ取得のオブジェクトを追加する。
          Add-Member -InputObject $InputObject -MemberType ScriptMethod -Name GetDepth -Value {
              param()
              CalcDepth -InputObject $this
          }
      }
      
      function ConvertJsonObjectToEditableJson {
          <#
          .SYNOPSIS
              オブジェクトをハッシュテーブルもしくは配列に変換する。
          .DESCRIPTION
      
          .PARAMETER InputObject
              Object型のオブジェクト
          .OUTPUTS
              object[].
          #>
          param (
              [parameter(Mandatory)]
              [Object]$InputObject
          )
      
          if ($InputObject -is [ValueType] -or $InputObject -is [string]) {
              throw "対応していない引数の型です。"
          }
      
          $dict = ConvertValuesInObjectToHashtable -InputObject $InputObject
          $table = [ordered]@{}
          foreach ($kvp in $dict.GetEnumerator()) {
              $addValue = $kvp.Value
              if ($kvp.Value -is [PSCustomObject] -or $kvp.Value -is [Array]) {
                  $addValue = ConvertJsonObjectToEditableJson -InputObject $kvp.Value
              }
              $table.Add($kvp.Key, $addValue)
          }
          # 入力が配列の場合、ハッシュテーブルを配列に変更する。
          if ($InputObject -is [Array]) {
              $table = [List[Object]]@( $table.Keys | Sort-Object | ForEach-Object { ,$table[$_] })
          }
      
          AddGetDepthMethod -InputObject $table
          # 配列の場合を考慮し、アンボクシングを防ぐ
          return , $table
      }
      
      function CalcDepth {
          <#
          .SYNOPSIS
              入れ子の深さを算出する。
          .DESCRIPTION
              引数のオブジェクトが持つ要素(プロパティ/以下の条件を入れ子と判断し、再帰的に本関数を実行する。
              - InputObjectがPSCustomObject型
              - InputObjectがIDictionary型
              - InputObjectが文字列以外のIEnumerable型
      
          .PARAMETER InputObject
              Object型のオブジェクト
          .OUTPUTS
              int型の入れ子の深さ.
          #>
          param (
              [Parameter(Mandatory)]
              [Object]
              $InputObject
          )
          if ($null -eq $InputObject) {
              throw "引数が不正です。"
          }
          [int]$maxDepth = 0
          [int]$tempDepth = 0
          [Hashtable]$hashtable = ConvertValuesInObjectToHashtable -InputObject $InputObject
          foreach ($value in $hashtable.Values) {
              if ($value -is [PSCustomObject] -or 
                  $Value -is [IDictionary] -or
                  $Value -is [IEnumerable] -and $Value -isnot [string]) {
                  $tempDepth = CalcDepth -InputObject $value
              }
              $maxDepth = [System.Math]::Max($maxDepth, $tempDepth)
          }
          return (1 + $maxDepth)
      }
      
      function ConvertFrom-JsonEditable {
          <#
          .SYNOPSIS
              JSON文字列から編集できるJSON型オブジェクトを生成する。
          .DESCRIPTION
              ConvertFrom-Jsonコマンド利用するため、それがエラーを起こす入力は同様にエラーを発生させる。
          .PARAMETER Value
              文字列のJSON
          .OUTPUTS
              [OrderedDictionary] | [Array]
          #>
          param (
              [Parameter(Mandatory, ValueFromPipeline = $true)]
              [Array]
              $Value
          )
      
          # パイプライン用
          Begin {
              $jsonText = ""
          }
          Process {
              $jsonText += $Value
          }
          End {
              if (-not $jsonText) {
                  throw "JSON文字列がnullもしくは空文字です。"
              }
              try {
                  $json = ConvertFrom-Json -InputObject $jsonText
                  $object = ConvertJsonObjectToEditableJson -InputObject $json
                  return ,$object
              }
              catch {
                  if (-not $json) {
                      Throw "ConvertFrom-Jsonエラー:JSONの形式を確認してください。"
                  }
                  Throw $_
              }
          }
      }
      

      テストコード

      Pester がプリインストールのバージョンだったので、アップデートしました。 Version5を前提としています。

      using namespace System.Management.Automation
      using namespace System.Collections.Specialized
      
      BeforeAll {
          . $PSCommandPath.Replace('.Tests.ps1', '.ps1')
      }
      
      Describe "ConvertValuesInObjectToHashtable" {
          Context "異常ケース" {
              It "NULL" {
                  { ConvertValuesInObjectToHashtable -InputObject $null } | Should -Throw
              }
              It "空配列" {
                  $actual = ConvertValuesInObjectToHashtable -InputObject (@())
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual.Count   | Should -BeExactly 0
              }
          }
          Context "正常ケース PSCustomObject" {
              It "Depth:1 Count:1" {
                  $expect = [PSCustomObject]@{a = 1 }
                  $actual = ConvertValuesInObjectToHashtable -InputObject $expect
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual         | Should -HaveCount 1
                  $actual.a       | Should -BeExactly $expect.a
              }
              It "Depth:1 Count:2 Array" {
                  $expect = [PSCustomObject]@{a = "1"; b = @(2, 3) }
                  $actual = ConvertValuesInObjectToHashtable -InputObject $expect
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual.count   | Should -BeExactly 2
                  $actual.a       | Should -BeExactly $expect.a
                  $actual.b       | Should -BeExactly $expect.b
              }
              It "Depth:2 Count:2 Hashtable" {
                  $expect = [PSCustomObject]@{a = "1"; b = @{c = 2 } }
                  $actual = ConvertValuesInObjectToHashtable -InputObject $expect
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual.count   | Should -BeExactly 2
                  $actual.a       | Should -BeExactly $expect.a
                  $actual.b       | Should -BeExactly $expect.b
              }
          }
          Context "正常ケース Hashtable" {
              It "Depth:1 Count:1" {
                  $expect = @{a = 1 }
                  $actual = ConvertValuesInObjectToHashtable -InputObject $expect
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual         | Should -HaveCount 1
                  $actual.a       | Should -BeExactly $expect.a
              }
              It "Depth:1 Count:2 Array" {
                  $expect = @{a = "1"; b = @(2, 3) }
                  $actual = ConvertValuesInObjectToHashtable -InputObject $expect
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual.count   | Should -BeExactly 2
                  $actual.a       | Should -BeExactly $expect.a
                  $actual.b       | Should -BeExactly $expect.b
              }
              It "Depth:2 Count:2 Hashtable" {
                  $expect = @{a = "1"; b = @{c = 2 } }
                  $actual = ConvertValuesInObjectToHashtable -InputObject $expect
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual.count   | Should -BeExactly 2
                  $actual.a       | Should -BeExactly $expect.a
                  $actual.b       | Should -BeExactly $expect.b
                  $actual.b       | Should -BeOfType [Hashtable]
              }
          }
          Context "正常ケース Array" {
              It "Depth:1 Count:1" {
                  $expect = @(1)
                  $actual = ConvertValuesInObjectToHashtable -InputObject $expect
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual         | Should -HaveCount 1
                  $actual.a       | Should -BeExactly $expect.a
              }
              It "Depth:1 Count:2 Array" {
                  $expect = @(1, @(2))
                  $actual = ConvertValuesInObjectToHashtable -InputObject $expect
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual.count   | Should -BeExactly $expect.count
                  $actual[0]      | Should -BeExactly $expect[0]
                  $actual[1]      | Should -BeExactly $expect[1]
              }
              It "Depth:2 Count:2 Hashtable" {
                  $expect = @(1, @{a = 2 })
                  $actual = ConvertValuesInObjectToHashtable -InputObject $expect
                  $actual         | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual.count   | Should -BeExactly $expect.count
                  $actual[0]      | Should -BeExactly $expect[0]
                  $actual[1]      | Should -BeExactly $expect[1]
              }
          }
      }
      
      Describe "AddGetDepthMethod" {
          Context "異常ケース" {
              It "Null" {
                  { AddGetDepthMethod -InputObject $null } | Should -Throw
              }
              It "ValueType" {
                  { AddGetDepthMethod -InputObject 1 } | Should -Throw
                  { AddGetDepthMethod -InputObject "1" } | Should -Throw
              }
          }
          Context "正常ケース" {
              It "Array" {
                  $array = @()
                  AddGetDepthMethod -InputObject $array
                  $array.GetDepth | Should -Not -Be $null
              }
              It "Hashtable" {
                  $array = @{}
                  AddGetDepthMethod -InputObject $array
                  $array.GetDepth | Should -Not -Be $null
              }
          }
      }
      
      Describe "ConvertJsonObjectToEditableJson" {
          Context "異常ケース" {
              It "Null" {
                  { ConvertJsonObjectToEditableJson -InputObject $null }  | Should -Throw
              }
              It "Not Hashtable or Array or PSCustomObject: ValueType" {
                  { ConvertJsonObjectToEditableJson -InputObject 1 }      | Should -Throw
              }
              It "Not Hashtable or Array or PSCustomObject: string" {
                  { ConvertJsonObjectToEditableJson -InputObject "2" }    | Should -Throw
              }
          }
      }
      
      Describe "CalcDepth" {
          Context "異常ケース" {
              It "Null" {
                  { CalcDepth -InputObject $null } | Should -Throw
              }
          }
          Context "正常ケース Array" {
              It "Enpty array" {
                  CalcDepth -InputObject @() | Should -Be 1
              }
              It "Nested array" {
                  CalcDepth -InputObject @(1, @()) | Should -Be 2
              }
              It "Hashtable in array" {
                  CalcDepth -InputObject @(1, [PSCustomObject]@{a = 2 }) | Should -Be 2
              }
              It "Depth (1,2,3)" {
                  CalcDepth -InputObject @(1, @(2), @(@(3), 4)) | Should -Be 3
              }
              It "Depth (3,2,1)" {
                  CalcDepth -InputObject @(@(@(1), 2), @(3), 4) | Should -Be 3
              }
          }
          Context "正常ケース Hashtable" {
              It "Enpty hashtable" {
                  CalcDepth -InputObject @{} | Should -Be 1
              }
              It "Nested hashtable" {
                  CalcDepth -InputObject @{a = 1; b = @{c = 3 } } | Should -Be 2
              }
              It "Array in hashtable" {
                  CalcDepth -InputObject @{a = 1; b = @(2, 3) } | Should -Be 2
              }
          }
          Context "正常ケース PSCustomObject" {
              It "Enpty hashtable" {
                  CalcDepth -InputObject ([PSCustomObject]@{}) | Should -Be 1
              }
              It "Nested hashtable" {
                  CalcDepth -InputObject ([PSCustomObject]@{a = 1; b = [PSCustomObject]@{c = 3 } }) | Should -Be 2
              }
              It "Array in hashtable" {
                  CalcDepth -InputObject ([PSCustomObject]@{a = 1; b = @(2, 3) }) | Should -Be 2
              }
              It "Depth (1,2,3)" {
                  CalcDepth -InputObject ([PSCustomObject]@{
                          a = 1;
                          b = [PSCustomObject]@{};
                          c = [PSCustomObject]@{
                              cc = [PSCustomObject]@{ccc = 3 }
                          }
                      }) | Should -Be 3
              }
              It "Depth (3,2,1)" {
                  CalcDepth -InputObject ([PSCustomObject]@{
                          a = [PSCustomObject]@{
                              aa = [PSCustomObject]@{}
                          };
                          b = [PSCustomObject]@{};
                          c = 3
                      }) | Should -Be 3
              }
          }
      }
      Describe "ConvertFrom-JsonEditable" {
          Context "異常ケース" {
              It "Null" {
                  { ConvertFrom-JsonEditable -Value $null } | Should -Throw
              }
              It "JSON形式でない文字列" {
                  $value = '{"value":{}'
                  { ConvertFrom-JsonEditable -Value $value } | Should -Throw
              }
          }
          Context "正常ケース Objectはじまり" {
              It "Without Array and Object" {
                  $value = @"
      {
          "a":1,
          "b":"2",
          "c":true,
          "d":null
      }
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual.a | Should -BeExactly 1
                  $actual.b | Should -BeExactly "2"
                  $actual.c | Should -BeExactly $true
                  $actual.d | Should -BeExactly $null
                  $actual.GetDepth() | Should -BeExactly 1
              }
              It "With Array" {
                  $value = @"
      {
          "a":1,
          "b":"2",
          "c":true,
          "d":[
              4,5,6
          ]
      }
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual.a               | Should -BeExactly 1
                  $actual.b               | Should -BeExactly "2"
                  $actual.c               | Should -BeExactly $true
                  $actual.d               | Should -BeExactly @(4, 5, 6)
                  $actual.GetDepth()      | Should -BeExactly 2
                  $actual.d.GetDepth()    | Should -BeExactly 1
              }
              It "With Object" {
                  $value = @"
      {
          "a":1,
          "b":"2",
          "c":false,
          "d":{
              "da":3,
              "db":"4",
              "dc":"true"
          }
      }
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual.a           | Should -BeExactly 1
                  $actual.b           | Should -BeExactly "2"
                  $actual.c           | Should -BeExactly $false
                  $actual.d           | Should -BeOfType [System.Collections.Specialized.OrderedDictionary] 
                  $actual.d.da        | Should -BeExactly 3
                  $actual.d.db        | Should -BeExactly "4"
                  $actual.d.dc        | Should -BeExactly $true
                  $actual.GetDepth()  | Should -BeExactly 2
                  $actual.d.GetDepth() | Should -BeExactly 1
              }
              It "With Array and Object" {
                  $value = @"
      {
          "a":1,
          "b":"2",
          "c":false,
          "d":{
              "da":3,
              "db":"4",
              "dc":"true"
          },
          "e":[
              5,
              "6",
              true,
              {"f":7}
          ]
      }
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual.a               | Should -BeExactly 1
                  $actual.b               | Should -BeExactly "2"
                  $actual.c               | Should -BeExactly $false
                  $actual.d               | Should -BeOfType [System.Collections.Specialized.OrderedDictionary] 
                  $actual.d.da            | Should -BeExactly 3
                  $actual.d.db            | Should -BeExactly "4"
                  $actual.d.dc            | Should -BeExactly $true
                  $actual.d.GetDepth()    | Should -BeExactly 1
                  $actual.e[0]            | Should -BeExactly 5
                  $actual.e[1]            | Should -BeExactly "6"
                  $actual.e[2]            | Should -BeExactly $true
                  $actual.e[3]            | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
                  $actual.e[3].f          | Should -BeExactly 7
                  $actual.e[3].GetDepth() | Should -BeExactly 1
                  $actual.e.GetDepth()    | Should -BeExactly 2
                  $actual.GetDepth()      | Should -BeExactly 3
              }
          }
      
          Context "正常ケース Arrayはじまり" {
              It "Arrayのみ" {
                  $value = @"
      [
          1,
          "2",
          true,
          null
      ]
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual | Should -BeExactly @(1, "2", $true, $null)
                  $actual.GetDepth() | Should -BeExactly 1
                  
              }
              It "Array + Array" {
                  $value = @"
      [
          1,
          "2",
          true,
          null,
          [3]
      ]
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual | Should -BeExactly @(1, "2", $true, $null, @(3))
                  $actual.GetDepth() | Should -BeExactly 2
                  $actual[4].GetDepth() | Should -BeExactly 1
                  
              }
          }
      
          Context "シナリオテスト" {
              It "JSON変換 → 編集 → JSON化 Objectはじまり" {
                  $value = @"
      {
          "a":1
      }
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual.Add("b", @{"c" = 2; "d" = @(3, 4) })
                  # 結果を標準のJSONオブジェクトに変換
                  $actualStdJson = $actual | ConvertTo-Json -Depth $actual.GetDepth() -Compress | ConvertFrom-Json
                  $expectStdJson = '{"a":1,"b":{"c":2,"d":[3,4]}}' | ConvertFrom-Json
                  $actualStdJson.a        | Should -Be $expectStdJson.a
                  $actualStdJson.b.c      | Should -Be $expectStdJson.b.c
                  $actualStdJson.b.d      | Should -Be $expectStdJson.b.d
                  # 結果を本スクリプトのハッシュテーブルに変換
                  $actualMyJson = $actual | ConvertTo-Json -Depth $actual.GetDepth() -Compress | ConvertFrom-JsonEditable
                  $expectMyJson = '{"a":1,"b":{"c":2,"d":[3,4]}}' | ConvertFrom-JsonEditable
                  $actualMyJson.a         | Should -Be $expectMyJson.a
                  $actualMyJson.b.c       | Should -Be $expectMyJson.b.c
                  $actualMyJson.b.d       | Should -Be $expectMyJson.b.d
              }
              It "JSON変換 → 編集 → JSON化 Arrayはじまり" {
                  $value = @"
      [{
          "a":1
      }]
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual[0].Add("b", @{"c" = 2; "d" = @(3, 4) })
                  $actual.Add(@{"e"="f"})
                  # 結果を標準のJSONオブジェクトに変換
                  $actualStdJson = $actual | ConvertTo-Json -Depth $actual.GetDepth() -Compress | ConvertFrom-Json
                  $expectStdJson = '[{"a":1,"b":{"c":2,"d":[3,4]}},{"e":"f"}]' | ConvertFrom-Json
                  $actualStdJson[0].a         | Should -BeExactly $expectStdJson[0].a
                  $actualStdJson[0].b.c       | Should -BeExactly $expectStdJson[0].b.c
                  $actualStdJson[0].b.d       | Should -BeExactly $expectStdJson[0].b.d
                  $actualStdJson[1].e         | Should -BeExactly $expectStdJson[1].e
                  # 結果を本スクリプトのハッシュテーブ-BeExactly変換
                  $actualMyJson = $actual | ConvertTo-Json -Depth $actual.GetDepth() -Compress | ConvertFrom-JsonEditable
                  $expectMyJson = '[{"a":1,"b":{"c":2,"d":[3,4]}},{"e":"f"}]' | ConvertFrom-JsonEditable
                  $actualMyJson[0].a          | Should -BeExactly $expectMyJson[0].a
                  $actualMyJson[0].b.c        | Should -BeExactly $expectMyJson[0].b.c
                  $actualMyJson[0].b.d        | Should -BeExactly $expectMyJson[0].b.d
                  $actualMyJson[1].e          | Should -BeExactly $expectMyJson[1].e
                  $actualMyJson.GetDepth()    | Should -BeExactly 4
              }
          }
          Context "Unboxing/Unpacking検証" {
              It "要素1のオブジェクト" {
                  
                  $value = @"
      {
          "a":1
      }
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual.a           | Should -BeExactly 1
                  $actual.GetDepth()  | Should -BeExactly 1
              }
              It "要素1のオブジェクト" {
                  
                  $value = @"
      [
          [
              1
          ]
      ]
      "@
                  $actual = ConvertFrom-JsonEditable -Value $value
                  $actual[0][0]       | Should -BeExactly 1
                  $actual.GetDepth()  | Should -BeExactly 2
              }
          }
      }
      

      以上!