Saturday, August 15, 2020

PowerShell: Converting Json-Formatted Strings to/from Json-Objects and Json-Object Depth

PowerShell has extremely versatile cmdlets for converting Json-formatted strings to Json-objects (ConvertFrom-Json) and converting Json-objects to Json-formatted strings (ConvertTo-Json). A real world case where such cmdlets come in handy is when working with Azure Resource Manger (ARM) templates (JSON formatted files) in order to create virtual machines from standard SKU's and from images. The ConvertTo-Json cmdlet has a default behavior that needs to be accounted for when using it in real world applications. There are too many online blogs that fail to demonstrate that by default ConvertTo-Json only works on Jason objects with a maximum depth of 2. This blog post shows the correct use of ConvertTo-Json. 

The PowerShell cmdlet that converts a Json-formatted string to a Json object or, when the -AsHashtable command-line option is specified, to a HashTable, is ConvertFrom-Json:

The PowerShell cmdlet that converts an object to a Json-formatted string is ConvertTo-Json:

The CovertFrom-Json cmdlet has a -Depth command-line option that defaults to a value of 1024 while the ConvertTo-Json cmdlet has a -Depth command-line option that defaults to a value of two. The depth of a Json object is defined as the maximum nested level of each property (starting with a { bracket and ending in a } bracket) and each nested level of an array (starting with a [ bracket and ending in a ] bracket).

Consider a case where a Json-formatted string with a depth of nine is converted to Json-object. There would be no need to set the -Depth parameter when invoking CovertFrom-Json as the default depth is 1024. If the Json-object is edited and then converted to a string using the CovertTo-Json cmdlet, there would be a problem unless the -Depth command-line option was set to at least a value of nine (as computed by the Get-Depth method below). Since CovertTo-Json default -Depth defaults to two, then the Json-formatted string would be malformed since the Json-object converted has a depth of nine.

The Get-Depth function takes as a parameter a Json-formatted string and returns the Json's depth:

function Get-Depth()
{
  param (
    [Parameter(Mandatory=$true)]
    [string] $json
  )

  # This step verifies that $json is a valid Json object
  $jsonObject = ConvertFrom-Json $json
  if ($null -eq $jsonObject)
  {
    return 0
  }

  [int] $maximumDepth = -1
  [int] $depth = 0
  [char[]] $startingBrackets = '[', '{'
  [char[]] $endingBrackets = @(']', '}')

  foreach ($c in $json.ToCharArray())
  {
    if ($c -in $startingBrackets)
    {
      ++$depth
      $maximumDepth = if ($maximumDepth -ge $depth)
        { $maximumDepth } else { $depth }
    }

    elseif ($c -in $endingBrackets)
    {
      --$depth
    }
  }

  return $maximumDepth
}

The following snippet of PowerShell shows CovertFrom-Json being used to convert a Json-formatted string to a Json-object. ConvertTo-Json is used with the -Depth command-line parameter assigned using a value return by the Get-Depth function and ConvertTo-Json is used without the -Depth command-line parameter assigned:

[string] $content = <getting content shown in future blog>
[int] $jsonDepth = Get-Depth $content
[PSCustomObject] $jsonObject = $content | ConvertFrom-Json
[string] $updatedContent = $jsonObject |
  ConvertTo-Json -Depth $jsonDepth
[string] $updatedContentDefaultDepth = $jsonObject | ConvertTo-Json

Below is an image of the Json-formatted string created by ConvertTo-Json using the default value for -Depth, two: 


Note that the properties hardwareProfile, storageProfile, networkProfile, osProfile, and diagnosticsProfile are unassigned as these properties exceed the default depth.

Below is an image of the Json-formatted string created by ConvertTo-Json using the -Deptth determined by the Get-Depth function and notice that hardwareProfile, storageProfile, networkProfile, osProfile, and diagnosticsProfile are all unassigned values:



No comments :

Post a Comment