Private/Join-ObjectByProperty.ps1
|
function Join-ObjectByProperty { <# .SYNOPSIS Merges two arrays of objects based on matching key property values. .DESCRIPTION For each object in PrimaryObjects, finds any objects in SecondaryObjects whose SecondaryKey property (supports dotted paths, e.g. "general.id") matches the object's PrimaryKey property value, and merges their properties into the output object. Properties already present on the primary object are not overwritten by matching secondary object properties. Property names listed in ExpandNested are treated as nested objects on the secondary object; rather than being copied as-is, their own child properties are flattened into the output object. If PrimaryObjects is null/empty, or has a Total property equal to 0, SecondaryObjects are returned instead, flattened (and with ExpandNested still applied) but with no merge/matching performed. .PARAMETER PrimaryObjects Array of primary objects to merge from. Properties on these objects take precedence over any matching properties found on merged SecondaryObjects. .PARAMETER SecondaryObjects Array of secondary objects to merge with. Optional. .PARAMETER PrimaryKey Property name on the primary objects used for matching against SecondaryKey. .PARAMETER SecondaryKey Property name (or dotted path, e.g. "general.id") on the secondary objects used for matching against PrimaryKey. .PARAMETER ExpandNested Names of nested properties in secondary objects to expand into the root level, instead of being copied as-is. .EXAMPLE Join-ObjectByProperty -PrimaryObjects $Platforms -SecondaryObjects $PlatformDetails -PrimaryKey 'PlatformID' -SecondaryKey 'general.id' Merges $PlatformDetails into $Platforms, matching $PlatformDetails.general.id to $Platforms.PlatformID. #> [CmdletBinding()] param ( [Parameter(Mandatory, HelpMessage = 'Array of primary objects to merge from.')] [AllowNull()] [object[]] $PrimaryObjects, [Parameter(HelpMessage = 'Array of secondary objects to merge with. Optional.')] [AllowNull()] [object[]] $SecondaryObjects, [Parameter(Mandatory, HelpMessage = 'Property name in primary objects used for matching.')] [ValidateNotNullOrEmpty()] [string] $PrimaryKey, [Parameter(Mandatory, HelpMessage = 'Property name in secondary objects used for matching.')] [ValidateNotNullOrEmpty()] [string] $SecondaryKey, [Parameter(HelpMessage = 'Names of nested properties in secondary objects to expand into the root level.')] [ValidateNotNull()] [string[]] $ExpandNested ) function Get-DottedPropertyValue { param( [object]$InputObject, [string]$Path ) $value = $InputObject foreach ($segment in $Path -split '\.') { if ($null -eq $value) { return $null } $value = $value.$segment } return $value } if (-not $PrimaryObjects -or $PrimaryObjects.Total -eq 0) { $fallbackOutput = [Collections.Generic.List[Object]]::New() foreach ($secondaryObject in $SecondaryObjects) { $flattened = [ordered]@{} foreach ($property in $secondaryObject.PSObject.Properties) { if (-not ($ExpandNested -contains $property.Name)) { $flattened[$property.Name] = $property.Value } } foreach ($nestedPropertyName in $ExpandNested) { $nestedObject = $secondaryObject.$nestedPropertyName if ($nestedObject -and $nestedObject -is [PSCustomObject]) { foreach ($property in $nestedObject.PSObject.Properties) { if (-not $flattened.Contains($property.Name)) { $flattened[$property.Name] = $property.Value } } } } $fallbackOutput.Add([PSCustomObject]$flattened) } return $fallbackOutput } $mergedOutput = [Collections.Generic.List[Object]]::New() foreach ($primaryObject in $PrimaryObjects) { $mergedObject = [ordered]@{} foreach ($property in $primaryObject.PSObject.Properties) { $mergedObject[$property.Name] = $property.Value } $matchingSecondaryObjects = $SecondaryObjects | Where-Object { (Get-DottedPropertyValue -InputObject $_ -Path $SecondaryKey) -eq $primaryObject.$PrimaryKey } if ($matchingSecondaryObjects) { foreach ($secondaryObject in $matchingSecondaryObjects) { foreach ($property in $secondaryObject.PSObject.Properties) { if (-not $mergedObject.Contains($property.Name) -and -not ($ExpandNested -contains $property.Name)) { $mergedObject[$property.Name] = $property.Value } } foreach ($nestedPropertyName in $ExpandNested) { $nestedObject = $secondaryObject.$nestedPropertyName if ($nestedObject -and $nestedObject -is [PSCustomObject]) { foreach ($property in $nestedObject.PSObject.Properties) { if (-not $mergedObject.Contains($property.Name)) { $mergedObject[$property.Name] = $property.Value } } } } } } $mergedOutput.Add([PSCustomObject]$mergedObject) } return $mergedOutput } |