Converting JSON to a hashtable
Table of Contents
What’s the problem?
The ConvertFrom-Json and ConvertTo-Json cmdlets are great. JSON is a pretty common standard that isn’t going anywhere, and the ability to work with JSON data as native PowerShell objects provides first-class support for this format in PowerShell. It’s far simpler for PS to use JSON than to use XML.
However, there are times when the PSCustomObject returned by ConvertFrom-Json is less than ideal, and it would be more helpful to use a hashtable instead:
Strict mode and missing properties
When using Strict mode, checking whether the JSON contained a key uses this fairly tedious snippet:
Hashtables make this far easier:
Extending and combining objects
PSCustomObjects have to be extended using Add-Member:
Hashtables can have additional properties added implicitly, even if the key doesn’t exist:
Hashtables can also be combined easily:
Is there a convenient way for us to create a hashtable from JSON?
That’s a Wrap
Actually, it turns out that this is a pretty trivial process with the lesser-known JavaScriptSerializer class. (I believe this was added in .NET 4.5, but if I’m incorrect, feel free to let me know.) This assembly isn’t loaded by default in a PowerShell session, but thanks to Add-Type
, loading an assembly is trivial (notice that the MSDN page tells us exactly what DLL file to reference). Thanks to Kevin Marquette’s article on hashtables for pointing me in this direction!
This is only half of the problem, though. We could write a function to wrap this class, but then any code where we need to reference our JSON-to-hashtable logic would need to include or reference that function. We’d need to do a find/replace to change all references of ConvertFrom-Json
to our custom function name.
Instead, PowerShell’s command precedence allows us to define a custom function that “overrides” a built-in cmdlet. PowerShell resolves commands in this order:
- Aliases
- Functions
- Native cmdlets
- Binaries (anything in the PATH variable)
Because of this, we can create a custom function and name it ConvertFrom-Json
, and it will take precedence over the native cmdlet (a function takes precedence over a cmdlet). This is the same reason typing dir
in PowerShell resolves to an alias to Get-ChildItem
before it resolves to dir.exe
- an alias takes precedence over a binary. This means when we load a custom function called ConvertFrom-Json
, it will magically be used by default anywhere we call ConvertFrom-Json
in that PowerShell session.
You can create a wrapper function yourself line-by-line if you’d like, but I found Jeff Hicks’ Copy-Command function gave me a pretty good starting point:
The -UseForwardHelp
switch generates some metadata that points Get-Help ConvertFrom-Json
to the cmdlet itself, rather than our function. This means we don’t need to mess with comment-based help here. (We don’t have help for the new parameter, -As
, but that’s what this article is for, right?)
The -IncludeDynamic
switch causes the Copy-Command
function to copy any dynamic parameter blocks from the cmdlet to our new wrapper function. As it turns out, this doesn’t matter in this case, but it doesn’t hurt - and it’s helpful if the cmdlet you wrap does include dynamic parameters.
The Code
Here’s the full code for my custom wrapper function around ConvertFrom-Json
:
Example Usage
Notice that the default behavior doesn’t change - if we don’t specify what we want to do with the JSON, it will be converted to an object as usual. This prevents any disruption to code that uses the built-in cmdlet and expects an object in response.
Conclusion
It might be worthwhile to wrap the JSON.NET library rather than the .NET class I referenced, since even MSDN has a note that serialization should be done with JSON.NET rather than that class. Since that would introduce a dependent library, though, it would be a candidate for a more full-featured module instead of a quick wrapper function. In fact, PowerShell Core seems to be referencing this library already!
Finally, if you plan to use this wrapper in code that gets pushed anywhere other than your local machine, it would be prudent to include the wrapper function as a private function in your module. That way, you don’t have to assume that the other system has this function available in its PowerShell profile.
I hope this is helpful!