使用 Powershell 查找和替换嵌套的 JSON 值

3

我有一个 appsettings.json 文件,希望能够通过 VSTS 发布管道中的 PowerShell 任务进行转换。(顺便说一下,我正在将 netstandard 2 Api 部署到 IIS)。JSON 的结构如下:

{
    "Foo": {
        "BaseUrl": "http://foo.url.com",
        "UrlKey": "12345"
    },
    "Bar": {
        "BaseUrl": "http://bar.url.com"
    },
    "Blee": {
        "BaseUrl": "http://blee.url.com"
    }
}

我想替换每个部分中的BaseUrl和(如果存在)UrlKey值,这些部分是Foo、Bar和Blee。(例如:Foo:BaseUrl、Foo:UrlKey、Bar:BaseUrl等)

我使用以下JSON结构来保存新值:

{ 
    "##{FooUrl}":"$(FooUrl)", 
    "##{FooUrlKey}":"$(FooUrlKey)",
    "##{BarUrl}":"$(BarUrl)",
    "##{BleeUrl}":"$(BleeUrl)"
}

到目前为止,我有以下脚本:

# Get file path
$filePath = "C:\mywebsite\appsettings.json"

# Parse JSON object from string
$jsonString = "$(MyReplacementVariablesJson)"
$jsonObject = ConvertFrom-Json $jsonString

# Convert JSON replacement variables object to HashTable
$hashTable = @{}
foreach ($property in $jsonObject.PSObject.Properties) {
    $hashTable[$property.Name] = $property.Value
}

# Here's where I need some help

# Perform variable replacements
foreach ($key in $hashTable.Keys) {
    $sourceFile = Get-Content $filePath
    $sourceFile -replace $key, $hashTable[$key] | Set-Content $filePath
    Write-Host 'Replaced key' $key 'with value' $hashTable[$key] 'in' $filePath
}

1
https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens - gvee
4个回答

3

为什么要将替换值定义为JSON字符串呢?这只会让你的生活更加痛苦。如果你已经在脚本中定义了这些值,那就直接将它们定义为哈希表吧:

$newUrls = @{
    'Foo'  = 'http://newfoo.example.com'
    'Bar'  = 'http://newbaz.example.com'
    'Blee' = 'http://newblee.example.com'
}

$newKeys = @{
    'Foo' = '67890'
}

即使您想从文件中读取它们,也可以将该文件制作为包含这些哈希表的PowerShell脚本,并对其进行点源操作。或者至少将这些值定义为文本文件中的key=value行列表,这些列表可以轻松转换为哈希表:

$newUrls = Get-Content 'new_urls.txt' | Out-String | ConvertFrom-StringData
$newKeys = Get-Content 'new_keys.txt' | Out-String | ConvertFrom-StringData

然后遍历您输入的JSON数据的顶层属性,并用新值替换嵌套属性:

$json = Get-Content $filePath | Out-String | ConvertFrom-Json
foreach ($name in $json.PSObject.Properties) {
    $json.$name.BaseUrl = $newUrls[$name]
    if ($newKeys.ContainsKey($name)) {
        $json.$name.UrlKey = $newKeys[$name]
    }
}
$json | ConvertTo-Json | Set-Content $filePath

请注意,如果您的实际 JSON 数据具有超过 2 层的层次结构,则需要通过参数 -Depth 告诉ConvertTo-Json 应该转换多少层级。
另外说明:需要通过 Out-StringGet-Content 输出传输,因为 ConvertFrom-Json希望将 JSON 输入作为单个字符串,并且使用 Out-String 可以使代码与所有 PowerShell 版本兼容。如果您使用 PowerShell v3 或更新版本,则可以通过替换 Get-Content | Out-StringGet-Content -Raw 来简化代码。

我收到了“在此对象上找不到属性'BaseUrl'。请验证该属性是否存在并且可以设置。”的错误信息。如何检查确保名称具有BaseUrl属性?例如,Foo具有BaseUrl但Baz没有。 - TrevorBrooks
对于这样的情况,您需要枚举 $json.$name 的属性。 - Ansgar Wiechers

1
感谢您,Ansgar,提供了详细的答案,对我有很大帮助。最终,在无法迭代输入JSON数据的顶级属性时,我采用了以下代码:
$json = (Get-Content -Path $filePath) | ConvertFrom-Json

    $json.Foo.BaseUrl = $newUrls["Foo"]
    $json.Bar.BaseUrl = $newUrls["Bar"]
    $json.Blee.BaseUrl = $newUrls["Blee"]

    $json.Foo.Key = $newKeys["Foo"]

$json | ConvertTo-Json | Set-Content $filePath

我希望这能帮助其他人。


1

要在json/config文件中更新不同深度的键值,您可以使用"."在级别之间传递键名,例如AppSettings.Setting.Third表示:

{
    AppSettings = {
        Setting = {
            Third = "value I want to update"
        }
    }
}

要为多个设置项设置值,您可以像这样操作:

$file = "c:\temp\appSettings.json"

# define keys and values in hash table
$settings = @{
    "AppSettings.SettingOne" = "1st value"
    "AppSettings.SettingTwo" = "2nd value"
    "AppSettings.SettingThree" = "3rd value"
    "AppSettings.SettingThree.A" = "A under 3rd"
    "AppSettings.SettingThree.B" = "B under 3rd"
    "AppSettings.SettingThree.B.X" = "Z under B under 3rd"
    "AppSettings.SettingThree.B.Y" = "Y under B under 3rd"
}

# read config file
$data = Get-Content $file -Raw | ConvertFrom-Json

# loop through settings
$settings.GetEnumerator() | ForEach-Object {
    $key = $_.Key
    $value = $_.Value

    $command = "`$data.$key = $value"
    Write-Verbose $command

    # update value of object property
    Invoke-Expression -Command $command
}
        
$data | ConvertTo-Json -Depth 10 | Out-File $file -Encoding "UTF8"

1
这个方法可以深度克隆一个哈希表,但会覆盖你在另一个对象中提供的键。它使用递归来处理嵌套。如果你使用的是PS7,ConvertFrom-Json -AsHashtable 是很有帮助的,但如果像我一样被困在PS5上,你可以看看我的另一个答案将PSObject转换为哈希表
到目前为止,我遇到的唯一缺点是我没有一个好的方法来更新数组中的键,但我想无论如何都分享给大家,如果我找不到解决方案或者无法解决这个问题。
我不喜欢使用数字索引,比如foo.0.bar,因为如果有人在数组开头插入另一个对象,那么这个语句会更新错误的值,所以我理想的解决方案是在对象内部进行另一个键的查找。
<#
.SYNOPSIS

Given two hashtables this function overrides values of the first using the second.

.NOTES

This function is based on Kevin Marquette's Get-DeepClone
function as documented below.
https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-hashtable?view=powershell-7.3#deep-copies

.EXAMPLE
# Use nested hashtable to apply updates.

$Settings = @{
    foo = "foo"
    bar = "bar"
    one = @{
        two = "three"
    }
}

$Updates = @{
    foo = 'fubar'
    one = @{
        two = 'four'
    }
}

$Clone = Update-Hashtable $Settings $Updates
$Clone

.EXAMPLE

# Use flattened hashtable to apply updates.

$Settings = @{
    foo = "foo"
    bar = "bar"
    one = @{
        two = "three"
    }
}

$StringData = @"
foo=fubar
one.two=five
"@
$Updates = $StringData | ConvertFrom-StringData

$Clone = Update-Hashtable $Settings $Updates -UpdatesAreFlattened
$Clone
#>
function Update-Hashtable {
    [CmdletBinding()]
    param(
        [Object] $InputObject,
        [Object] $Updates = @{},
        [Switch] $UpdatesAreFlattened,
        
        # This parameter is used to keep track of our position
        # in a nested object during recursion.
        [Parameter(DontShow)]
        [String] $Keychain
    )
    process {
        if ($InputObject -is [Hashtable]) {
            $Clone = @{}
            
            foreach ($Key in $InputObject.Keys) {
                # Track our nested level by appending keys.
                $Keychain = if ($KeyChain) { "$Keychain.$Key" } else { $Key }

                # Because flattened updates don't keep track our nested level, use the
                # keychain to index it instead of the current key.
                $UpdateKey = if ($UpdatesAreFlattened) { $Keychain } else { $Key }
                $UpdateValue = $Updates.$UpdateKey

                if ($Updates -and $UpdateValue) {
                    $Clone.$Key = $UpdateValue
                } else {
                    # Unflattened updates provide the nested object while flattened updates are single
                    # level so pass the full object
                    $ForwardUpdates = if ($UpdatesAreFlattened) { $Updates } else { $Updates.$Key }
                    $Clone.$Key = Update-Hashtable $InputObject.$Key $ForwardUpdates $Keychain -UpdatesAreFlattened:$UpdatesAreFlattened
                }
                $KeyChain = $null  # Reset the chain.
            }
            return $Clone
        } else {
            return $InputObject
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接