AzPolicyLens.Wiki.Utility.Helper.psm1

<#
===============================================================================================
AUTHOR: Tao Yang
DATE: 11/09/2025
NAME: AzPolicyLens.Wiki.Utility.Helper.psm1
VERSION: 2.0.0
COMMENT: this nested module contains the utility helper functions for AzPolicyLens.Wiki module
===============================================================================================
#>


#function to get the current UTC time
function getCurrentUTCString {
  "$([DateTime]::UtcNow.ToString('u')) UTC"
}

#function to clean JSON string by removing null properties
function RemoveNullPropertiesFromJson {
  [CmdletBinding()]
  [OutputType([string])]
  param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [string]$JsonString,

    [Parameter(Mandatory = $false)]
    [switch]$Recurse,

    [Parameter(Mandatory = $false)]
    [switch]$RemoveEmptyArrays,

    [Parameter(Mandatory = $false)]
    [switch]$RemoveEmptyStrings,

    [Parameter(Mandatory = $false)]
    [int]$Depth = 99
  )

  process {
    try {
      # Convert JSON string to PSCustomObject
      $jsonObject = $JsonString | ConvertFrom-Json

      # Remove null properties
      $cleanedObject = RemoveNullProperties -InputObject $jsonObject -Recurse:$Recurse -RemoveEmptyArrays:$RemoveEmptyArrays -RemoveEmptyStrings:$RemoveEmptyStrings

      # Convert back to JSON
      $cleanedJson = $cleanedObject | ConvertTo-Json -Depth $Depth

      return $cleanedJson
    } catch {
      Write-Error "[$(getCurrentUTCString)]: Failed to process JSON: $_"
      return $JsonString
    }
  }
}


#function to remove null properties from a JSON object
function RemoveNullProperties {
  [CmdletBinding()]
  [OutputType([PSCustomObject])]
  param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [PSCustomObject]$InputObject,

    [Parameter(Mandatory = $false)]
    [switch]$Recurse,

    [Parameter(Mandatory = $false)]
    [switch]$RemoveEmptyArrays,

    [Parameter(Mandatory = $false)]
    [switch]$RemoveEmptyStrings
  )

  process {
    $result = [PSCustomObject]@{}

    foreach ($property in $InputObject.PSObject.Properties) {
      $propertyName = $property.Name
      $propertyValue = $property.Value

      # Skip null values
      if ($null -eq $propertyValue) {
        Write-Verbose "[$(getCurrentUTCString)]: Removing null property '$propertyName'" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        continue
      }

      # Skip empty strings if requested
      if ($RemoveEmptyStrings -and $propertyValue -is [string] -and [string]::IsNullOrWhiteSpace($propertyValue)) {
        Write-Verbose "[$(getCurrentUTCString)]: Removing empty string property '$propertyName'" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        continue
      }

      # Skip empty arrays if requested
      if ($RemoveEmptyArrays -and $propertyValue -is [array] -and $propertyValue.Count -eq 0) {
        Write-Verbose "[$(getCurrentUTCString)]: Removing empty array property '$propertyName'" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        continue
      }

      # Handle nested objects recursively if requested
      if ($Recurse -and $propertyValue -is [PSCustomObject]) {
        $cleanedValue = RemoveNullProperties -InputObject $propertyValue -Recurse:$Recurse -RemoveEmptyArrays:$RemoveEmptyArrays -RemoveEmptyStrings:$RemoveEmptyStrings

        # Only add the property if the cleaned object has properties
        if ($cleanedValue.PSObject.Properties.Count -gt 0) {
          Write-Verbose "[$(getCurrentUTCString)]: Adding cleaned object property '$propertyName'" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
          Add-Member -InputObject $result -MemberType NoteProperty -Name $propertyName -Value $cleanedValue -Force
        }
      }
      # Handle hashtables (like OrderedDictionary) recursively if requested
      elseif ($Recurse -and $propertyValue -is [System.Collections.IDictionary]) {
        $cleanedHashtable = [ordered]@{}

        foreach ($key in $propertyValue.Keys) {
          $value = $propertyValue[$key]
          if ($null -ne $value) {
            if ($value -is [PSCustomObject]) {
              $cleanedValue = RemoveNullProperties -InputObject $value -Recurse:$Recurse -RemoveEmptyArrays:$RemoveEmptyArrays -RemoveEmptyStrings:$RemoveEmptyStrings
              if ($cleanedValue.PSObject.Properties.Count -gt 0) {
                $cleanedHashtable[$key] = $cleanedValue
              }
            } else {
              $cleanedHashtable[$key] = $value
            }
          }
        }

        if ($cleanedHashtable.Count -gt 0) {
          Write-Verbose "[$(getCurrentUTCString)]: Adding cleaned hashtable property '$propertyName'" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
          Add-Member -InputObject $result -MemberType NoteProperty -Name $propertyName -Value $cleanedHashtable -Force
        }
      }
      # Handle arrays containing objects recursively if requested
      elseif ($Recurse -and $propertyValue -is [array]) {
        $cleanedArray = @()

        foreach ($item in $propertyValue) {
          if ($item -is [PSCustomObject]) {
            $cleanedItem = RemoveNullProperties -InputObject $item -Recurse:$Recurse -RemoveEmptyArrays:$RemoveEmptyArrays -RemoveEmptyStrings:$RemoveEmptyStrings
            if ($cleanedItem.PSObject.Properties.Count -gt 0) {
              $cleanedArray += $cleanedItem
            }
          } elseif ($null -ne $item) {
            $cleanedArray += $item
          }
        }

        # Only add the array if it's not empty or if we're not removing empty arrays
        if ($cleanedArray.Count -gt 0 -or -not $RemoveEmptyArrays) {
          Write-Verbose "[$(getCurrentUTCString)]: Adding cleaned array property '$propertyName'" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
          Add-Member -InputObject $result -MemberType NoteProperty -Name $propertyName -Value $cleanedArray -Force
        }
      } else {
        # Add the property as-is
        Write-Verbose "[$(getCurrentUTCString)]: Adding property '$propertyName'" -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        Add-Member -InputObject $result -MemberType NoteProperty -Name $propertyName -Value $propertyValue -Force
      }
    }

    return $result
  }
}