7

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.

0

2 Answers 2

10

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.

Sign up to request clarification or add additional context in comments.

Comments

6

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 - see GitHub issue #5955 - 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).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.