Powershell Add-Member - But without “Value” and “Count” elements in JSON

喜夏-厌秋 提交于 2020-01-01 19:14:14

问题


I am successfully adding a member to my JSON, but I end up with unwanted elements. What I am trying to add is the element inside "Value" that is appearing in the resulting JSON.

{
"Block1": value1,
"Block2": value2,
"Block3": []
}

Then doing the Add-Member cmdlet.

$objectFromJson |
  Add-Member -NotePropertyName "Block3" -NotePropertyValue $newblock -Force

I realize I do not have to do the -Force part, but in my working code, my JSON string is parsed to an object using ConvertFrom-Json and that part is effective for my purposes.

There are from 1 to N elements in the array stored in $newblock, to be serialized as array-valued property Block3.

Unfortunately, I end up with the following:

{
"Block1": value1,
"Block2": value2,
"Block3": [ { "value": { <elements of $newblock> }, "Count": <n> } ]
}

In the snippet above, <elements of $newblock> represent the JSON representation of the $newblock array elements, and <n> the count of elements in the array.

It is valid JSON, but not what I desire. Instead, I want the elements of $newblock to be direct elements of the Block3 array, without the extraneous wrapper object with the value and Count properties:

{
"Block1": value1,
"Block2": value2,
"Block3": [ <elements of $newblock> ]
}

回答1:


The solution to my issue was as follows (pseudo code):

$json = @"
{
"Block1": value1,
"Block2": value2,
"Block3": []
}
"@

$objFromJson = $json | ConvertFrom-Json

$listCount = ($newblock.ToCharArray() | Where-Object ($_ -eq ";" | Measure-Object).Count + 1

for($i = 0; $i -lt $listCount; $i++) {
  $newJson = @{ element1 = value1; element2 = value2; etc. }
  $objFromJson.Block3 += $newJson
}

$objFromJson | ConvertTo-Json

The part I had issues with was that Add-Member was the wrong approach, since it adds a PSObject or PSCustomObject under the covers.

Thanks to @mklement0 for pointing me to the root cause of my question.




回答2:


tl;dr

  • Your own solution avoids the original problem, and is arguably the better approach to begin with: Create the .Block3 property as an array via the original JSON (rather than later via Add-Member), and add elements to that array later with +=.

  • However, you could have fixed the original problem by simply (but obscurely) passing
    -NotePropertyValue $newblock.psobject.BaseObject instead of
    -NotePropertyValue $newblock, which removes the invisible [psobject] wrapper around the array stored in $newblock that caused the problem. Read on for an explanation.


What matters in the end is: the array stored in the .Block3 property must not have an invisible [psobject] wrapper, because in Windows PowerShell that causes the array to serialize to JSON wrapped in an extra object with "Count" and "values" properties.

The extra object stem from the presence of an obsolete ETS (extended-type system) property named .Count for arrays, which takes effect for [psobject]-wrapped arrays - see this answer for the gory details.

The problem no longer surfaces in PowerShell [Core] v6+, because this ETS property has been removed there.

Add-Member was the wrong approach, since it adds a PSObject or PSCustomObject under the covers.

Actually, Add-Member by itself does not do that, because the -NotePropertyValue parameter is [object]-typed, not [psobject]-typed.

The array stored in your $newblock variable must already have been [psobject] wrapped:
$newblock -is [psobject] probably indicates $true for you, whereas a regular array does not (e.g., 1, 2 -is [psobject] is $false)

For instance, an array returned from a cmdlet, as a whole will have an invisible [psobject] wrapper, notably when you use the New-Object cmdlet:
(New-Object string[] 2) -is [psobject] returns $true

See this GitHub issue for all scenarios in which this invisible extra [psobject] wrapper is added, which can cause other subtle behavioral differences as well, which still affect PowerShell [Core] as of v7.0 (but, as stated, this particular issue has been fixed by the removal of the ETS property).


There are two general workarounds:

  • Session-wide:

    • Before calling ConvertTo-Json, run the following command, which removes the obsolete ETS property, after which arrays serialize as expected - whether [psobject]-wrapped or not:
      Remove-TypeData System.Array
  • For a given array variable:

    • Use .psobject.BaseObject to access the array's unwrapped, underlying .NET array; in your case: $newblock.psobject.BaseObject

Examples:

Session-wide workaround:

# The problem: Serialize a [psobject]-wrapped array (0, 0):
PS> ConvertTo-Json -InputObject (New-Object int[] 2)
# Note the extra object with the "count" (element count) and "value" property (elements)
{
    "value":  [
                  0,
                  0
              ],
    "Count":  2
}

# Remove the ETS definitions for System.Array
Remove-TypeData System.Array

# Rerun the command:
PS> ConvertTo-Json -InputObject (New-Object int[] 2)
# OK
[
    0,
    0
]

Workaround for a given array variable:

PS> $arr = New-Object int[] 2; ConvertTo-Json -InputObject $arr
# Note the extra object with the "count" (element count) and "value" property (elements)
{
    "value":  [
                  0,
                  0
              ],
    "Count":  2
}

# $arr.psobject.BaseObject bypasses the [psobject] wrapper
PS> ConvertTo-Json -InputObject $arr.psobject.BaseObject
# OK
[
    0,
    0
]


来源:https://stackoverflow.com/questions/57576341/powershell-add-member-but-without-value-and-count-elements-in-json

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!