这是我的 Instagram 帐户备份的一部分
[
{
"media": [
{
"title": "\u00d0\u0094\u00d0\u00be\u00d1\u0080\u00d0\u00be\u00d0\u00b3\u00d0\u00be\u00d0\u00b9 \u00d0\u00b4\u00d1\u0080\u00d1\u0083\u00d0\u00b3"
}
]
}
]
为了解析这个,我使用
Codable
struct BlogPost: Codable {
let media: [Media]
}
struct Media: Codable {
let title: String
}
但是此代码打印 ÐÐ⁄ÑÐ⁄гÐ⁄Ð1 дÑÑг
let bundle = Bundle.main
let path = bundle.path(forResource: "posts_1", ofType: "json")
let content = try? String(contentsOfFile: path!)
let data = content!.data(using: .utf8)!
let result = try? JSONDecoder().decode([BlogPost].self, from: data)
print(result![0].media[0].title)
它应该打印 Дорогой друг。如何在 iOS 上解码该字符串?我还使用 mothereff.in 来解码备份数据。
让我们首先总结一些细节。 Instagram 正在将字符串
"Дорогой друг"
编码为 "\u00d0\u0094\u00d0\u00be\u00d1\u0080\u00d0\u00be\u00d0\u00b3\u00d0\u00be\u00d0\u00b9 \u00d0\u00b4\u00d1\u0080\u00d1\u0083\u00d0\u00b3"
让我们看看这意味着什么。
Д
是 Unicode 字符 U+0414。它的 UTF-8 编码为 D0 94
。请注意,JSON 中的编码标题以 \u00d0\u0094
开头。那么 о
就是 Unicode 字符 U+043E,UTF-8 编码为 D0 BE
。果然,JSON 中的编码标题将 \u00d0\u00be
作为下一组值。因此,Instagram 似乎将字符串编码为 UTF-8,同时使用 \uxxxx
转义字符。至少对于西里尔字母来说是这样。空格被编码为常规空格字符。
问题在于
JSONDecoder
期望如果字符串包含 \uxxxx
形式的转义字符,它会假定代码是 Unicode 值,而不是 UTF-8 编码的一部分。当它解析标题时,它首先看到\u00d0
。这就是 Unicode 字符 Ð
。然后它看到\u0094
。这就是 Unicode 字符“CANCEL CHARACTER”,一个不可打印的字符。继续下去,您最终会得到 "ÐоÑогой дÑÑг"
。
JSONDecoder
没有内置功能来告诉它如何处理 Instagram 的非标准字符串编码。所以这意味着唯一的解决方案是编写一个自定义解码器。
这是一个可行的解决方案。按如下方式更新您的
Media
结构:
struct Media: Codable {
let title: String
init(title: String) {
self.title = title
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let str = try container.decode(String.self, forKey: .title)
let data = Data(str.reduce([], { partialResult, char in
char.unicodeScalars.reduce(into: partialResult) { partialResult, scalar in
partialResult.append(UInt8(scalar.value))
}
}))
let res = String(data: data, encoding: .utf8)
self.title = res ?? "" // some fallback as desired
}
}
如果只有一个值需要处理,这很好。如果您需要为多个属性处理此问题,请将逻辑移至
String
扩展:
extension String {
var fromInstagramEncoding: String? {
let data = Data(self.reduce([], { partialResult, char in
char.unicodeScalars.reduce(into: partialResult) { partialResult, scalar in
partialResult.append(UInt8(scalar.value))
}
}))
return String(data: data, encoding: .utf8)
}
}
那么更新后的
Media
代码就变成:
struct Media: Codable {
let title: String
init(title: String) {
self.title = title
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let str = try container.decode(String.self, forKey: .title)
self.title = str.fromInstagramEncoding ?? "" // some fallback as desired
}
}
请注意,此解决方案适用于提供的示例。 Instagram 对某些字符的编码方式可能导致此解决方案在某些情况下可能会失败。如果没有更多数据,我无法确定。如果您遇到此代码无法正确处理的示例,请发表包含相关详细信息的评论。