20
votes

I have a json file (test.json) that looks something like this:

{
    "root":
    {
        "key":"value"
    }
}

I'm loading it into powershell using something like this:

PS > $data = [System.String]::Join("", [System.IO.File]::ReadAllLines("test.json")) | ConvertFrom-Json

root
----
@{key=value}

I'd like to be able to enumerate the keys of the 'hashtable' like object that is defined by the json file. So, Ideally, I'd like to be able to do something like:

$data.root.Keys

and get ["key"] back. I can do this with the built-in hashtables in powershell, but doing this with a hashtable loaded from json is less obvious.

In troubleshooting this, I've noticed that the fields returned by ConvertFrom-json have different types than those of Powershell's hashtables. For example, calling .GetType() on a built-in hashtable makes shows that it's of type 'Hashtable':

PS > $h = @{"a"=1;"b"=2;"c"=3}
PS > $h.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Hashtable                                System.Object

doing the same for my json object yields PSCustomObject:

PS > $data.root.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    PSCustomObject                           System.Object

Is there any way to cast or transform this object into a typical powershell Hashtable?

5
I saw this link (stackoverflow.com/questions/3740128/pscustomobject-to-hashtable) which seems relevant, yet didn't quite work for me.brad

5 Answers

16
votes

Here's a quick function to convert a PSObject back into a hashtable (with support for nested objects; intended for use with DSC ConfigurationData, but can be used wherever you need it).

function ConvertPSObjectToHashtable
{
    param (
        [Parameter(ValueFromPipeline)]
        $InputObject
    )

    process
    {
        if ($null -eq $InputObject) { return $null }

        if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
        {
            $collection = @(
                foreach ($object in $InputObject) { ConvertPSObjectToHashtable $object }
            )

            Write-Output -NoEnumerate $collection
        }
        elseif ($InputObject -is [psobject])
        {
            $hash = @{}

            foreach ($property in $InputObject.PSObject.Properties)
            {
                $hash[$property.Name] = ConvertPSObjectToHashtable $property.Value
            }

            $hash
        }
        else
        {
            $InputObject
        }
    }
}
14
votes

The ConvertFrom-Json cmdlet gives you a custom object so you have to access them using dot notation rather than as a subscript. Usually you would know what fields you expect in the JSON so this is actually more useful in general than getting a hash table. Rather than fighting the system by converting back to a hash table, I suggest you work with it.

You can use select with wildcard property names to get at the properties:

PS D:\> $data = @"
{
    "root":
    {
        "key":"value", "key2":"value2", "another":42
    }
}
"@ | ConvertFrom-Json

PS D:\> $data.root | select * | ft -AutoSize

key   key2   another
---   ----   -------
value value2      42



PS D:\> $data.root | select k* | ft -AutoSize

key   key2  
---   ----  
value value2

and Get-Member if you want to extract a list of property names that you can iterate over:

PS D:\> ($data.root | Get-Member -MemberType NoteProperty).Name
another
key
key2

Putting that into a loop gives code like this:

PS D:\> foreach ($k in ($data.root | Get-Member k* -MemberType NoteProperty).Name) {
    Write-Output "$k = $($data.root.$k)"
    }
key = value
key2 = value2
3
votes

The example was for a relatively shallow source object (not nested objects in the properties).

Here's a version that will go 2 levels deep into the source object, and should work with your data:

$data = @{}

foreach ($propL1 in $x.psobject.properties.name)
   {
     $data[$propL1] = @{}
     foreach ($propL2 in $x.$propL1.psobject.properties.name)
        {
          $data[$PropL1][$PropL2] = $x.$propL1.$propL2
        }
    }


$data.root.keys

key
2
votes

I put this together to handle nested json to hashtables

    function ConvertJSONToHash{
    param(
        $root
    )
    $hash = @{}

    $keys = $root | gm -MemberType NoteProperty | select -exp Name

    $keys | %{
        $key=$_
        $obj=$root.$($_)
        if($obj -match "@{")
        {
            $nesthash=ConvertJSONToHash $obj
            $hash.add($key,$nesthash)
        }
        else
        {
           $hash.add($key,$obj)
        }

    }
    return $hash
}

I have only tested with 4 levels but recursive until it has complete hashtable.

0
votes

@Duncan: If you need to use JSON Input for an command expecting a hashmap though (eg SET-ADUSER), try something like this:

function SetADProperties{
    param($PlannedChanges)
    $UserName = $PlannedChanges.Request.User
    $Properties = @{}
    foreach ($key in ($PlannedChanges.SetADProperties | Get-Member -MemberType NoteProperty).Name)
    {
        $Properties[$key] = $PlannedChanges.SetADProperties.$key
    }
    # Call Set-ADUser only once, not in a loop
    Set-ADUser -Identity $UserName -Replace $Properties
}

$content = Get-Content -encoding UTF8 $FileName
$PlannedChanges = $content | ConvertFrom-Json
SetADProperties $PlannedChanges | Write-Output

Example JSON:

{"SetADProperties":{"postalCode":"01234","l":"Duckburg","employeenumber":"012345678"},
"Request":{"Action":"UserMove","User":"WICHKIND","Change":"CH1506-00023"}}