There are plenty of examples of removing null and empty/whitespace keys from arrays, hashtables, and PSCustomObjects. I've adapted and merged several functions I found on this site to come up with this:
Function Remove-NullAndEmptyProperties {
[CmdletBinding()]
Param(
# Object from which to remove the null values.
[Parameter(ValueFromPipeline,Mandatory,Position=0)]
$InputObject,
# Instead of also removing values that are empty strings, include them
# in the output.
[Switch]$LeaveEmptyStrings,
# Additional entries to remove, which are either present in the
# properties list as an object or as a string representation of the
# object.
# I.e. $item.ToString().
[Object[]]$AlsoRemove = @()
)
begin {
$IsNonRefType = {
param (
[AllowNull()]
$Val
)
return (($null -ne $Val) -and (-not($Val.GetType().IsValueType)))
}
}
Process {
try {
$IsValidType = (& $IsNonRefType -Val $InputObject)
if(-not$IsValidType){
Write-Error "Input object is an invalid type."
return
}
if($InputObject.GetType().name -eq 'Hashtable'){
$TmpString = $InputObject | ConvertTo-Json -Depth 15
$TmpString = $TmpString -replace '"\w+?"\s*:\s*null,?'
if(!$LeaveEmptyStrings){
$TmpString = $TmpString -replace '"\w+?"\s*:\s*"\s+",?'
}
$NewHash = $TmpString | ConvertFrom-Json
$NewHash
} else {
# Iterate InputObject in case input was passed as an array
ForEach ($obj in $InputObject) {
$obj | Select-Object -Property (
$obj.PSObject.Properties.Name | Where-Object {
-not (
# If prop is null, remove it
$null -eq $obj.$_ -or
# If -LeaveEmptyStrings is not specified and the property
# is an empty string, remove it
(-not $LeaveEmptyStrings.IsPresent -and
[string]::IsNullOrWhiteSpace($obj.$_)) -or
# If AlsoRemove contains the property, remove it
$AlsoRemove.Contains($obj.$_) -or
# If AlsoRemove contains the string representation of
# the property, remove it
$AlsoRemove.Contains($obj.$_.ToString())
)
}
)
}
}
}
catch {
Write-Output "A terminating error occured: $($PSItem.ToString())"
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
}
These examples work fine:
$Hash = @{
"Key1" = 'Keyboard'
"Key2" = ' '
"Key3" = 'Mouse'
"Key4" = $null
"Key5" = 'Computer'
}
$Hash | Remove-NullAndEmptyProperties
$PSC = [PSCustomObject]@{
KeyA = " "
KeyB = "Windows"
KeyC = "Applications"
KeyD = $null
KeyE = ""
KeyF = "Documents"
}
$PSC | Remove-NullAndEmptyProperties
But if I try this:
$OHash = [ordered]@{
"Key1" = 'Alpha'
"Key2" = ' '
"Key3" = 'Beta'
"Key4" = $null
"Key5" = 'Omega'
}
$OHash | Remove-NullAndEmptyProperties
I'm greeted with this:
How can I iterate over an Ordered Dictionary and remove the $null
and whitespace keys/values?
I'd like this function to be as robust as possible.
Any solutions are greatly welcomed!
The way I would handle null or whitespace values in an OrderedDictionary
is by enumerating the key / value pairs and using [string]::IsNullOrWhiteSpace
to skip them:
$OHash = [ordered]@{
'Key1' = 'Alpha'
'Key2' = ''
'Key3' = 'Beta'
'Key4' = $null
'Key5' = 'Omega'
}
# toggle for testing
[Switch] $LeaveEmptyStrings = $true
# can use `-is [System.Collections.IDictionary]` here
# to target any type implementing the interface (i.e.: hashtable)
if ($OHash -is [System.Collections.Specialized.OrderedDictionary]) {
$newDict = [ordered]@{}
foreach ($pair in $OHash.GetEnumerator()) {
if (-not $LeaveEmptyStrings.IsPresent -and [string]::IsNullOrWhiteSpace($pair.Value)) {
continue
}
elseif ($null -eq $pair.Value) {
continue
}
$newDict[$pair.Key] = $pair.Value
}
$newDict
}
The logic above attempts to create a copy of the input dictionary, it does not attempt to mutate it, however if the keys could have reference values that need to be dereferenced then you will need to serialize it before making the copy. ConvertTo-Json
and ConvertFrom-Json
is a good option.
As aside, this logic can also be re-used in the PSCustomObject
code path with very small variations in the code, you can use an OrderedDictionary
to construct the new object then cast the [pscustomobject]
accelerator:
$PSC = [PSCustomObject]@{
KeyA = ' '
KeyB = 'Windows'
KeyC = 'Applications'
KeyD = $null
KeyE = ''
KeyF = 'Documents'
}
[switch] $LeaveEmptyStrings = $true
if ($PSC -is [System.Management.Automation.PSCustomObject]) {
$newObject = [ordered]@{}
foreach ($property in $PSC.PSObject.Properties) {
if (-not $LeaveEmptyStrings.IsPresent -and [string]::IsNullOrWhiteSpace($property.Value)) {
continue
}
elseif ($null -eq $property.Value) {
continue
}
$newObject[$property.Name] = $property.Value
}
[pscustomobject] $newObject
}