json - 如何将powershell中的对象序列化为json并在PS桌面和核心中获得相同的结果?
问题描述
序言
事实证明,就我而言,了解对象的来源很重要——它是来自 REST API 响应的 JSON 有效负载。不幸的是,JSON -> 对象转换在 PS 桌面和 PS 核心上产生不同的结果。在桌面上,数字被反序列化为Int32
类型,但在核心 - 到Int64
类型。由此得出我不能使用Export-CliXml
,因为对象的二进制布局不同。
主要问题
我有一个单元测试需要将实际结果与预期结果进行比较。预期的结果保存在一个json文件中,所以过程是:
- 将实际结果转换为json字符串
- 将预期结果从磁盘读取到字符串
- 比较实际和预期的字符串
不幸的是,这个方案不起作用,因为 PS 桌面ConvertTo-Json
和 PS 核心ConvertTo-Json
不会产生相同的结果。因此,如果预期结果保存在桌面上并且测试在核心上运行 - 繁荣,失败。反之亦然。
一种方法是保留两个版本的 jsons。另一种方法是使用库来创建 json。
首先,我尝试了Newtonsoft-Json powershell 模块,但它不起作用。我认为问题在于,无论我们使用什么 C# 库,它都必须了解PSCustomObject
和相似并特别对待它们。因此,我们不能只使用任何 C# JSON 库。
在这一点上,我只剩下两个 json - 每个 PS 版本一个,这有点可悲。
有更好的选择吗?
编辑 1
我想我总是可以读取 json,转换为对象,然后再次返回 json。这太糟糕了。
编辑 2
我尝试使用ConvertTo-Json -Compress
. 这消除了间距的差异,但问题是由于某种原因桌面版本将所有非字符转换为\u000...
表示形式。核心版本不这样做。
请注意:
桌面
C:\> @{ x = "'a'" } |ConvertTo-Json -Compress
{"x":"\u0027a\u0027"}
C:\>
核
C:\> @{ x = "'a'" } |ConvertTo-Json -Compress
{"x":"'a'"}
C:\>
现在核心版本有 flag -EscapeHandling
,所以:
C:\> @{ x = "'a'" } |ConvertTo-Json -Compress -EscapeHandling EscapeHtml
{"x":"\u0027a\u0027"}
C:\>
答对了!结果相同。但是现在这个代码不能在没有这个标志的桌面版本上运行。需要更多的按摩。我会检查这是否是唯一的问题。
编辑 3
如果不进行昂贵的后期处理,就不可能调和核心版本和桌面版本之间的差异。请注意:
桌面
C:\> @{ x = '"a"';y = "'b'" } |ConvertTo-Json -Compress
{"y":"\u0027b\u0027","x":"\"a\""}
C:\>
核
C:\> @{ x = '"a"';y = "'b'" } |ConvertTo-Json -Compress -EscapeHandling EscapeHtml
{"y":"\u0027b\u0027","x":"\u0022a\u0022"}
C:\> @{ x = '"a"';y = "'b'" } |ConvertTo-Json -Compress
{"y":"'b'","x":"\"a\""}
C:\>
关于如何挽救 json 方法的任何建议?
编辑 4
由于 PS 版本之间的差异,该Export-CliXml
方法也不起作用。
桌面
C:\> ('{a:1}' | ConvertFrom-Json).a.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
C:\>
核
C:\> ('{a:1}' | ConvertFrom-Json).a.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int64 System.ValueType
C:\>
因此,相同的 JSON 使用不同的数字类型表示 -Int32
在桌面和Int64
核心中。这使得使用Export-CliXml
.
除非我错过了什么。
我相信没有其他选择,但是做双重转换 - json -> object -> json然后我将在同一个 PS 版本上创建两个 json。这很糟糕。
解决方案
在从原始 JSON转换时,使用第三方Newtonsoft.Json PowerShell wrapper的
ConvertFrom-JsonNewtonsoft
cmdlet - 这应该确保跨版本兼容性(内置ConvertFrom-Json
不保证跨 PowerShell 版本,因为 Windows PowerShell 使用自定义解析器,而PowerShell [Core] v6+ 使用 Newtonsoft.json 至少到 v7.1,尽管即将迁移到新的(ish).NET CoreSystem.Text.Json
API)。重要:
ConvertFrom-JsonNewtonsoft
返回(数组)嵌套有序哈希表([ordered] @{ ... }
, ),与内置输出System.Collections.Specialized.OrderedDictionary
的嵌套 图不同。同样,只需要(数组)哈希表(字典)作为输入,尤其是不支持实例,正如您自己了解到的那样。[pscustomobject]
ConvertFrom-Json
ConvertTo-JsonNewtonsoft
[pscustomobject]
请注意,为了手动解析从 RESTful Web 服务获得的 JSON,您不能使用
Invoke-RestMethod
,因为它会隐式解析并返回[pscustomobject]
对象图。相反,使用Invoke-WebRequest
和访问返回的响应的.Content
属性。
在转换为适合存储在磁盘上的格式时,您有两种选择:
(A)如果您确实需要序列化格式也为 JSON,则必须将所有
[pscustomobject]
图形转换为(有序)哈希表,然后再将它们传递给ConvertTo-JsonNewtonsoft
.- 请参阅下面的功能
ConvertTo-OrderedHashTable
,它就是这样做的。
- 请参阅下面的功能
(B)如果特定的序列化格式不重要,即如果所有重要的是格式在 PowerShell 版本之间是相同的以便比较,则不需要额外的工作:使用内置的
Export-Clixml
cmdlet,它可以处理任何键入并生成PowerShell 的原生、基于 XML 的序列化格式,称为 CLIXML(特别是在 PowerShell 远程处理中使用),它应该是跨版本兼容的(至少在 Windows PowerShell 端与 v5.1 和从 PowerShell [Core] v7 开始.1,两者都使用相同版本的序列化协议,1.1.0.1
如 ) 所报告的$PSVersionTable.SerializationVersion
。虽然您可以使用 将此类持久文件重新转换为对象,但反序列化时类型保真度
Import-Clixml
的潜在损失使得比较序列化(CLIXML) 表示是可取的。另请注意,从 PowerShell v7.1 开始,没有基于 cmdlet 的方法来创建内存中CLIXML 表示,因此您现在必须直接使用 PowerShell API
System.Management.Automation.PSSerializer.Serialize
:.Import-CliXml
但是,以/ cmdletExport-CliXml
的形式为ConvertFrom-CliXml
/提供内存中的对应项ConvertTo-CliXml
已被批准为未来的增强功能。
Re (A): 这是函数ConvertTo-OrderedHashtable
,它将(可能嵌套的)[pscustomobject]
对象转换为有序哈希表,同时传递其他类型,因此您应该能够简单地将其插入管道中,如下所示:
# CAVEAT: ConvertTo-JsonNewtonSoft only accepts a *single* input object.
[pscustomobject] @{ foo = 1 }, [pscustomobject] @{ foo = 2 } |
ConvertTo-OrderedHashtable |
ForEach-Object { ConvertTo-JsonNewtonSoft $_ }
function ConvertTo-OrderedHashtable {
<#
.SYNOPSIS
Converts custom objects to ordered hashtables.
.DESCRIPTION
Converts PowerShell custom objects (instances of [pscustomobject]) to
ordered hashtables (instances of [System.Collections.Specialized.OrderedDictionary]),
which is useful for to-JSON serialization via the Newtonsoft.JSON library.
Note:
* Custom objects are processed recursively.
* Any scalar non-custom objects are passed through as-is.
* Any (non-dictionary) collections in property values are converted to
[object[]] arrays.
.EXAMPLE
1, [pscustomobject] @{ foo = [pscustomobject] @{ bar = 'none' }; other = 2 } | ConvertTo-OrderedHashtable
Passes integer 1 through, and converts the custom object to a nested ordered
hashtable.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)] $InputObject
)
begin {
# Recursive helper function
function convert($obj) {
if ($obj -is [System.Management.Automation.PSCustomObject]) {
# a custom object: recurse on its properties
$oht = [ordered] @{ }
foreach ($prop in $obj.psobject.Properties) {
$oht.Add($prop.Name, (convert $prop.Value))
}
return $oht
}
elseif ($obj -isnot [string] -and $obj -is [System.Collections.IEnumerable] -and $obj -isnot [System.Collections.IDictionary]) {
# A collection of sorts (other than a string or dictionary (hash table)), recurse on its elements.
return @(foreach ($el in $obj) { convert $el })
}
else {
# a non-custom object, including .NET primitives and strings: use as-is.
return $obj
}
}
}
process {
convert $InputObject
}
}
Re (B):该方法的演示Export-CliXml
(您可以从任一 PS 版本运行此代码):
$sb = {
Install-Module -Scope CurrentUser Newtonsoft.json
if (-not $IsCoreClr) {
# Workaround for PS Core's $env:PSModulePath overriding WinPS'
Import-Module $HOME\Documents\WindowsPowerShell\Modules\newtonsoft.json
}
@'
{
"results": {
"users": [
{
"userId": 1,
"emailAddress": "jane.doe@example.com",
"date": "2020-10-05T08:08:43.743741-04:00",
"attributes": {
"height": 165,
"weight": 60
}
},
{
"userId": 2,
"emailAddress": "john.doe@example.com",
"date": "2020-10-06T08:08:43.743741-04:00",
"attributes": {
"height": 180,
"weight": 72
}
}
]
}
}
'@ | ConvertFrom-JsonNewtonsoft | Export-CliXml "temp-$($PSVersionTable.PSEdition).xml"
}
# Execute the script block in both editions
Write-Verbose -vb 'Running in Windows PowerShell...'
powershell -noprofile $sb
Write-Verbose -vb 'Running in PowerShell Core...'
pwsh -noprofile $sb
# Compare the resulting CLIXML files.
Write-Verbose -vb "Comparing the resulting files: This should produce NO output,`n indicating that the files have identical content."
Compare-Object (Get-Content 'temp-Core.xml') (Get-Content 'temp-Desktop.xml')
Write-Verbose -vb 'Cleaning up...'
Remove-Item 'temp-Core.xml', 'temp-Desktop.xml'
您应该看到以下详细输出:
VERBOSE: Running in Windows PowerShell...
VERBOSE: Running in PowerShell Core...
VERBOSE: Comparing the resulting files: This should produce NO output,
indicating that the files have identical content.
VERBOSE: Cleaning up...
推荐阅读
- overflow - 如何解决此错误:“RuntimeWarning:在正方形中遇到溢出”
- node.js - 我部署的 MERN 应用程序无法从其他设备上的后端服务检索数据
- javascript - 未捕获的错误:语法错误,使用 replaceWith 时无法识别的表达式
- javascript - Shopify 导航栏在窗口调整大小时压缩
- bash - 要求 bash 排序 -n 选项说明
- puppeteer - 通过 puppeteer 监听自定义事件
- css - font-family 继承了 overpower 的子类规范,其中包括 font-family
- c - 谁能告诉我哪里出错了
- python - VSCode 默认查找与路径上的第一个不同的 python
- php - 有没有办法更改 Wordpress 插件中的文本?