1

How can I remove the double quotes from PowerShell output?

The curl statement only runs when the quotes around key names are removed. I have tested my endpoint with a hard coded JSON object. (see Desired Output)

The curl statement fails with the PowerShell constructed object where the key name quotes are included. (see Actual Output)

Any hints are most appreciated.

Actual Output

{"ISOYear":[{"ISOYear":2023}],"ISOWeek":[{"ISOWeek":23}],"SkillCategory":"Warehouse","Building":"ABC"}

Desired Output

{ISOYear:[{ISOYear:2023}],ISOWeek:[{ISOWeek:29}],SkillCategory:'Warehouse',Building:'ABC'}

PowerShell Script

  $hash=@{
        ISOYear=@()
        ISOWeek=@()
        SkillCategory="Warehouse"
        Building="ABC"
        }
    $hash.ISOYear +=  @{ISOYear=2023}
    $hash.ISOWeek +=  @{ISOWeek=23}
    
    New-Object -TypeName PSObject -Property $hash | Select-Object ISOYear,ISOWeek,SkillCategory,Building
    Write-Output $hash
    
    Pause
    
    #THIS DOES NOT WORK WITH MY CURL STATEMENT
    #KEYs include "
    $json = $hash | ConvertTo-Json -Compress
    Write-Output $json 
    
    #--->ADDED THIS TO ESCAPE \" $json 
    $jsonEscaped = $json  -replace '([\\]*)"', '$1$1\"'
    Write-Output $jsonEscaped 

    Pause
    
    #THIS WORKS WITH MY CURL STATEMENT
    #KEYs do not include "
    $CURL_DATA="{ISOYear:[{ISOYear:$O_YEAR}],ISOWeek:[{ISOWeek:$O_WEEK}],SkillCategory:'Warehouse',Building:'ABC'}";
    Write-Output $CURL_DATA
    
    Pause
    #WORKS
    #curl.exe -k --ntlm -u: -X POST --data $CURL_DATA https://localhost:44379/api/controller/method -H "Content-Type: application/json; charset=utf-8"

#UPDATED TO RECEIVE \" escaped json
#curl.exe -k --ntlm -u: -X POST --data "$jsonEscaped" https://localhost:44379/api/controller/method -H "Content-Type: application/json; charset=utf-8"
    
    Pause

===================UPDATE 1===================

I have added the curl statement using the PowerShell constructed object. I found a few minutes ago that I have to call it like "$json" and it at least gets to the method. Here's the error now

This is the error I receive now.

{"errors":{"Building":["Unexpected character encountered while parsing value: B. Path 'Building', line 1, position 33."]},"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-4cbdc390e1a314d50861365e4c34aa12-f1f7defa11ef159e-00"}

===================UPDATE 2===================

Per @mklement0, his link gave a replace statement which works.

For programs that expect "-escaping, use the following -replace operation to robustly perform the extra escaping programmatically:

'...' -replace '([\]*)"', '$1$1"'

So I added this to my PowerShell script.

#--->ADDED THIS TO ESCAPE \" $json 
$jsonEscaped = $json  -replace '([\\]*)"', '$1$1\"'
Write-Output $jsonEscaped 

I have updated the full script above.

cmill
  • 849
  • 7
  • 20
  • What is the error message from `curl.exe`? And have you verified that passing the string without the quotes works? – Mathias R. Jessen Jul 27 '23 at 16:53
  • @MathiasR.Jessen I added the error. I'm at least receiving now something indicating what's wrong in the json. Still not sure how to fix it. – cmill Jul 27 '23 at 17:22

1 Answers1

1

Your JSON string - with properly quoted property names - does work, but a long-standing bug prevented it from getting passed properly to curl.exe:

  • As detailed in this answer, Windows PowerShell and PowerShell (Core) up to v7.2.x require " characters embedded in PowerShell strings to be explicitly escaped as \" when passed to external programs such as curl.exe

  • Passing a programmatically escaped value, ($json -replace '(\\*)"', '$1$1\"'), in lieu of just $json to curl.exe solved the problem.

    • For instance, this programmatic escaping turns verbatim { "foo": "3\" of snow" } into verbatim { \"foo\": \"3\\\" of snow\" }, which PowerShell then encloses in "..." on the process command line.[1]

Some APIs accept a simplified - but not standards-compliant - form of JSON, in which property names may be unquoted, and string values may be enclosed in '...' rather than "..."

Given that your web service seemingly accepts this simplified format, using it would indeed avoid the "-related bug (assuming that there aren't any property values with embedded ").

While ConvertFrom-Json can read this simplified format too (in both PowerShell editions), ConvertTo-Json cannot create it (in neither edition).

Below is a custom implementation that creates this format, ConvertTo-SimplifiedJson; assuming it is already defined, the following:

'{"ISOYear":[{"ISOYear":2023}],"ISOWeek":[{"ISOWeek":23}],"SkillCategory":"Warehouse","Building":"ABC"}' |
  ConvertFrom-Json |
  ConvertTo-SimplifiedJson

outputs:

{ISOYear:[{ISOYear:2023}],ISOWeek:[{ISOWeek:23}],SkillCategory:'Warehouse',Building:'ABC'}

ConvertTo-SimplifiedJson source code:
# Creates a simplified JSON representation for the given object (graph)
# using unquoted property names, where possible, and single-quoting for string values.
# Note: 
#  * The resulting respresentation is compressed (no whitespace for readability).
#  * Property values of types that cannot be represented in JSON are represented
#    as strings:
#    * [datetime] and [datetimeoffset] instances are stringified as ISO 8601 timestamps,
#       using .ToString('o')
#    * All other such types are represented by their .ToString() values.
function ConvertTo-SimplifiedJson {
  param([Parameter(ValueFromPipeline)] [object] $InputObject)
  begin {
    function test-ListLike { 
      param($o) 
      $o -is [System.Collections.IEnumerable] -and $o -isnot [string] -and $o -isnot [System.Collections.IDictionary] 
    }
  }
  process {
    $properties = 
      if ($InputObject -is [System.Collections.IDictionary]) {
        $InputObject.GetEnumerator()
      } else {
        $InputObject.psobject.Properties
      }
    if (test-ListLike $InputObject) {
      # array as single input object -> recurse
      '[' + $(foreach ($o in $InputObject) { ConvertTo-SimplifiedJson $o }) + ']'
    }
    elseif ($InputObject.GetType() -in [datetime], [datetimeoffset]) {
      "'{0}'" -f $InputObject.ToString('o') # Use an ISO 8601 time string
    }
    elseif ($InputObject -is [bool]) {
      "$($InputObject)".ToLower() # JSON requires case-exact 'true' or 'false'
    }
    elseif ($InputObject.GetType() -in [byte], [sbyte], [int16], [uint16], [int32], [uint32], [int64], [uint64], [bigint], [decimal], [float], [double] ) {
      # A number type.
      [string] $InputObject # Use culture-invariant stringification, to be embedded unquoted in the JSON.
    }
    elseif ($InputObject.GetType() -in [char], [string] -or $properties.Count -eq 0) {
      # A char., a string, or an unsupported property-less object.
      "'{0}'" -f ($InputObject -replace "'", "\'")
    }
    else { 
      # A (nested) object that doesn't map onto a JSON primitive.
      # Recursively process its properties.
      $sep = '{'
      -join $(
        foreach ($p in $properties) {
          $name = if ($p.Name -notmatch '\W') { $p.Name } else { "'{0}'" -f ($p.Name -replace "'", "\'") }
          $value = ConvertTo-SimplifiedJson $p.Value
          '{0}{1}:{2}' -f $sep, $name, $value
          $sep = ','
        }
      ) + '}'
    }
  }
}

[1] That is, the explicit escaping does what PowerShell should always have done itself, behind the scenes, and now does in v7.3+. Prior to v7.3, it blindly enclosed arguments containing spaces in "...", without ever escaping embedded ". It is this broken behavior that the explicit-escaping workaround builds on.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • I can't thank you enough for the assistance! Thank you for the detailed explanation and the conversion function. This helps tremendously, Cheers! – cmill Jul 28 '23 at 13:32
  • 1
    Glad to hear it, @cmill; my pleasure. Thanks for catching the `[uint]` problem: it is short for `[System.UInt32]`, but_ only in PowerShell (Core) 7+_ (which now also supports `[ulong]` for `[System.UInt64]`. Using `[uint32]` works in Windows PowerShell too. Also, I was missing `[uint64]` from the list - please see the updated code. – mklement0 Jul 28 '23 at 13:47