【问题标题】:Convert emoji Unicode byte sequences to Unicode characters with jq使用 jq 将 emoji Unicode 字节序列转换为 Unicode 字符
【发布时间】:2021-11-16 00:10:17
【问题描述】:

我是filtering Facebook Messenger JSON dumpsjq。源 JSON 包含作为 Unicode 序列的表情符号。如何将这些输出为表情符号?

echo '{"content":"\u00f0\u009f\u00a4\u00b7\u00f0\u009f\u008f\u00bf\u00e2\u0080\u008d\u00e2\u0099\u0082\u00ef\u00b8\u008f"}' | jq -c '.'

实际结果:

{"content":"ð¤·ð¿ââï¸"}

想要的结果:

{"content":"????????‍♂️"}

【问题讨论】:

  • 问题在于控制台正在解释那些反斜杠序列。 echo '{... \\u00f0 ...}' 更正它...当然实际上您可能不会在控制台中使用复制粘贴的文字,对吧...?
  • 这不是控制台,而是每个 UTF-8 字节都被编码为单独的 Unicode 代码点这一事实。 content 映射到需要显式解码的字符串。
  • 这是不正确的 JSON。该规范不允许您以这种方式编码 Unicode;该规范要求带有\u 的代码点使用UTF-16 值,而不是UTF-8 字节序列。可以编写一个解析器,甚至可能部分使用 jq,它将对其进行解码并正确重新编码,但是有没有办法修复发送方以正确编码呢? (有关详细信息,请参阅第 9 节,字符串:ecma-international.org/wp-content/uploads/…
  • 我不会说它是不正确的,但它没有那么直接。
  • 这真的是你收到的 JSON 吗?还是您从调试器中复制的字节数组?如果是这样,您将在调试器工件上浪费大量时间。

标签: json unicode jq


【解决方案1】:

这是一个仅限 jq 的解决方案。它适用于 jq 的 C 和 Go 实现。

# input: a decimal integer
# output: the corresponding binary array, most significant bit first
def binary_digits:
  if . == 0 then 0
  else [recurse( if . == 0 then empty else ./2 | floor end ) % 2]
    | reverse
    | .[1:] # remove the leading 0
  end ;

def binary_to_decimal:
  reduce reverse[] as $b ({power:1, result:0};
       .result += .power * $b
       | .power *= 2)
  | .result;

# input: an array of decimal integers representing the utf-8 bytes of a Unicode codepoint.
# output: the corresponding decimal number of that codepoint.
def utf8_decode:
   # Magic numbers:
   # x80: 128,       # 10000000
   # xe0: 224,       # 11100000
   # xf0: 240        # 11110000
     (-6) as $mb     # non-first bytes start 10 and carry 6 bits of data
                     # first byte of a 2-byte encoding starts 110 and carries 5 bits of data
                     # first byte of a 3-byte encoding starts 1110 and carries 4 bits of data
                     # first byte of a 4-byte encoding starts 11110 and carries 3 bits of data
   | map(binary_digits) as $d
   | .[0]
   | if   . < 128 then $d[0]
     elif . < 224 then [$d[0][-5:][], $d[1][$mb:][]]
     elif . < 240 then [$d[0][-4:][], $d[1][$mb:][], $d[2][$mb:][]]
     else              [$d[0][-3:][], $d[1][$mb:][], $d[2][$mb:][], $d[3][$mb:][]]
     end
   | binary_to_decimal ;
{"content":"\u00f0\u009f\u00a4\u00b7\u00f0\u009f\u008f\u00bf\u00e2\u0080\u008d\u00e2\u0099\u0082\u00ef\u00b8\u008f"}
| .content|= (explode| [utf8_decode] | implode)

成绩单:

$ jq -nM -f program.jq
{
  "content": "?"
}

【讨论】:

    【解决方案2】:

    @chepner 在 Python 中对 Latin1 的使用终于在我脑海中挥之不去,几乎可以直接使用 jq。你需要通过 iconv 管道:

    $ echo '{"content":"\u00f0\u..."}' | jq -c . | iconv -t latin1
    {"content":"??‍♂️"}
    

    在 JSON 中,字符串 \u00f0 并不意味着“字节 0xF0,作为 UTF-8 编码序列的一部分”。它的意思是“Unicode 代码点 0x00F0”。那是ð,jq正确显示为UTF-8编码0xc3 0xb0。

    iconv 调用将 ð (0xc3 0xb0) 的 UTF-8 字符串重新解释为 Latin1 为 0xf0(Latin1 与前 255 个 Unicode 代码点完全匹配)。然后,支持 UTF-8 的终端将 that 解释为 UTF-8 序列的第一个字节。

    【讨论】:

      【解决方案3】:

      问题在于响应包含 Unicode 代码点的 UTF-8 编码,而不是代码点本身。 jq 无法自行解码。您可以使用另一种语言;例如,在 Python 中

      >>> x = json.load(open("response.json"))['content']
      >>> x
      'ð\x9f¤·ð\x9f\x8f¿â\x80\x8dâ\x99\x82ï¸\x8f'
      >>> x.encode('latin1').decode()
      '??\u200d♂️'
      

      这不是精确,但我不确定编码是否明确。例如,

      >>> x.encode('latin1')
      b'\xf0\x9f\xa4\xb7\xf0\x9f\x8f\xbf\xe2\x80\x8d\xe2\x99\x82\xef\xb8\x8f'
      >>> '??‍♂️'.encode()
      b'\xf0\x9f\xa4\xb7\xf0\x9f\x8f\xbf\xe2\x80\x8d\xe2\x99\x82\xef\xb8\x8f'
      >>> '??‍♂️'.encode().decode()
      '??\u200d♂️'
      

      使用 Latin-1 重新编码响应的结果与将所需表情符号编码为 UTF-8 相同,但解码并不能返回完全相同的表情符号(或者至少,Python 没有呈现它同样。)

      【讨论】:

        【解决方案4】:

        首先,你需要一个支持这个的字体。

        您将 Unicode 组合字符与 UTF-8 编码混淆了。它必须是:

        $ echo '{"content":"\u1F937\u200D\u2642"}' | jq -c '.'
        

        $ echo '{"content":"\u1F937\u200D\u2642\uFE0F"}' | jq -c '.'
        

        【讨论】:

        • 问题中的mojibake与fonts无关。而且您的解决方案还遭受到 Unicode 转义序列在到达 jq 之前就被 shell 解释的问题。
        • @deceze shell 不会解释单引号内的任何内容。
        • 告诉我的 zsh...
        • 顺便说一句,这两种方法都不起作用。另一方面,echo '{"content":"\uD83E\uDD37"}' | jq -c . 确实有效(在 bash 上,xpg_echo 关闭)。
        • @deceze echo 本身正在扩展字符串。如果您将原始 JSON 响应存储在文件中,则不会有问题。 (尽管您仍然会遇到 JSON 响应包含 UTF-8 字节而不是正确的 Unicode 代码点的问题。)
        猜你喜欢
        • 2018-09-18
        • 2021-08-16
        • 2021-11-27
        • 2019-08-15
        • 1970-01-01
        • 2010-11-02
        • 2010-09-16
        • 2020-03-15
        相关资源
        最近更新 更多