0

I created powershell script which request API and get response but many times I getting null value without double quotes in JSON object which created error in other script.

How can I put double quotes where getting null value.

Response :

{ "Name" : { 
             "Tag1" : "server1",
             "Tag2" : null,
             "Tag3" : "web"
            }
}

Here, I expecting "Tag2" : "null" ( need double quotes where get null response)

Context : I'm fetching tag from azure vm, if that particular tag is not available then itz return null as expected.

Ex.
 $webvmdata = @{
 Vmtype = $wenvm.Tags.vmtype
  }
  $webvmdata | ConvertTo-Json

How can we easily handle this using powershell.

Thanks in advance.

Nullpointer
  • 1,895
  • 20
  • 26
  • 2
    Don't use other other people's avatars. Especially not this one. – Tomalak Mar 23 '22 at 14:01
  • 3
    *"expecting "Tag2" : "null" (need double quotes where get null response)"* - No, you don't. This case is already easily handled in PowerShell by default. You're doing something wrong. Show more context. – Tomalak Mar 23 '22 at 14:04
  • @Tomalak i added powershell code – Nullpointer Mar 23 '22 at 14:21
  • 1
    So you really want `null` - which translates into `$null` when parsed in PowerShell objects with `ConvertFrom-Json` (which is built into `Invoke-RestMethod`) - to be replaced with the _string_ `"null"`? – mklement0 Mar 23 '22 at 15:51
  • @mklement0 First, Without Double quotes null value which getting errors in python which loading. Getting "malformed string" error. So I need double quotes null value. Even used ConvertFrom-Json where also getting without double quotes for null. – Nullpointer Mar 23 '22 at 16:29

1 Answers1

1

Note:

  • Generally speaking, null is a legitimate JSON value that becomes $null when PowerShell parses JSON into custom objects with its ConvertFrom-Json cmdlet, which, in effect, is also built into Invoke-RestMethod.

If you really need to transform the null values into "null" values - i.e. strings - in your JSON, the most robust - but not fast - approach is to:

  • Let ConvertFrom-Json parse your JSON into custom objects...
  • ... then walk the internal structure of these objects to look for $null property values and replace them with string 'null'...
    • See the bottom section for generalized helper function Edit-LeafProperty that encapsulates this behavior.
  • ... and convert the modified objects back to JSON.
# Parse JSON into custom object(s).
$fromJson = ConvertFrom-Json @'
{
  "Name": {
    "Tag1": "server1",
    "Tag2": null,
    "Tag3": "web"
  }
}
'@

# Transform the object(s) in place.
$fromJson | ForEach-Object {
  # Helper script block that walks the object graph
  $sb = {
    foreach ($el in @($args[0])) {
      if ($el -is [System.Collections.IEnumerable]) { # Nested array
        foreach ($subEl in $el) { & $sb $subEl } # Recurse
      }
      elseif ($el -is [System.Management.Automation.PSCustomObject]) {
        foreach ($prop in $el.psobject.Properties) { 
          # If the property value is $null, replace it with string 'null'
          if ($null -eq $prop.Value) { $prop.Value = 'null' }
          elseif ($prop.Value -is [System.Management.Automation.PSCustomObject]) {
            & $sb $prop.Value  # Recurse
          }
        } 
      }
    }
  }
  # Call the helper script block with the input object.
  & $sb $_
} 

# Convert the transformed object(s) back to JSON
ConvertTo-Json $fromJson

Output (note the "null"):

{
  "Name": {
    "Tag1": "server1",
    "Tag2": "null",
    "Tag3": "web"
  }
}

Caveat re single-element arrays:

If your input JSON is an array ([ ... ]) that happens to contain just one element, in PowerShell (Core) 7+ you need to add -NoEnumerate to the ConvertFrom-Json call in order to parse the JSON into a PowerShell array - otherwise, you'll get just get that one element itself, not wrapped in an array. (This isn't necessary in Windows PowerShell).[1]


With generalized helper function Edit-LeafProperty:

If you define the helper function below (before running the following code), the code simplifies to the following:

@'
{
  "Name": {
    "Tag1": "server1",
    "Tag2": null,
    "Tag3": "web"
  }
}
'@  | ConvertFrom-Json | 
  Edit-LeafProperty -PassThru { if ($null -eq $_.Value) { $_.Value = 'null' } } |
    ConvertTo-Json

Note:

  • Each leaf property (an object property that doesn't itself contain another, nested [pscustomobject]) is passed to the specified script block ({ ... }) bound to $_, where its .Value (and, if needed, .Name) property can be examined and updated, as needed.

  • -PassThru passes each modified object through (outputs it after modification), so that the result can directly be piped to ConvertTo-Json

    • The usual caveat re potentially unexpected truncation of the output applies: use
      -Depth as needed for object graphs more than 2 levels deep - see this post
  • The caveats re array preservation in PowerShell (Core) apply as before, with an additional twist:

    • Use ConvertFromJson -NoEnumerate to preserve (potentially nested) single-element arrays as such.
    • Because of the use of an intermediate streaming command - Edit-LeafProperty - use ConvertTo-Json -AsArray if you need to guarantee that the output JSON is an array, even when containing just one element.

Note that the function is designed to work with [pscustomobject] ([System.Management.Automation.PSCustomObject]) graphs only, such as returned by ConvertFrom-Json and ConvertFrom-Csv.

Edit-LeafProperty source code:

function Edit-LeafProperty {
  [CmdletBinding(PositionalBinding = $false)]
  param(
    [Parameter(Mandatory, Position = 0)]
    [scriptblock] $ScriptBlock,
    [Parameter(Mandatory, ValueFromPipeline)]
    [AllowNull()]
    $InputObject,
    [switch] $PassThru
  )
  begin {
    # Helper script block that walks the object graph
    $sb = {
      foreach ($el in @($args[0])) {
        if ($el -is [System.Collections.IEnumerable]) { # Nested array
          foreach ($subEl in $el) { & $sb $subEl } # Recurse
        }
        elseif ($el -is [System.Management.Automation.PSCustomObject]) {
          foreach ($prop in $el.psobject.Properties) { 
            if ($prop.Value -is [System.Management.Automation.PSCustomObject]) {
              & $sb $prop.Value # Recurse
            }
            else {
              # Invoke the leaf-property processing script block with
              # the property at hand bound to $_
              ForEach-Object $ScriptBlock -InputObject $prop
            }
          } 
        }
      }
    }
  }

  process {
    & $sb $InputObject # Walk the input object at hand.
    if ($PassThru) { $InputObject } # Pass it through, if requested.
  }

}

[1] See this answer for what prompted this change in behavior.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Thanks for sharing this. I can use it..but we can do it quick way by using powershell function or something else ? – Nullpointer Mar 24 '22 at 04:37
  • @Nullpointer, there is no built-in function, but you can wrap the object-graph walker functionality in a custom function. Please see my update, which defines and uses generalized helper function `Edit-LeafProperty`. – mklement0 Mar 24 '22 at 13:51