Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding a specific property to each json object in an array in powershell

I have an array of JSON objects and I want to add a particular property to each object present.

For example, the array is as follows:

[
    {
        "Average":  1.3085,
        "ExtendedStatistics":  {

                               },
        "Maximum":  0,
        "Minimum":  0,
        "SampleCount":  0,
        "Sum":  0,
        "Timestamp":  "\/Date(1496972280000)\/",
        "Unit":  {
                     "Value":  "Percent"
                 }
    },
    {
        "Average":  1.4324999999999999,
        "ExtendedStatistics":  {

                               },
        "Maximum":  0,
        "Minimum":  0,
        "SampleCount":  0,
        "Sum":  0,
        "Timestamp":  "\/Date(1496944680000)\/",
        "Unit":  {
                     "Value":  "Percent"
                 }
    }
]

I want to add "source": "CPU" to all objects. How do I go about doing that? I am new to PowerShell and haven't been able to get this done.

like image 620
Arnav Kundra Avatar asked Jun 10 '17 08:06

Arnav Kundra


2 Answers

You could do the following:

$JSON | ConvertFrom-Json | ForEach-Object { 
    $_ | Add-Member -MemberType NoteProperty -Name 'Source' -Value 'CPU' -PassThru
} | ConvertTo-Json

This assumes your JSON input is in a variable named $JSON, you'll need to replace this with however you access your JSON content (e.g Get-Content yourfile.json).

Once you have the JSON, we use ConvertFrom-JSON to convert it to a PowerShell object.

We then use the pipeline to send this to a ForEach-Object loop which uses the Add-Member cmdlet to add a property to each item in the collection (the current item is represented by $_) named 'Source' with a value of 'CPU'. Per the comments from mklement0, it is necessary to use the -PassThru switch to send the result back to the pipeline.

Then we pipe that output to ConvertTo-JSON to convert it back.

like image 51
Mark Wragg Avatar answered Oct 16 '22 05:10

Mark Wragg


Mark Wragg's helpful answer works well, but you may wonder why the Add-Member cmdlet cannot be piped to directly, as opposed to requiring an enclosing ForEach-Object call:

Arguably, the following should work, but in Windows PowerShell doesn't:

# !! Does NOT work as expected in Windows PowerShell.
# OK in PowerShell (Core) 7+ 
$JSON | ConvertFrom-Json | 
  Add-Member -MemberType NoteProperty -Name 'Source' -Value 'CPU' -PassThru 

The idea is that Add-Member uses pipeline input directly, and, after modifying each input object, outputs it, thanks to -PassThru (by default Add-Member produces no output).

The reason that it doesn't work is that when Windows PowerShell's ConvertFrom-Json outputs an array, it outputs it as a single object rather than sending its elements one by one through the pipeline, as one would expect.

  • In PowerShell [Core] 7.0, the behavior was changed to align with the usual enumeration-of-elements behavior, and a -NoEnumerate switch was added as an opt-in to the old behavior. For the discussion that led to this change, see GitHub issue #3424.

Workarounds:

  • Use (...), which forces enumeration of the array:
# Enclosing the ConvertFrom-Json command in (...) forces enumeration.
($JSON | ConvertFrom-Json) | 
  Add-Member -MemberType NoteProperty -Name 'Source' -Value 'CPU' -PassThru 

Note that, generally, using (...) to force collection of a command's entire output in-memory in an array is convenient, but can be problematic with large output sets. As PetSerAl points out, however, in this case it is fine, because ConvertFrom-Json itself constructs the entire output array in memory up front anyway.

  • Alternative: A pass-through call to Write-Output -NoEnumerate (Windows PowerShell) /
    just Write-Output (PowerShell Core), whose sole purpose is to force enumeration of the array elements:
# Inserting Write-Output [-NoEnumerate] between ConvertFrom-Json and Add-Member
# forces enumeration of the array elements.

# *Windows PowerShell*, as of v5.1:
$JSON | ConvertFrom-Json | Write-Output -NoEnumerate |
  Add-Member -MemberType NoteProperty -Name 'Source' -Value 'CPU' -PassThru 

# PowerShell *Core*:
$JSON | ConvertFrom-Json | Write-Output |
  Add-Member -MemberType NoteProperty -Name 'Source' -Value 'CPU' -PassThru 

Optional reading: the quirks of Write-Output:

Windows PowerShell as of v5.1:

Due to a bug, Write-Output invariably enumerates single objects that are collections themselves, even when you add -NoEnumerate.

Paradoxically, -NoEnumerate is actually needed in this case - even though we do want to enumerate! - so as to prevent Write-Output from applying enumeration twice: once (invariably) to the input array, and again to the individual array elements (thanks again, PetSerAl); e.g.:

# !! Unexpectedly returns 4(!): enumerates the outer 2-element array
# !! *and* its elements.
# (In PowerShell *Core*, this yields 2, as expected.)
Write-Output -InputObject (1, 2), (3, 4) | Measure-Object

# BETTER: yields 2, because only the outer 2-element array is enumerated
# (In PowerShell *Core*, this yields 1, as expected.)
Write-Output -NoEnumerate -InputObject (1, 2), (3, 4) | Measure-Object

PowerShell [Core] v6.2.3+:

The above problem has been fixed, which means that - sensibly - you mustn't use -NoEnumerate if you do want Write-Output to enumerate pipeline objects that are themselves collections (and enumeration no longer recurses 1 level).

like image 43
mklement0 Avatar answered Oct 16 '22 04:10

mklement0