11

我有一个包含一些属性的文件,其中一些属性的值包含转义字符,例如一些 Urls 和 Regex 模式。

读取内容并转换回 json 时,无论是否转义,内容都不正确。如果我用取消转义转换回 json,一些正则表达式会中断,如果我用取消转义转换,url 和一些正则表达式会中断。

我该如何解决这个问题?

最小完整可验证示例

以下是一些简单的代码块,可让您简单地重现问题:

内容

$fileContent = 
@"
{
    "something":  "http://domain/?x=1&y=2",
    "pattern":  "^(?!(\\`|\\~|\\!|\\@|\\#|\\$|\\||\\\\|\\'|\\\")).*"
}
"@

与逃脱

如果我阅读内容然后使用以下命令将内容转换回 json:

$fileContent | ConvertFrom-Json | ConvertTo-Json | %{[regex]::Unescape($_)}

输出(这是错误的)将是:

{
    "something":  "http://domain/?x=1&y=2",
    "pattern":  "^(?!(\|\~|\!|\@|\#|\$|\||\\|\'|\")).*"
}

没有逃避

如果我阅读内容然后使用以下命令将内容转换回 json:

$fileContent | ConvertFrom-Json | ConvertTo-Json 

输出(这是错误的)将是:

{
    "something":  "http://domain/?x=1\u0026y=2",
    "pattern":  "^(?!(\\|\\~|\\!|\\@|\\#|\\$|\\||\\\\|\\\u0027|\\\")).*"
}

预期结果

预期结果应与输入文件内容相同。

4

3 回答 3

20

我决定使用,而是用它们的字符串值Unescape替换 unicode字符,现在它可以正常工作了:\uxxxx

$fileContent = 
@"
{
    "something":  "http://domain/?x=1&y=2",
    "pattern":  "^(?!(\\`|\\~|\\!|\\@|\\#|\\$|\\||\\\\|\\'|\\\")).*"
}
"@

$fileContent | ConvertFrom-Json | ConvertTo-Json | %{
    [Regex]::Replace($_, 
        "\\u(?<Value>[a-zA-Z0-9]{4})", {
            param($m) ([char]([int]::Parse($m.Groups['Value'].Value,
                [System.Globalization.NumberStyles]::HexNumber))).ToString() } )}

这会产生预期的输出:

{
    "something":  "http://domain/?x=1&y=\\2",
    "pattern":  "^(?!(\\|\\~|\\!|\\@|\\#|\\$|\\||\\\\|\\'|\\\")).*"
}
于 2017-12-12T18:50:12.750 回答
3

如果您不想依赖正则表达式(来自@Reza Aghaei 的回答),您可以导入Newtonsoft JSON库。好处是默认的StringEscapeHandling属性,它只转义控制字符。另一个好处是避免使用 Regex 进行的潜在危险字符串替换。

StringEscapeHandling也是 PowerShell Core(版本 6 及更高版本)的默认处理方式,因为从那时起他们开始在内部使用 Newtonsoft。因此,另一种选择是使用 PowerShell Core 中的 ConvertFrom-Json 和 ConvertTo-Json。

如果您导入 Newtonsoft JSON 库,您的代码将如下所示:

[Reflection.Assembly]::LoadFile("Newtonsoft.Json.dll")

$json = Get-Content -Raw -Path file.json -Encoding UTF8 # read file
$unescaped = [Newtonsoft.Json.Linq.JObject]::Parse($json) # similar to ConvertFrom-Json

$escapedElementValue = [Newtonsoft.Json.JsonConvert]::ToString($unescaped.apiName.Value) # similar to ConvertTo-Json
$escapedCompleteJson = [Newtonsoft.Json.JsonConvert]::SerializeObject($unescaped) # similar to ConvertTo-Json

Write-Output "Variable passed = $escapedElementValue"
Write-Output "Same JSON as Input = $escapedCompleteJson"
于 2019-09-14T11:17:13.760 回答
1

tl;博士

问题不影响PowerShell (Core) 6+(按需安装、跨平台 PowerShell 版本),它使用基于Newtonsoft.JSONConvertTo-Json的不同实现和cmdlet (其直接使用显示在r3verse 的答案中) ),从 Powershell 7.2 开始。在那里,您的示例往返命令按预期工作。ConvertFrom-Json

ConvertTo-JsonWindows PowerShell中受到影响(与 Windows PowerShell 捆绑的版本,其最新和最终版本为 5.1)。但请注意,JSON 表示(虽然出乎意料)在技术上是正确的。

一个简单但强大的解决方案只专注于对那些ConvertTo-Json 意外创建的 Unicode 转义序列进行转义 - 即 for & ' < >- 同时排除误报:

# The following sample JSON with undesired Unicode escape sequences for `& < > '`, was
# created with Windows PowerShell's ConvertTo-Json as follows:
#   ConvertTo-Json "Ten o'clock at <night> & later. \u0027 \\u0027"
# Note that \u0027 and \\u0027 are NOT Unicode escape sequences and must not be
# interpreted as such.
# The *desired* JSON representation - without the unexpected escaping - would be:
#   "Ten o'clock at <night> & later. \\u0027 \\\\u0027"
$json = '"Ten o\u0027clock at \u003Cnight\u003e \u0026 later. \\u0027 \\\\u0027"'

[regex]::replace(
  $json, 
  '(?<=(?:^|[^\\])(?:\\\\)*)\\u(00(?:26|27|3c|3e))', 
  { param($match) [char] [int] ('0x' + $match.Groups[1].Value) },
  'IgnoreCase'
)

上面输出了所需的 JSON 表示,没有不必要的转义:

"Ten o'clock at <night> & later. \\u0027 \\\\u0027"

背景资料

ConvertTo-JsonWindows PowerShell中,意外地通过JSON 字符串中的 Unicode 转义序列表示以下 ASCII 范围字符:

  • &(Unicode 转义序列\u0026:)
  • '( \u0027)
  • <>(\u003c\u003e)

这样做没有充分的理由(这些字符只需要在 HTML/XML 文本中转义)。

然而,任何兼容的 JSON 解析器——包括ConvertFrom-Json——将这些转义序列转换回它们所代表的字符。

换句话说:虽然由 Windows PowerShell 创建的 JSON 文本ConvertTo-Json是出乎意料的并且可能会妨碍可读性,但它在技术上是正确的,并且 - 虽然不完全相同-就其所代表的数据而言等同于原始表示。


修复可读性问题

顺便说一句:虽然[regex]::Unescape()它的目的是仅对正则表达式进行转义,但它也将 Unicode 转义序列转换为它们所代表的字符,但它从根本上不适合选择性地对 Unicode 序列JSON字符串进行转义,因为必须保留所有其他 转义符才能使用 JSON字符串保持语法有效。\

虽然您的答案通常效果很好,但它有局限性(除了容易纠正的问题,a-zA-Z应该a-fA-F限制匹配那些有效的十六进制数字的字母):

  • 它不排除误报,例如\\u0027or \\\\u0027(\\转义\,因此该u0027部分成为逐字字符串,不得视为转义序列)。

  • 它转换所有Unicode 转义序列,这会带来两个问题:

    • 表示需要转义的字符的转义序列也将转换为逐字字符表示,这将破坏JSON 表示\u005c,例如,假设它表示的字符\需要转义。

    • 对于必须表示为Unicode 转义序列(所谓的代理对)的非 BMP Unicode 字符,您的解决方案会错误地尝试分别对这对的每一半进行转义。

有关克服这些限制的强大解决方案,请参阅此答案 (代理对保留为 Unicode 转义序列,其字符需要转义的 Unicode 转义序列将转换为\基于 - 的(C 样式)转义,例如\n,如果可能的话)。

ConvertTo-Json 但是,如果唯一的要求是取消转义 Windows PowerShell意外创建的那些 Unicode 转义序列,那么顶部的解决方案就足够了。

于 2021-10-20T17:06:49.773 回答