Home > Software engineering >  Powershell 7.2: ConvertFrom-Json - Date Handling
Powershell 7.2: ConvertFrom-Json - Date Handling

Time:11-05

With Powershell 7.2 there seems to be a change in how a JSON is deserialized into an object in terms of dates -> instead of string it is now datetime. But I want to have the "old" behavior, i.e. that it is handled as string and NOT datetime.

How can I achieve that when using ConvertFrom-Json in Powershell 7.2 all dates are deserialized as string and not datetime?

EDIT:

$val = '{ "date":"2022-09-30T07:04:23.571 00:00" }' | ConvertFrom-Json
$val.date.GetType().FullName

CodePudding user response:

This is actually a known issue, see: #13598 Add a -DateKind parameter to ConvertFrom-Json to control how System.DateTime / System.DateTimeOffset values are constructed. Yet I think there is no easy solution for this. One thing you might do is just invoke (Windows) PowerShell. Which isn't currently straights forward as well therefore I have created a small wrapper to send and receive complex objects between PowerShell sessions (see also my #18460 Invoke-PowerShell purpose):

function Invoke-PowerShell ($Command) {
    $SerializeOutput = @"
         `$Output = $Command
         [System.Management.Automation.PSSerializer]::Serialize(`$Output)
"@
    $Bytes = [System.Text.Encoding]::Unicode.GetBytes($SerializeOutput)
    $EncodedCommand = [Convert]::ToBase64String($Bytes)
    $PSSerial = PowerShell -EncodedCommand $EncodedCommand
    [System.Management.Automation.PSSerializer]::Deserialize($PSSerial)
}

Usage:

Invoke-PowerShell { '{ "date":"2022-09-30T07:04:23.571 00:00" }' | ConvertFrom-Json }

date
----
2022-09-30T07:04:23.571 00:00

CodePudding user response:

Based on the input from @zett42 here my solution:

Assuming we know the regex pattern of the date used in the JSON I get the JSON as string, add a prefix so that ConvertFrom-Json does not convert dates to datetime but keeps it as string, convert it with ConvertFrom-Json to a PSCustomObject, do whatever I need to do on the object, serialize it back to a JSON string with ConvertTo-Json and then remove the prefix again.

[string]$json = '{ "date":"2022-09-30T07:04:23.571 00:00", "key1": "value1" }'

[string]$jsonWithDatePrefix = $json -replace '"(\d -\d .\d T\d :\d :\d \.\d \ \d :\d )"', '"#$1"'

[pscustomobject]$jsonWithDatePrefixAsObject = $jsonWithDatePrefix | ConvertFrom-Json

$jsonWithDatePrefixAsObject.key1 = "value2"

[string]$updatedJsonString = $jsonWithDatePrefixAsObject | ConvertTo-Json

[string]$updatedJsonStringWithoutPrefix = $updatedJsonString -replace '"(#)(\d -\d .\d T\d :\d :\d \.\d \ \d :\d )"', '"$2"'

Write-Host $updatedJsonStringWithoutPrefix

CodePudding user response:

Here's a generalization of your own approach:

  • It injects a NUL character ("`0") at the start of each string that matches the pattern of a timestamp - the assumption is that the input itself never contains such characters, which is fair to assume.

  • This, as in your approach, prevents ConvertFrom-Json from recognizing timestamp strings as such, and leaves them untouched.

  • The [pscsutomobject] graph that ConvertFrom-Json outputs must then be post-processed in order to remove the injected NUL characters again.

    • This is achieved with a ForEach-Object call that contains a helper script block that recursively walks the object graph, which has the advantage of working with JSON input whose timestamp strings may be at any level of the hierarchy.

    • Note: The assumption is that the timestamp strings are only ever contained as property values in the input; more work would be needed if you wanted to handle input JSON such as '[ "2022-09-30T07:04:23.571 00:00" ]' too, where the strings are input objects themselves.

# Sample JSON.
$val = '{ "date":"2022-09-30T07:04:23.571 00:00" }'

$val -replace '"(?=\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[ -]\d{2}:\d{2}")', "`"`0" |          #"
  ConvertFrom-Json |
  ForEach-Object {
    # Helper script block that walks the object graph
    $sb = {
      foreach ($el in @($args[0])) {
        if ($el -is [Array]) {
          foreach ($subEl in $el) { & $sb $subEl } # nested array -> 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
            }
            elseif ($prop.Value -is [string] -and $prop.Value -match '^\0') { 
              $prop.Value = $prop.Value.Substring(1)
            }
          } 
        }
      }
    }
    # Call the helper script block with the input object.
    & $sb $_
    $_ # Output the modified object.
  } 
  • Related