DEV Community

Matthew Hart
Matthew Hart

Posted on • Edited on • Originally published at mattou07.net

Creating complex JSON with Powershell

How do I create a JSON file with Powershell?
How do I create a JSON array with Powershell?
How do I create a JSON List using Powershell?
How can I create a JSON array with a List inside with Powershell and vice-versa?

This post will serve as a reference to show how to make complex JSON structures with Powershell, as things can get messy when you are trying to figure out how to use a list or an Array structure and meld them together to make your desired JSON.

How to write to a JSON file

First the basics, here is how you can write out your data to a JSON file:

$yourData | ConvertTo-Json -Depth 10 | Out-File ".\myJsonFile.json"

What does this mean? First we get our variable $yourData which contains your data whether its a list or a hashtable or a mix of each. We then pipe this variable into ConvertTo-Json, we then need to specify the Depth. By default the depth is 2, depth refers to the number of levels powershell can write into the JSON. If your JSON has a lot of nested arrays and lists, increase the depth to prevent powershell from ignoring deeper elements in your data.

Lastly we pipe out the contents to a JSON file that will be created if it doesn’t exist, using the Out-File command. Adding .\ in front of the file name will create the JSON file in the same directory that the script is running in.

Constructing our JSON

Defining a JSON List

Below is the full code snippet to make a basic list in JSON:

$jsonBase = @{}$list = New-Object System.Collections.ArrayList $list.Add("Foo")
$list.Add("Bar") 
$jsonBase.Add("Data",$list)
$jsonBase | ConvertTo-Json -Depth 10 | Out-File ".\write-list.json"

This produces the following JSON:

{"Data":     [     "Foo",     "Bar"    ]}

How does this work? First to begin our JSON with the curly brackets we need to define a hashtable, we can do this by defining a variable with @{}. What happens if I don't use an empty hashtable at the start? Depending on how big your list is, if there is only one element it will just write in that single element without any square brackets. I circumvent this by nesting the list into a hashtable.

Next we define a list using New-Object System.Collections.ArrayList.

We then add our elements by calling .Add() on the variable defined as a List.

Lastly, we add our List to our Hashtable (curly braces) by using the .Add() method however in hashtables we need to additionally define a key in the first argument .Add(key,object). Which is then piped out to a json file.

Defining a JSON Array

Below is the snippet to make a basic JSON Array:

$jsonBase = @{}

$array = @{}

$data = @{"Name"="Matt";"Colour"="Black";}

$array.Add("Person",$data)

$jsonBase.Add("Data",$array)
$jsonBase | ConvertTo-Json -Depth 10 | Out-File ".\write-array.json"

This produces the following JSON:

{
   "Data":  {
                "Person":  {
                               "Name":  "Matt",
                               "Colour":  "Black"
                           }
            }
}

How does this work? First we define two hashtables one which will start our JSON with curly brackets, the other will be used to define our person object.

Next I create a hashtable containing some dummy data, each entry requires a key and a value, divided by a semi colon. Hence we have “Name”=”Matt”;”Colour”=”Black”;.

I then add the $data into another hashtable with the key of "Person". Which is then added to the $jsonBase hashtable with the key of "Data" which is then piped out to the file.

Combining Lists and Arrays

This next set of examples will combine what we have learned and mix our data structures within the same file.

$jsonBase = @{}
$list = New-Object System.Collections.ArrayList
$list = "apples","pears","oranges","strawberries"
$basket = @{"Basket"=$list;}
$customer = @{"Name"="John";"Surname"="Smith";"OnSubscription"=$true;}

$jsonBase.Add("Data",$basket)
$jsonBase.Add("Customer",$customer)
$jsonBase | ConvertTo-Json -Depth 10 | Out-File ".\basket.json"

This produces the following:

{
   "Customer":  {
                    "OnSubscription":  true,
                    "Name":  "John",
                    "Surname":  "Smith"
                },
   "Data":  {
                "Basket":  [
                               "apples",
                               "pears",
                               "oranges",
                               "strawberries"
                           ]
            }
}

How about lists containing arrays?

Snippet:

$jsonBase = @{}
$list = New-Object System.Collections.ArrayList

$list.Add(@{"Name"="John";"Surname"="Smith";"OnSubscription"=$true;})
$list.Add(@{"Name"="Daniel";"Surname"="Cray";"OnSubscription"=$false;})
$list.Add(@{"Name"="James";"Surname"="Reed";"OnSubscription"=$true;})
$list.Add(@{"Name"="Jack";"Surname"="York";"OnSubscription"=$false;})

$customers = @{"Customers"=$list;}

$jsonBase.Add("Data",$customers)
$jsonBase | ConvertTo-Json -Depth 10 | Out-File ".\customers.json"

JSON:

{
   "Data":  {
                "Customers":  [
                                  {                                       "OnSubscription":  true,                                       "Name":  "John",                                       "Surname":  "Smith"                                   },                                   {                                       "OnSubscription":  false,                                       "Name":  "Daniel",                                       "Surname":  "Cray"                                   },                                   {                                       "OnSubscription":  true,                                       "Name":  "James",                                       "Surname":  "Reed"                                   },                                   {                                       "OnSubscription":  false,                                       "Name":  "Jack",                                       "Surname":  "York"                                   }
                              ]
            }
}

How about something extra?

Below I have consumed some data from a JSON placeholder api and have separated the data based on whether the ID number is even or odd. I then pipe the results to their own JSON files. To make things easier I use the Invoke-RestMethod command to convert the JSON into a PSCustomObject. This allows me to use this notation $i.id instead of $i["id"].

Here is the snippet:

#Use Invoke-RestMethod to convert to PSCustomObject
$data = Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/users"

#Some basic filter logic here
$even = New-Object System.Collections.ArrayList
$odd = New-Object System.Collections.ArrayList
foreach ($i in $data){
    if($i.id % 2 -eq 0) {
        $even.Add($i)
    }
    else {
        $odd.Add($i)
    }

}

$data | ConvertTo-Json -Depth 10 | Out-File ".\api.json"
$even | ConvertTo-Json -Depth 10 | Out-File ".\api-even.json"
$odd | ConvertTo-Json -Depth 10 | Out-File ".\api-odd.json"

To keep things short this is the output for odd numbers:

[
    {
        "id":  1,
        "name":  "Leanne Graham",
        "username":  "Bret",
        "email":  "Sincere@april.biz",
        "address":  {
                        "street":  "Kulas Light",
                        "suite":  "Apt. 556",
                        "city":  "Gwenborough",
                        "zipcode":  "92998-3874",
                        "geo":  {
                                    "lat":  "-37.3159",
                                    "lng":  "81.1496"
                                }
                    },
        "phone":  "1-770-736-8031 x56442",
        "website":  "hildegard.org",
        "company":  {
                        "name":  "Romaguera-Crona",
                        "catchPhrase":  "Multi-layered client-server neural-net",
                        "bs":  "harness real-time e-markets"
                    }
    },
    {
        "id":  3,
        "name":  "Clementine Bauch",
        "username":  "Samantha",
        "email":  "Nathan@yesenia.net",
        "address":  {
                        "street":  "Douglas Extension",
                        "suite":  "Suite 847",
                        "city":  "McKenziehaven",
                        "zipcode":  "59590-4157",
                        "geo":  {
                                    "lat":  "-68.6102",
                                    "lng":  "-47.0653"
                                }
                    },
        "phone":  "1-463-123-4447",
        "website":  "ramiro.info",
        "company":  {
                        "name":  "Romaguera-Jacobson",
                        "catchPhrase":  "Face to face bifurcated interface",
                        "bs":  "e-enable strategic applications"
                    }
    },
    {
        "id":  5,
        "name":  "Chelsey Dietrich",
        "username":  "Kamren",
        "email":  "Lucio_Hettinger@annie.ca",
        "address":  {
                        "street":  "Skiles Walks",
                        "suite":  "Suite 351",
                        "city":  "Roscoeview",
                        "zipcode":  "33263",
                        "geo":  {
                                    "lat":  "-31.8129",
                                    "lng":  "62.5342"
                                }
                    },
        "phone":  "(254)954-1289",
        "website":  "demarco.info",
        "company":  {
                        "name":  "Keebler LLC",
                        "catchPhrase":  "User-centric fault-tolerant solution",
                        "bs":  "revolutionize end-to-end systems"
                    }
    },
    {
        "id":  7,
        "name":  "Kurtis Weissnat",
        "username":  "Elwyn.Skiles",
        "email":  "Telly.Hoeger@billy.biz",
        "address":  {
                        "street":  "Rex Trail",
                        "suite":  "Suite 280",
                        "city":  "Howemouth",
                        "zipcode":  "58804-1099",
                        "geo":  {
                                    "lat":  "24.8918",
                                    "lng":  "21.8984"
                                }
                    },
        "phone":  "210.067.6132",
        "website":  "elvis.io",
        "company":  {
                        "name":  "Johns Group",
                        "catchPhrase":  "Configurable multimedia task-force",
                        "bs":  "generate enterprise e-tailers"
                    }
    },
    {
        "id":  9,
        "name":  "Glenna Reichert",
        "username":  "Delphine",
        "email":  "Chaim_McDermott@dana.io",
        "address":  {
                        "street":  "Dayna Park",
                        "suite":  "Suite 449",
                        "city":  "Bartholomebury",
                        "zipcode":  "76495-3109",
                        "geo":  {
                                    "lat":  "24.6463",
                                    "lng":  "-168.8889"
                                }
                    },
        "phone":  "(775)976-6794 x41206",
        "website":  "conrad.com",
        "company":  {
                        "name":  "Yost and Sons",
                        "catchPhrase":  "Switchable contextually-based project",
                        "bs":  "aggregate real-time technologies"
                    }
    }
]

Thanks for reading!

I hope this helped you in some way, I am currently working on a feature for my IIS Builder script to work at a solution (sln) level rather than at a project (csproj) level. You can read more about it from my post on the Moriyama blog: Automate your local IIS Development Environment.

Some projects can contain multiple web projects in a solution. So far I have seen a solution containing 4 different Umbraco web projects! Having my script 4 times in a project is not ideal so moving the script to work at sln level will hopefully rectify this!

I am currently using these techniques to construct JSON based on any visual studio solution containing a web project. My aim is to have the script read an sln file and identify the web projects in the solution. This JSON can then be used with my IIS Builder script to construct local IIS sites for each web project in the solution. Allowing my colleagues to work more efficiently when facing a new project.

Top comments (2)

Collapse
 
mburszley profile image
Maximilian Burszley • Edited

So PowerShell's *-Json cmdlets will convert any structure that is serializable. As a note, I would advise against using ArrayList and using the generic List<T> for performance (if performance matters, the same can be done for Dictionary<K, V> instead of [hashtable]/@{}). Otherwise using a plain array is fine for small allocations (@()). You can initialize the generic like so:

[System.Collections.Generic.List[T]]@(...)

Although if you want to call a constructor first, I recommend using the built-in one (available in v5+):

$list = [System.Collections.Generic.List[T]]::new()

I'm not sure why, but all of your code examples had their newlines stripped out. If you're not already, I recommend using the triple grave-accent method of defining your code blocks in the editor appended with the language:

\`\`\`powershell
# code here
\`\`\`

(ignore the backslashes, I haven't found how to escape them effectively in the DEV editor)

Collapse
 
mattou07 profile image
Matthew Hart

Thanks for your advice! I will try give those suggestions a go and see where I can include it.

I have fixed the formatting issues, when I grabbed the post from my site it put some lines on the same line and I didn't notice.