ConvertTo-JSON adds Count and Value Property Names

This topic contains 7 replies, has 4 voices, and was last updated by Profile photo of Michael Klement Michael Klement 5 months ago.

  • Author
    Posts
  • #45672
    Profile photo of Michael Maher
    Michael Maher
    Participant

    Hi Folks,

    I have to construct a JSON string containing some basics on my ESXi host and it's VMs.

    $esxihost = 'esxi-01'
    Connect-VIServer $esxihost
    
    Get-VMHost -Name $esxihost | Get-View | 
        Select @{N="host_cpus";E={($_.Hardware.CpuInfo.NumCpuPackages * $_.Hardware.CpuInfo.NumCpuCores)}},
            @{N="host_sn";E={(Get-EsxCli).hardware.platform.get().SerialNumber}},
            @{N="hostname";E={$_.Name}},
            @{N="vms";E={Get-VM |
                Select @{N="memory";E={[string]($_.MemoryMB)}},
                    @{N="name";E={$_.Name }}          
            }}| ConvertTo-Json -Depth 2
    

    This is the object before it's converted to JSON

    $object | GM
    
       TypeName: Selected.VMware.Vim.HostSystem
    
    Name        MemberType   Definition                                             
    ----        ----------   ----------                                             
    Equals      Method       bool Equals(System.Object obj)                         
    GetHashCode Method       int GetHashCode()                                      
    GetType     Method       type GetType()                                         
    ToString    Method       string ToString()                                      
    hostname    NoteProperty System.String hostname=ESXI-01.company.local
    host_cpus   NoteProperty System.Int32 host_cpus=40                              
    host_sn     NoteProperty System.String host_sn=8XXXX2                          
    vms         NoteProperty System.Object[] vms=                                  
    
    $object | fl
    
    host_cpus : 40
    host_sn   : 8XXXXX2
    hostname  : ESXI-01.company.local
    vms       : {@{memory=16384; name=DC-01}, @{memory=8192; name=test-centos}}
    

    Looks ok, but this is the converted JSON

    {
        "host_cpus":  40,
        "host_sn":  "8XXXXX2",
        "hostname":  "ESXI-01.company.local",
        "vms":  {
                    "value":  [
                                  {
                                      "memory":  "16384",
                                      "name":  "DC-01"
                                  },
                                  {
                                      "memory":  "8192",
                                      "name":  "test-centos"
                                  }
                              ],
                    "Count":  2
                }
    }
    

    My problem is the Value and Count properties are being added.

    The format I need to send the JSON doesn't allow these.

    {
        "host_cpus":  40,
        "host_sn":  "8XXXXX2",
        "hostname":  "ESXI-01.company.local",
        "vms":  {
                   [
                                  {
                                      "memory":  "16384",
                                      "name":  "DC-01"
                                  },
                                  {
                                      "memory":  "8192",
                                      "name":  "test-centos"
                                  }
                              ],
                }
    }
    
    • This topic was modified 5 months ago by Profile photo of Michael Maher Michael Maher. Reason: Removed identifying informtion
  • #45677
    Profile photo of Michael Maher
    Michael Maher
    Participant

    I forgot to mention, I found this explanation from Trevor Sullivan but I can't get it to work for me.

    http://stackoverflow.com/questions/20848507/why-does-powershell-give-different-result-in-one-liner-than-two-liner-when-conve

    PowerShell automatically wraps multiple objects into a collection called a PSMemberSet that has a Count property on it. It's basically how PowerShell manages arbitrary arrays of objects. What's happening is that the Count property is getting added to the resulting JSON, yielding the undesirable results that you're seeing.

    You can work around this behavior by referencing the SyncRoot property of the PSMemberSet (which implements the ICollection .NET interface), and passing the value of that property to ConvertTo-Json.

    • This reply was modified 5 months ago by Profile photo of Michael Maher Michael Maher. Reason: formatting
  • #45680
    Profile photo of Don Jones
    Don Jones
    Keymaster

    I suspect asking Trevor might be the best solution. I've pinged him on Twitter, @pcgeek86.

  • #45684
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    What version of PowerShell are you running? With the latest version of WMF5 on Windows 10, I'm not seeing a problem when I tried to reproduce this, but I do remember there being some headaches with the JSON serializer in PSv4:

    $json = @'
    {
        "host_cpus":  40,
        "host_sn":  "8XXXXX2",
        "hostname":  "ESXI-01.company.local",
        "vms":                 [
                                  {
                                      "memory":  "16384",
                                      "name":  "DC-01"
                                  },
                                  {
                                      "memory":  "8192",
                                      "name":  "test-centos"
                                  }
                              ]
                }
    '@
    
    $json | ConvertFrom-Json | ConvertTo-Json -Depth 2
    
    < #
    {
        "host_cpus":  40,
        "host_sn":  "8XXXXX2",
        "hostname":  "ESXI-01.company.local",
        "vms":  [
                    {
                        "memory":  "16384",
                        "name":  "DC-01"
                    },
                    {
                        "memory":  "8192",
                        "name":  "test-centos"
                    }
                ]
    }
    #>
    
  • #45692
    Profile photo of Michael Maher
    Michael Maher
    Participant

    Hi Dave,

    I'm on WMF5 but Windows 7 (5.0.10586.117).

    I will set-up a W12 R2 or W10 system with WMF5 and see if that works.

    Thanks,

    Michael

  • #45862
    Profile photo of Michael Maher
    Michael Maher
    Participant

    Hi Guys,

    Yes hit the issue on Windows 2012 R2 and Windows 10.

    I involved one of my developer friends and he picked up on how to apply Trevor's recommendation to use syncroot. The issue appears when using Select-Object (which you have not run Dave).

    Get-VMHost -Name $esxihost | Get-View | 
        Select @{N="host_cpus";E={($_.Hardware.CpuInfo.NumCpuPackages * $_.Hardware.CpuInfo.NumCpuCores)}},
            @{N="host_sn";E={(Get-EsxCli).hardware.platform.get().SerialNumber}},
            @{N="hostname";E={$_.Name}},
            @{N="vms";E={Get-VM |
                Select @{N="memory";E={[string]($_.MemoryMB)}},
                    @{N="name";E={$_.Name }}                
            }}| % { $_.vms = $_.vms.syncroot; $_ } | ConvertTo-Json -Depth 2
    

    Regards,

    Michael

    • This reply was modified 5 months ago by Profile photo of Michael Maher Michael Maher. Reason: formatting
  • #45869
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    That is just weird. It's certainly a bug in ConvertTo-Json, but so far I haven't spotted the problem in the decompiled code. In any case, I was at least able to reproduce the problem.

  • #45907
    Profile photo of Michael Klement
    Michael Klement
    Participant

    To expand on Michael's answer:

    * The problem is not OS-specific and has been around since PSv3.

    * The problem is not ConvertTo-Json; rather, Select-Object creates the problem when creating array-valued properties via a script block that functions as the expression entry of a hashtable-defined property. In short: the values of such properties are treated as a single object rather than as a collection.
    Here's a quick demonstration that ConvertTo-Json generally does handle array-valued properties properly:

    [pscustomobject] @{ host = 'somehost'; vms = 'vm1', 'vm2' } | ConvertTo-Json

    * The workaround is to force such array properties to behave like a regular collection, for which you can use the SyncRoot property, but it's not necessary; redefining the property with its value evaluated via $(...) is sufficient.

    * I've posted a generic (albeit non-recursive) workaround in this SO answer. Note that the linked answer focuses on a similar bug in ConvertFrom-Json (note: From-Json, not To-Json), which exhibits the same – presumably buggy – behavior when processing a JSON string that is an array. Also note that I believe Trevor's answer there to be incorrect.

You must be logged in to reply to this topic.