PowerShell递归合并JSON对象时数组合并异常排查
PowerShell 递归合并JSON数组缺陷修复方案
现有自定义Merge-Json方法的普通对象属性合并逻辑可正常工作,能正确实现新增属性、覆盖标量属性值的效果;数组处理逻辑存在缺失,遇到数组类型属性时直接整体替换,会触发PowerShell对象类型转换的隐式行为,导致原数组结构被转为普通对象、数组元素内未覆盖的属性丢失,无法实现按索引合并数组元素、保留原有属性的预期。
核心修复方式是在递归判断逻辑中新增数组类型的专属处理分支,对两个数组按索引对齐后逐个递归合并对应元素,长度不一致时自动保留较长侧多出来的元素,避免直接整体替换。修复后的完整实现如下:
function AddPropertyRecurse { param( [PSCustomObject]$Target, [PSCustomObject]$Source ) foreach ($prop in $Source.PSObject.Properties) { # 目标不存在该属性,直接添加 if ($Target.PSObject.Properties.Name -notcontains $prop.Name) { $Target | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value continue } $targetProp = $Target.$($prop.Name) $sourceProp = $prop.Value # 两边都是PSCustomObject,递归合并 if ($targetProp -is [PSCustomObject] -and $sourceProp -is [PSCustomObject]) { AddPropertyRecurse -Target $targetProp -Source $sourceProp continue } # 数组类型专属处理分支:按索引递归合并对应元素 if ($targetProp -is [array] -and $sourceProp -is [array]) { $mergedArray = @() $maxLen = [Math]::Max($targetProp.Count, $sourceProp.Count) for ($i = 0; $i -lt $maxLen; $i++) { # 索引超过原数组长度,直接取待合并数组元素 if ($i -ge $targetProp.Count) { $mergedArray += $sourceProp[$i] continue } # 索引超过待合并数组长度,保留原数组元素 if ($i -ge $sourceProp.Count) { $mergedArray += $targetProp[$i] continue } # 对应位置元素都是对象,递归合并属性 if ($targetProp[$i] -is [PSCustomObject] -and $sourceProp[$i] -is [PSCustomObject]) { $tempObj = [PSCustomObject]@{} # 先拷贝原元素所有属性,避免修改原对象 foreach ($p in $targetProp[$i].PSObject.Properties) { $tempObj | Add-Member -NotePropertyName $p.Name -NotePropertyValue $p.Value } AddPropertyRecurse -Target $tempObj -Source $sourceProp[$i] $mergedArray += $tempObj } else { # 标量值直接用待合并值覆盖 $mergedArray += $sourceProp[$i] } } # 替换原数组为合并后结果 $Target.$($prop.Name) = $mergedArray continue } # 其余类型(标量、类型不匹配场景)直接用待合并值覆盖 $Target.$($prop.Name) = $sourceProp } } function Join-Objects { param( [PSCustomObject]$Target, [PSCustomObject]$Source ) # 先拷贝原目标对象属性,避免修改原始输入对象 $result = [PSCustomObject]@{} foreach ($prop in $Target.PSObject.Properties) { $result | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value } AddPropertyRecurse -Target $result -Source $Source return $result } function Merge-Json { param( [Parameter(Mandatory=$true)] [string]$TargetJsonPath, [Parameter(Mandatory=$true)] [string]$SourceJsonPath ) $target = Get-Content $TargetJsonPath -Raw | ConvertFrom-Json $source = Get-Content $SourceJsonPath -Raw | ConvertFrom-Json $merged = Join-Objects -Target $target -Source $source # 指定足够大的序列化深度,避免默认深度2导致的深层对象转字符串问题 return $merged | ConvertTo-Json -Depth 100 }
测试验证样例
下面是两组测试用JSON文件内容,可直接用于验证合并效果:
基础配置文件 target.json
{ "AppConfig": { "JobServer3Camadas": true, "Version": "1.0.0" }, "DbConfig": { "AppServer": [ { "Name": "App1", "DbType": "SqlServer", "ConnectionString": "Server=old;Database=App1" }, { "Name": "App2", "DbType": "PostgreSql", "ConnectionString": "Server=old;Database=App2" } ], "JobServer": [ { "Name": "Job1", "DbType": "SqlServer", "MaxConcurrency": 5 } ] } }
待合并覆盖配置文件 source.json
{ "AppConfig": { "JobServer3Camadas": false, "Teste": "NewProperty" }, "DbConfig": { "AppServer": [ { "ConnectionString": "Server=new;Database=App1" }, { "ConnectionString": "Server=new;Database=App2" } ], "JobServer": [ { "MaxConcurrency": 10 } ] } }
合并效果说明
调用命令:
Merge-Json -TargetJsonPath .\target.json -SourceJsonPath .\source.json
输出结果完全符合预期:
- AppConfig节点:原有
Version属性保留,JobServer3Camadas值被正确覆盖为false,新增Teste属性 - DbConfig下的AppServer、JobServer数组:
- 数组结构完整保留,不会被转换为普通对象
- 按索引对齐递归合并数组内的对象,原有未被覆盖的属性(如
Name、DbType)全部保留 - 待合并配置中指定的属性(如新的ConnectionString、调整后的MaxConcurrency值)正确覆盖原有值
- 若两个待合并数组长度不一致,较长侧多出来的元素会直接追加到合并结果中,不会丢失
注意:当前数组合并逻辑为按索引位置对齐匹配,如果业务场景需要按数组元素的唯一键(如Name字段)匹配合并而非按索引,仅需修改数组分支内的元素匹配规则即可,整体递归框架无需调整。
内容的提问来源于stack exchange,提问作者Leandro Campos
相关产品推荐
相关产品推荐

