为从 json 加载的包含注释的对象设置 属性 值

Set property value on object loaded from json containing comments

从 json 文件加载对象时,通常可以设置属性值并将文件写回,如下所示:

$manifest = (gc $manifestPath) | ConvertFrom-Json -AsHashtable
$manifest.name = "$($manifest.name)-sxs"
$manifest | ConvertTo-Json -depth 100 | Out-File $manifestPath -Encoding utf8NoBOM

但是如果源 json 文件包含注释,则无法设置对象的属性:

// *******************************************************
// GENERATED FILE - DO NOT EDIT DIRECTLY
// *******************************************************
{
  "name": "PublishBuildArtifacts"
}

运行 上面的代码抛出错误:

$manifest

id                 : 1D341BB0-2106-458C-8422-D00BCEA6512A
name               : PublishBuildArtifacts
friendlyName       : ms-resource:loc.friendlyName
description        : ms-resource:loc.description
category           : Build
visibility         : {Build}
author             : Microsoft Corporation
version            : @{Major=0; Minor=1; Patch=71}
demands            : {}
inputs             : {@{name=CopyRoot; type=filePath; label=ms-resource:loc.input.label.CopyRoot; defaultValue=;
                     required=False; helpMarkDown=Root folder to apply copy patterns to.  Empty is the root of the
                     repo.}, @{name=Contents; type=multiLine; label=ms-resource:loc.input.label.Contents;
                     defaultValue=; required=True; helpMarkDown=File or folder paths to include as part of the
                     artifact.}, @{name=ArtifactName; type=string; label=ms-resource:loc.input.label.ArtifactName;
                     defaultValue=; required=True; helpMarkDown=The name of the artifact to create.},
                     @{name=ArtifactType; type=pickList; label=ms-resource:loc.input.label.ArtifactType;
                     defaultValue=; required=True; helpMarkDown=The name of the artifact to create.; options=}…}
instanceNameFormat : Publish Artifact: $(ArtifactName)
execution          : @{PowerShell=; Node=}

$manifest.name
PublishBuildArtifacts

$manifest.name = "sxs"
InvalidOperation: The property 'name' cannot be found on this object. Verify that the property exists and can be set.

当我删除注释时,我可以覆盖 属性。

有什么方法可以让 PowerShell 在加载 json file/convert 对象时忽略注释并生成可写对象?

我不确定这是否有意,但似乎 ConvertFrom-Json 在将 Json 上的注释转换为对象时将其视为 $null只有当它从管道接收到 object[] 时才会发生这种情况,带有 stringmulti-line string效果很好.

使用问题中发布的完全相同的 Json 来演示这一点的简单方法:

$contentAsArray = Get-Content test.json | Where-Object {
    -not $_.StartsWith('/')
} | ConvertFrom-Json -AsHashtable

$contentAsArray['name'] = 'hello' # works

在这里你可以看到差异和解决方法,绝对推荐在 Get-Content 上使用 -Raw 这样你就可以传递一个 multi-line 字符串ConvertFrom-Json:

$contentAsString = Get-Content test.json -Raw | ConvertFrom-Json -AsHashtable
$contentAsArray = Get-Content test.json | ConvertFrom-Json -AsHashtable

$contentAsString.PSObject, $contentAsArray.PSObject | Select-Object TypeNames, BaseObject

TypeNames                                      BaseObject
---------                                      ----------
{System.Collections.Hashtable, System.Object}  {name}
{System.Object[], System.Array, System.Object} {$null, System.Collections.Hashtable}


$contentAsArray['name']      # null
$null -eq $contentAsArray[0] # True
$contentAsArray[1]['name']   # PublishBuildArtifacts
$contentAsArray[1]['name'] = 'hello'
$contentAsArray[1]['name']   # hello

给出了有效的解决方案并分析了症状。让我补充一些 背景信息

tl;dr

  • 您看到了一个已知错误的变体,从 PowerShell 7.2.1 开始仍然存在,其中空行或 single-line 注释作为 first 输入对象意外导致 $null 被发射(第一个)——见 GitHub issue #12229.

  • -RawGet-Content isn't just a workaround, it is the right - and faster - thing to do when piping a file containing JSON to be parsed as whole to ConvertFrom-Json

    结合使用

对于多个输入对象(字符串),ConverFrom-Json有一个(不幸的)启发式内置试图推断 多个字符串是否表示 (a) 单个 JSON 文档的行或 (b) 个单独的 JSON 个文档,每个文档各占一行,如下所示:

  • 如果第一个输入字符串有效JSON本身,则假定(b) ,并且表示已解析的 JSON 的对象([pscustomobject] 或 ,具有 -AsHashtable[hastable] 或两者的数组)为每个输入字符串输出 .

  • 否则,假定(a)并且所有输入字符串都首先被收集,在一个multi-line字符串中,即然后解析。

前面提到的错误是,如果第一个字符串是 empty/blank 行或 single-line 评论[1](同时适用于 // ... 评论,它们 总是 single-line,如果它们 恰好是 single-line,则 /* ... */) , (b) 是(合理地)假设的,但是在解析剩余的 (non-blank, non-comment) 行之前发出了无关的 $null 并且他们的对象表示被输出。

结果总是返回一个 array,它的第一个元素是 $null - 这并不明显,但会导致行为的细微变化,因为你经历过:

值得注意的是,尝试在假定的 单个 对象上设置 属性 会失败,因为正在访问 array 的事实使得 属性 访问 member-access enumeration - implicitly applying property access to all elements of a collection rather than the collection itself - which only works for getting property values - and fails obscurely when setting is attempted - see this answer 的实例以获取详细信息。

一个简化的例子:

# Sample output hashtable parsed from JSON
$manifest = @{ name = 'foo' }

# This is what you (justifiably) THOUGHT you were doing.
$manifest.name = 'bar' # OK

# Due to the bug, this is what you actually attempted.
($null, $manifest).name = 'bar' # !! FAILS - member-access enumeration doesn't support setting.

如上所述,生成的错误消息 - The property 'name' cannot be found on this object. ... - 没有帮助,因为它没有指出问题的真正原因。

改进它会很棘手,但是,因为用户的意图本质上是模棱两可的:属性 名称可能是在情况下不成功的尝试引用不存在的 属性 of集合本身 或者,在这种情况下,根本不支持通过member-access 枚举.

设置属性

可以想象,如果从 PowerShell 的角度来看目标对象是一个集合(可枚举),则以下内容会有所帮助:The property 'name' cannot be found on this collection, and setting a collection's elements' properties by member-access enumeration isn't supported.


[1] 请注意,JSON 中的注释仅在 PowerShell (Core) v6+ 中受支持。