PowerShell 7.2:ConvertFrom-Json - 日期处理

3

在Powershell 7.2中,JSON序列化为对象时日期的处理方式似乎发生了变化,即现在变成了datetime而不是string。但我想要“旧”的行为,也就是将其处理为字符串而非datetime。

当在Powershell 7.2中使用ConvertFrom-Json时,如何实现所有日期都被反序列化为字符串而不是datetime?

编辑:

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

3
很遗憾,目前你无法控制如何解析日期。这个GitHub问题 提出了一个新参数来改变这一点,但它仍处于“打开”状态。作为一种解决方法,您可以预处理JSON以使日期无法被ConvertFrom-Json识别,例如通过插入非空白前缀字符。例如: '{ "date":"2022-09-30T07:04:23.571+00:00" }' -replace '"(\d+-\d+.\d+T\d+:\d+:\d+\.\d+\+\d+:\d+)"', '"#$1"' | ConvertFrom-Json - zett42
@zett42 感谢您的回复和建议。这很不幸...我本来期望如果实施了这种更改,也会有调整行为的选项,但显然没有。我希望这很快就会改变。与此同时,我将尝试您的方法 :) - quervernetzt
4个回答

4

实际上,这是一个众所周知的问题,请参见:#13598 添加-DateKind参数到ConvertFrom-Json以控制如何构造System.DateTime / System.DateTimeOffset值。但我认为没有易于解决的方法。您可以做的一件事情就是调用(Windows) PowerShell。由于这也不是很简单,因此我创建了一个小包装器,在PowerShell会话之间发送和接收复杂对象(请参见我的 #18460 Invoke-PowerShell 目的):

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)
}

用法:

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

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

更新

正如mklement0所评论的那样,我显然让答案变得复杂了。

通过powershell.exe进行调用是一种实用的解决方法(尽管速度慢且仅限于Windows),但请注意,您不需要一个辅助函数:如果您从PowerShell向powershell.exe(或pwsh.exe)传递一个脚本块,基于Base64 CLIXML的序列化会在后台自动发生。尝试 powershell.exe -noprofile { $args | ConvertFrom-Json } -args '{ "date":"2022-09-30T07:04:23.571+00:00" }' 因此,我认为没有必要使用Invoke-PowerShell cmdlet。

$Json = '{ "date":"2022-09-30T07:04:23.571+00:00" }'
powershell.exe -noprofile { $args | ConvertFrom-Json } -args $Json

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

假设我有一个脚本,可以并行执行许多作业,每个作业都处理一个JSON文件,如何使用这种方法实现?到目前为止,我遇到了错误:“程序'powershell.exe'无法运行:仅在重定向标准输出时支持StandardOutputEncoding。”对于"[pscustomobject]$jsonObject = powershell.exe -noprofile { $args | ConvertFrom-Json } -args $json" - quervernetzt
阅读此在GitHub Actions中获取“StandardOutputEncoding仅在重定向标准输出时受支持”,我猜测json文件中可能有一些特殊字符或为空。当您打开(Windows)PowerShell会话并执行$YourJson | ConvertFrom-Json时会发生什么?也许最好使用[mcve]和具体的Json示例提出一个新问题。 - iRon

2
  • iRon's helpful answer provides a pragmatic solution via the Windows PowerShell CLI, powershell.exe, relying on the fact that ConvertFrom-Json there does not automatically transform ISO 8601-like timestamp strings to [datetime] instances.

    • Hopefully, the proposal in the GitHub issue he links to, #13598, will be implemented in the future, which would then simplify the solution to:

       # NOT YET IMPLEMENTED as of PowerShell 7.2.x
       '{ "date":"2022-09-30T07:04:23.571+00:00" }' |
         ConvertFrom-Json -DateTimeKind None
      
  • However, a powershell.exe workaround has two disadvantages: (a) it is slow (a separate PowerShell instance in a child process must be launched), and (b) it is Windows-only. The solution below is a generalization of your own approach that avoids these problems.


这是关于你自己正在进行的方法的一个概括:

:在这里链接1

它在与时间戳模式匹配的每个字符串的开头注入了一个NUL字符(`“`0”`),假设输入本身不包含这样的字符,这是可以合理假设的。 与您的方法一样,这可以防止ConvertFrom-Json将时间戳字符串识别为此类字符串,并使它们保持不变。 然后必须对[pscustomobject]图进行后处理,以便再次删除注入的NUL字符。 这通过包含帮助器脚本块的ForEach-Object调用来实现,该脚本块递归地遍历对象图,具有以下优点:它可以与时间戳字符串可能位于层次结构的任何级别(即它们也可以位于嵌套对象的属性中)的JSON输入一起使用。 注意:假定时间戳字符串仅作为属性值包含在输入中;如果要处理输入JSON(例如'[ "2022-09-30T07:04:23.571+00:00" ]'),其中字符串本身是输入对象,则需要进行更多工作。
# 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 ($o in $args[0]) { 
        if ($o -is [Array]) { # nested array -> recurse
          foreach ($el in $o) { & $sb $el } # recurse
        }
        elseif ($o -is [System.Management.Automation.PSCustomObject]) {
          foreach ($prop in $o.psobject.Properties) { 
            if ($prop.Value -is [Array]) {
              foreach ($o in $prop.Value) { & $sb $o } # nested array -> recurse
            }
            elseif ($prop.Value -is [System.Management.Automation.PSCustomObject]) { 
              & $sb $prop.Value # nested custom object -> recurse
            }
            elseif ($prop.Value -is [string] -and $prop.Value -match '^\0') { 
              $prop.Value = $prop.Value.Substring(1) # Remove the NUL again.
            }
          } 
        }
      }
    }
    # Call the helper script block with the input object.
    & $sb $_
    # Output the modified object.
    if ($_ -is [array]) {
      # Input object was array as a whole (implies use of -NoEnumerate), output as such.
      , $_ 
    } else {
      $_
    }
  } 

0

基于@zett42的输入,这是我的解决方案:

假设我们知道JSON中日期使用的正则表达式模式,我将JSON作为字符串获取,添加前缀,以便ConvertFrom-Json不会将日期转换为日期时间,而是将其保留为字符串,使用ConvertFrom-Json将其转换为PSCustomObject,在对象上进行任何需要的操作,使用ConvertTo-Json将其序列化回JSON字符串,然后再次删除前缀。

[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

0

改变日期格式的另外两种方法:

Get-Node

使用这个Get-Node,它与mklement0递归函数非常相似:

$Data = ConvertFrom-Json $Json
$Data |Get-Node -Where { $_.Value -is [DateTime] } | ForEach-Object {
    $_.Value  = GetDate($_.Value) -Format 'yyyy-MM-ddTHH\:mm\:ss.fffzzz' -AsUTC
}
$Data

自己动手

或者自己动手,构建自己的Json反序列化器:

function ConvertFrom-Json {
    [CmdletBinding()][OutputType([Object[]])] param(
        [Parameter(ValueFromPipeLine = $True, Mandatory = $True)][String]$InputObject,
        [String]$DateFormat = 'yyyy-MM-ddTHH\:mm\:ss.fffffffzzz', # Default: ISO 8601, https://www.newtonsoft.com/json/help/html/datesinjson.htm
        [Switch]$AsLocalTime,
        [Switch]$AsOrdered
    )
    function GetObject($JObject) {
        switch ($JObject.GetType().Name) {
            'JValue' {
                switch ($JObject.Type) {
                    'Boolean'  { $JObject.Value }
                    'Integer'  { 0 + $JObject.Value }                                                 # https://github.com/PowerShell/PowerShell/issues/14264
                    'Date'     { Get-Date $JObject.Value -Format $DateFormat -AsUTC:(!$AsLocalTime) } # https://github.com/PowerShell/PowerShell/issues/13598
                    Default    { "$($JObject.Value)" }
                }
            }
            'JArray' {
                ,@( $JObject.ForEach{ GetObject $_ } )
            }
            'JObject' {
                $Properties = [Ordered]@{}
                $JObject.ForEach{ $Properties[$_.Name] = GetObject $_.Value }
                if ($AsOrdered) { $Properties } else { [PSCustomObject]$Properties }                  # https://github.com/PowerShell/PowerShell/pull/17405
            }
        }
    }
    GetObject ([Newtonsoft.Json.Linq.JObject]::Parse($InputObject))
}

使用方法:

ConvertFrom-Json $Json -DateFormat 'yyyy-MM-ddTHH\:mm\:ss.fffzzz' |ConvertTo-Json -Depth 9

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接