Facebook在数据导出的JSON文件中使用什么编码?

bwitn5fc  于 2023-10-21  发布在  其他
关注(0)|答案(8)|浏览(126)

我用的是Facebook feature to download all my data。生成的zip文件包含JSON文件中的Meta信息。问题是这些JSON文件中字符串中的Unicode字符以一种奇怪的方式转义。
下面是这样一个字符串的例子:
"nejni\u00c5\u00be\u00c5\u00a1\u00c3\u00ad bod: 0 mnm Ben\u00c3\u00a1tky\n"
例如,当我尝试用JavaScript的JSON.parse()解析字符串并打印出来时,我得到:
"nejnižší bod: 0 mnm Benátky\n"
虽然应当
"nejnižší bod: 0 mnm Benátky\n"
我可以看到\u00c5\u00be应该在某种程度上对应于ž,但我不能找出一般模式。
到目前为止,我已经能够理解这些字符:

'\u00c2\u00b0' : '°',
'\u00c3\u0081' : 'Á',
'\u00c3\u00a1' : 'á',
'\u00c3\u0089' : 'É',
'\u00c3\u00a9' : 'é',
'\u00c3\u00ad' : 'í',
'\u00c3\u00ba' : 'ú',
'\u00c3\u00bd' : 'ý',
'\u00c4\u008c' : 'Č',
'\u00c4\u008d' : 'č',
'\u00c4\u008f' : 'ď',
'\u00c4\u009b' : 'ě',
'\u00c5\u0098' : 'Ř',
'\u00c5\u0099' : 'ř',
'\u00c5\u00a0' : 'Š',
'\u00c5\u00a1' : 'š',
'\u00c5\u00af' : 'ů',
'\u00c5\u00be' : 'ž',

那么这个奇怪的编码是什么呢?有没有已知的工具可以正确解码?

t8e9dugd

t8e9dugd1#

编码是有效的UTF-8。问题是,JavaScript不使用UTF-8,它使用UTF-16。因此,您必须将有效的UTF-8转换为JavaScript UTF-16:

function decode(s) {
   let d = new TextDecoder;
   let a = s.split('').map(r => r.charCodeAt());
   return d.decode(new Uint8Array(a));
}

let s = "nejni\u00c5\u00be\u00c5\u00a1\u00c3\u00ad bod: 0 mnm Ben\u00c3\u00a1tky\n";
s = decode(s);
console.log(s);

https://developer.mozilla.org/docs/Web/API/TextDecoder

uplii1fm

uplii1fm2#

感谢Jen的精彩提问和Shawn的评论。
基本上,facebook似乎采用了unicode字符串表示的每个字节,然后导出为JSON,就好像这些字节是单独的Unicode码点一样。
我们需要做的是把每个六位数的最后两个字符(例如,c3 from \u00c3),将它们连接在一起并作为Unicode字符串读取。
这就是我在Ruby中的做法(参见gist):

require 'json'
require 'uri'

bytes_re = /((?:\\\\)+|[^\\])(?:\\u[0-9a-f]{4})+/

txt = File.read('export.json').gsub(bytes_re) do |bad_unicode|
  $1 + eval(%Q{"#{bad_unicode[$1.size..-1].gsub('\u00', '\x')}"}).to_json[1...-1]
end

good_data = JSON.load(txt)

使用bytes_re,我们可以捕获所有错误的Unicode字符序列。
然后,对于每个序列,将'\u00'替换为'\x'(例如,\xc3),在"周围加上引号,并使用Ruby的内置字符串解析,以便将\xc3\xbe...字符串转换为实际的字节,这些字节稍后将在JSON中保留为Unicode字符或由#to_json方法正确引用。
[1...-1]用于删除#to_json插入的引号
我想解释代码,因为问题不是ruby特定的,读者可能会使用另一种语言。
我想有人可以用一个足够丑陋的sed命令来做这件事。

wkftcu5l

wkftcu5l3#

您可以使用正则表达式查找几乎是unicode的字符组,将它们解码为Latin-1,然后再编码回UTF-8
下面的代码应该在python3.x中工作:

import re

re.sub(r'[\xc2-\xf4][\x80-\xbf]+',lambda m: m.group(0).encode('latin1').decode('utf8'), s)
u4vypkhs

u4vypkhs4#

JSON文件本身是UTF-8,但字符串是UTF-16字符,转换为字节序列,然后使用转义序列转换为UTF-8。
这个命令在Emacs中修复了这样一个文件:

(defun k/format-facebook-backup ()
  "Normalize a Facebook backup JSON file."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (let ((inhibit-read-only t)
          (size (point-max))
          bounds str)
      (while (search-forward "\"\\u" nil t)
        (message "%.f%%" (* 100 (/ (point) size 1.0)))
        (setq bounds (bounds-of-thing-at-point 'string))
        (when bounds
          (setq str (--> (json-parse-string (buffer-substring (car bounds)
                                                              (cdr bounds)))
                         (string-to-list it)
                         (apply #'unibyte-string it)
                         (decode-coding-string it 'utf-8)))
          (setf (buffer-substring (car bounds) (cdr bounds))
                (json-serialize str))))))
  (save-buffer))
ct3nt3jp

ct3nt3jp5#

只是添加了如何从“\u00c5\u0098”到“\”的一般规则。将\u部分的最后两个字母放在一起得到c5和98,这是utf-8表示的两个字节。UTF-8将代码点编码为两个字节,如下所示:110 xxxxx 10 xxxxxx,其中x是字符代码的实际位。你可以把这两个字节,用&来得到x的部分,把它们一个接一个地放在下一个后面,然后把它读成一个数字,你得到0x 158,这是'x'的代码。
我的JavaScript实现:

function fixEncoding(s) {
        var reg = /\\u00([a-f0-9]{2})\\u00([a-f0-9]{2})/gi;
        return s.replace(reg, function(a, m1, m2){
            b1 = parseInt(m1,16);
            b2 = parseInt(m2,16);
            var maskedb1 = b1 & 0x1F;
            var maskedb2 = b2 & 0x3F;
            var result = (maskedb1 << 6) | maskedb2;
            return String.fromCharCode(result);
        })
    }
flvtvl50

flvtvl506#

以防万一,如果有人正在寻找PHP解决方案;)

$result = preg_replace_callback(
    '/\\\u00([[:xdigit:]]{2})/',
    function ($matches) {
        return chr(hexdec($matches[1]));
    },
    $str
);

var_dump($result);

模式如下:“查找以\u00开头并继续到十六进制数字的内容”。然后将这些数字转换为相应的字节。

mzsu5hc0

mzsu5hc07#

如果有人正在寻找GO版本的代码,这里是:

func decode(s string) string {
// Create a slice to hold the individual runes
var runeSlice []rune
// Convert the string to a slice of runes
for _, r := range s {
    runeSlice = append(runeSlice, r)
}

// Create a byte slice from the rune slice
byteSlice := make([]byte, len(runeSlice))
for i, r := range runeSlice {
    byteSlice[i] = byte(r)
}

// Convert the byte slice to a UTF-8 string
utf8String := string(byteSlice)

// Validate that the string is valid UTF-8
if !utf8.ValidString(utf8String) {
    // Handle invalid UTF-8
    fmt.Println("Invalid UTF-8 string")
    return ""
}

return utf8String

}

fgw7neuy

fgw7neuy8#

对于python,有一个名为unicode-escape的编码,它将解析这些\u00xx字符,所以你不需要使用正则表达式。
此外,我们可以使用latin-1编码将其重新编码为纯8位二进制。这种编码是非常明显和天真的编码,它将字符串视为一系列8位字节。然后,当我们解码为UTF-8时,一切又正常了。

import json

with open("broken-fb-encoding.json", "r", encoding="unicode-escape") as f:
    broken_text = f.read()
text = broken_text.encode("latin-1").decode("utf8")
obj = json.loads(text)

相关问题