使用jq将表情符号Unicode字节序列转换为Unicode字符



我正在过滤Facebook Messenger JSON转储与jq。源JSON包含Unicode序列的表情符号。我怎么把这些输出回表情符号?

echo '{"content":"u00f0u009fu00a4u00b7u00f0u009fu008fu00bfu00e2u0080u008du00e2u0099u0082u00efu00b8u008f"}' | jq -c '.'

实际结果:

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

预期的结果:

{"content":"🤷🏿‍♂️"}

@chepner在Python中对Latin1的使用终于在我的脑海中彻底震撼了如何直接使用jq。你需要通过iconv:

$ echo '{"content":"u00f0u..."}' | 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的终端然后将解释为UTF-8序列的第一个字节。

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

>>> x = json.load(open("response.json"))['content']
>>> x
'ðx9f¤·ðx9fx8f¿âx80x8dâx99x82ï¸x8f'
>>> x.encode('latin1').decode()
'🤷🏿u200d♂️'

它不是确切的,但我不确定编码是明确的。例如,

>>> x.encode('latin1')
b'xf0x9fxa4xb7xf0x9fx8fxbfxe2x80x8dxe2x99x82xefxb8x8f'
>>> '🤷🏿‍♂️'.encode()
b'xf0x9fxa4xb7xf0x9fx8fxbfxe2x80x8dxe2x99x82xefxb8x8f'
>>> '🤷🏿‍♂️'.encode().decode()
'🤷🏿u200d♂️'

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

这是一个仅限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":"u00f0u009fu00a4u00b7u00f0u009fu008fu00bfu00e2u0080u008du00e2u0099u0082u00efu00b8u008f"}
| .content|= (explode| [utf8_decode] | implode)

记录:

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

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

你混淆了Unicode组合字符和UTF-8编码。它必须是:

$ echo '{"content":"u1F937u200Du2642"}' | jq -c '.'

$ echo '{"content":"u1F937u200Du2642uFE0F"}' | jq -c '.'

相关内容

  • 没有找到相关文章

最新更新