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 thatConvertFrom-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.
}