为从 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[]
时才会发生这种情况,带有 string 或 multi-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.
将 -Raw
与 Get-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+ 中受支持。
从 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[]
时才会发生这种情况,带有 string 或 multi-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.将
结合使用-Raw
与Get-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 toConvertFrom-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+ 中受支持。