我們在一個項目中創(luan)新(xie)地用JSON來編碼msgpack編碼後的結果(即encoded = json_encode(msgpack_encode(txt))),結果發現Golang側無法解碼。

首先我們可以確定msgpack沒有問題,因為輸入給msgpack解碼數據就與輸入值不一致。

我們使用lua-cjson來編碼一個JSON,因為結果不是printable的,所以在外面加一層base64.encode

結果是eyJhIjoihjEyMyJ9。

在Python里解碼它:

結果是{‘a’: ‘\x86123’}。沒有問題,和輸入一致。

在Go里解碼它:

結果是[239 191 189 49 50 51],可以看到\x86被解碼成了\239 \191 \189即\xefbfbd,表示無效的UTF8字符。

這是因為Go默認採用UTF-8解碼,如果field被標記為string,則json.Unmarshal會使用utf8.DecodeRune來嘗試解碼輸入https://github.com/golang/go/blob/master/src/encoding/json/decode.go#L1304。但在我們的場景中,\x86是一個單字節的非UTF-8字符,所以utf8.DecodeRune返回了utf8.RuneError並把它放到了結果里。

那麼到底是哪裡出了問題呢?

首先,JSON的RFC指出,其中的字符串必須以UTF-8編碼(https://datatracker.ietf.org/doc/html/rfc8259#section-8.1),但是同時也提到,除了幾個特殊的字符外,其中的字符可以被escape也可以不escape(https://datatracker.ietf.org/doc/html/rfc8259#section-7)。所以lua-cjson的這種編碼方式似乎也是合法的?

解決辦法是寫一個自己的Unmarshal方法。首先把結構體中的field標記為自定義類型:

然後我們魔改unquote方法,在原來的基礎上加上對解碼結果是否為utf8.RuneError的判斷

還需要注意的是,Go的json包默認對[]byte類型的field進行base64編解碼:

結果是> [123 34 120 34 58 34 107 119 61 61 34 125] {“x”:”kw==”};同理Unmarshal時也會需要輸入為base64編碼結果。

因此在上面這個解決方法中,我們用rawBytes這個新類型來alias到[]byte,而並不直接使用[]byte類型再在之後自己解碼。

發了一個issue:https://github.com/golang/go/issues/51094。

另外的JSON庫沒有這個問題,測試了https://github.com/json-iterator/go 和 https://github.com/bytedance/sonic