jsonpowershellpowershell-7.0

Set property value on object loaded from json containing comments


When loading an object from a json file one can normally set the value on properties and write the file back out like so:

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

But if the source json file contains comments, the object's properties can't be set:

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

Running the code above throws an error:

$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.

When I strip the comments, I can overwrite the property.

Is there a way I can coax PowerShell to ignore the comments while loading the json file/convert the object and generate a writable object?


Solution

  • I'm not sure if this is intended, but seems like ConvertFrom-Json is treating the comments on the Json as $null when converting it to an object. This only happens if it's receiving an object[] from pipeline, with a string or multi-line string it works fine.

    A simple way to demonstrate this using the exact same Json posted in the question:

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

    Here you can see the differences and the workaround, it is definitely recommended to use -Raw on Get-Content so you're passing a multi-line string to 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