【问题标题】:Powershell - Retain the text of all Enum properties with ConvertTo-JsonPowershell - 使用 ConvertTo-Json 保留所有枚举属性的文本
【发布时间】:2017-11-13 15:11:34
【问题描述】:

对于“Get-Msoldomain”powershell 命令让我得到以下输出(我们称之为输出#1),其中名称、状态和身份验证是属性名称以下是它们各自的值。

Name                    Status   Authentication

myemail.onmicrosoft.com Verified Managed

当我使用带有“ConvertTo-Json”的命令时,如下所示

GetMsolDomain |ConvertTo-Json

我得到以下 Json 格式的输出(我们称之为 Output#2)。

{
    "ExtensionData":  {

                      },
    "Authentication":  0,
    "Capabilities":  5,
    "IsDefault":  true,
    "IsInitial":  true,
    "Name":  "myemail.onmicrosoft.com",
    "RootDomain":  null,
    "Status":  1,
    "VerificationMethod":  1
}

但是,问题是,如果您注意到两个输出中的 Status 属性,情况就不同了。 VerificationMethod 属性也是如此。不使用 ConvertTo-JSon Powershell 给出文本,使用 ConvertTo-Json 给出整数。

当我给出以下命令时

get-msoldomain |Select-object @{Name='Status';Expression={"$($_.Status)"}}|ConvertTo-json

我得到的输出为

{
    "Status":  "Verified"
}

但是,我想要一些东西,这样我就不必指定任何特定的属性名称来转换它,就像我在上面指定的那样

Select-object @{Name='Status';Expression={"$($_.Status)"}}

此行仅转换 Status 属性,而不是 VerificationMethod 属性,因为这是我提供的输入。

问题:我可以给“ConvertTo-Json”命令行开关提供一些通用的东西,以便它返回 ALL 枚举属性作为文本而不是整数,没有明确命名它们,所以我得到如下输出:

{
    "ExtensionData":  {

                      },
    "Authentication":  0,
    "Capabilities":  5,
    "IsDefault":  true,
    "IsInitial":  true,
    "Name":  "myemail.onmicrosoft.com",
    "RootDomain":  null,
    "Status":  "Verified",
    "VerificationMethod":  "DnsRecord"
}

【问题讨论】:

    标签: json powershell enums


    【解决方案1】:

    好吧,如果您不介意走一趟 :) 您可以将其转换为强制字符串输出的 CSV,然后将其从 CSV 重新转换回 PS 对象,最后再转换回 Json。

    像这样:

    Get-MsolDomain | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json
    
    • 如果您需要保留原始类型而不是将其全部转换为字符串,请参阅 mklement0 有用的答案...

    【讨论】:

    • 一个简单实用的解决方案,但(抛开效率不谈)它有一个很大的警告:所有属性值总是被转换为字符串,这通常是不可取的。
    【解决方案2】:

    PowerShell Core(PowerShell 版本 6 及更高版本)通过 ConvertTo-Json-EnumsAsStrings 开关提供了一个简单的解决方案。

    GetMsolDomain | ConvertTo-Json -EnumsAsStrings  # PS *Core* (v6+) only
    

    很遗憾,Windows PowerShell 不支持此开关

    Avshalom's answer 提供了一种快速的解决方法,但需要注意的是:所有属性值在此过程中总是转换为字符串,这通常是不可取的(例如,Authentication 属性的 numeric0 将变成 string '0')。

    这是一个更通用的基于过滤器函数的解决方法,它递归地自省输入对象并输出有序哈希表,这些哈希表反映了输入属性,枚举值转换为字符串和所有其他传递的值,您然后可以传递给ConvertTo-Json

    Filter ConvertTo-EnumsAsStrings ([int] $Depth = 2, [int] $CurrDepth = 0) {
      if ($_ -is [enum]) { # enum value -> convert to symbolic name as string
        $_.ToString() 
      } elseif ($null -eq $_ -or $_.GetType().IsPrimitive -or $_ -is [string] -or $_ -is [decimal] -or $_ -is [datetime] -or $_ -is [datetimeoffset]) {
        $_
      } elseif ($_ -is [Collections.IEnumerable] -and $_ -isnot [Collections.IDictionary]) { # enumerable (other than a dictionary)
        , ($_ | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth+1))
      } else { # non-primitive type or dictionary (hashtable) -> recurse on properties / entries
        if ($CurrDepth -gt $Depth) { # depth exceeded -> return .ToString() representation
          Write-Warning "Recursion depth $Depth exceeded - reverting to .ToString() representations."
          "$_"
        } else {
          $oht = [ordered] @{}
          foreach ($prop in $(if ($_ -is [Collections.IDictionary]) { $_.GetEnumerator() } else { $_.psobject.properties })) {
            if ($prop.Value -is [Collections.IEnumerable] -and $prop.Value -isnot [Collections.IDictionary] -and $prop.Value -isnot [string]) {
              $oht[$prop.Name] = @($prop.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth+1))
            } else {      
              $oht[$prop.Name] = $prop.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth+1)
            }
          }
          $oht
        }
      }
    }
    

    警告:与ConvertTo-Json 一样,递归深度(-Depth)默认限制为2,以防止无限递归/过大的输出(就像你会得到的类型例如[System.IO.FileInfo] 通过Get-ChildItem,例如)。同样,超过隐含或指定深度的值由其.ToString() 值表示。显式使用-Depth 控制递归深度。

    调用示例:

    PS> [pscustomobject] @{ p1 = [platformId]::Unix; p2 = 'hi'; p3 = 1; p4 = $true } | 
          ConvertTo-EnumsAsStrings -Depth 2 |
            ConvertTo-Json
    
    {
      "p1": "Unix",   # Enum value [platformId]::Unix represented as string.
      "p2": "hi",     # Other types of values were left as-is.
      "p3": 1,
      "p4": true
    }
    

    注意:-Depth 2 在这里不是必需的,因为 2 是默认值(并且输入的深度是 0),但这里显示它是为了提醒您可以想要明确控制它。


    如果您想为其他类型实现自定义表示,例如[datetime][datetimoffset](使用与 ISO 8601 兼容的 .NET round-trip date-time string format, o,正如 PowerShell (Core) v6+ 自动所做的那样),以及 [timespan][version][guid][ipaddress],请参阅 Brett's helpful variation of this answer

    【讨论】:

    • 这是一个比接受的答案更好的答案
    【解决方案3】:

    我需要将 pwsh 对象序列化为 JSON,并且无法使用 ConvertTo-Json-EnumsAsStrings 参数,因为我的代码在 psv5 上运行。由于我在使用@mklement0 的代码时遇到了无限循环编者注:自已修复。,我重写了它。我修改后的代码还处理了一些其他类型的序列化,例如日期,将它们序列化为 ISO 8601 格式,这通常是在 JSON 中表示日期的公认方式。随意使用它,如果您遇到任何问题,请告诉我。

    Filter ConvertTo-EnumsAsStrings ([int] $Depth = 10, [int] $CurrDepth = 0) {
    
      if ($CurrDepth -gt $Depth) {
        Write-Error "Recursion exceeded depth limit of $Depth"
        return $null
      }
    
      Switch ($_) {
        { $_ -is [enum] -or $_ -is [version] -or $_ -is [IPAddress] -or $_ -is [Guid] } {
          $_.ToString()
        }
        { $_ -is [datetimeoffset] } {
          $_.UtcDateTime.ToString('o')
        }
        { $_ -is [datetime] } {
          $_.ToUniversalTime().ToString('o')
        }
        { $_ -is [timespan] } {
          $_.TotalSeconds
        }
        { $null -eq $_ -or $_.GetType().IsPrimitive -or $_ -is [string] -or $_ -is [decimal] } {
          $_
        }
        { $_ -is [hashtable] } {
          $ht = [ordered]@{}
          $_.GetEnumerator() | ForEach-Object {
            $ht[$_.Key] = ($_.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth + 1))
          }
          if ($ht.Keys.Count) {
            $ht
          }
        }
        { $_ -is [pscustomobject] } {
          $ht = [ordered]@{}
          $_.PSObject.Properties | ForEach-Object {
            if ($_.MemberType -eq 'NoteProperty') {
              Switch ($_) {
                { $_.Value -is [array] -and $_.Value.Count -eq 0 } {
                  $ht[$_.Name] = @()
                }
                { $_.Value -is [hashtable] -and $_.Value.Keys.Count -eq 0 } {
                  $ht[$_.Name] = @{}
                }
                Default {
                  $ht[$_.Name] = ($_.Value | ConvertTo-EnumsAsStrings -Depth $Depth -CurrDepth ($CurrDepth + 1))
                }
              }
            }
          }
          if ($ht.Keys.Count) {
            $ht
          }
        }
        Default {
          Write-Error "Type not supported: $($_.GetType().ToString())"
        }
      }
    }
    

    【讨论】:

    • 一些不错的改进。至于[datetime][datetimeoffset]:除非你不介意丢失时区信息,否则我建议不要使用UTC。 PowerShell (Core) v6+ 也使用 o 格式,使用本地时间(如果适用)(例如,2021-07-12T23:37:13.803887-04:00)。
    • @mklement0 至少在我的测试中,PSv5 也支持这一点。但好点 - 我可能会修改这一点,因为如果时区规则发生变化,将 UTC 时间存储在数据库中的传统建议可能会给我带来麻烦。使用偏移量存储可能更具前瞻性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-14
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多