这是我的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
}

但是这个代码打印ÐоÑогой дÑÑг

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+043 E,UTF-8编码为D0 BE.可以肯定的是,JSON中的编码标题有\u00d0\u00be作为下一组值.因此,Instagram似乎将字符串编码为UTF-8,同时使用\uxxxx个转义字符.至少对于西里尔字符.空格被编码为常规空格字符.

问题是,JSONDecoder期望如果字符串包含表单\uxxxx中的转义字符,则它假定代码是Unicode值,而不是UTF-8编码的一部分.当它解析标题时,它首先看到\u00d0.这是Unicode字符Ð.然后它看到了\u0094.这是Unicode字符"Cancel Character",一个不可打印的字符.这种情况继续下go ,你最终得到"ÐоÑогой дÑÑг".

JSONDecoder没有内置的功能来告诉它如何处理Instagram的非标准字符串编码.因此,这意味着唯一的解决方案是编写一个定制的解码器.

以下是一个可行的解决方案.按如下方式更新您的Media struct :

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
    }
}

下面是一个可以在playground 上运行的完整示例:

struct BlogPost: Codable {
    let media: [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 ?? ""
    }
}

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)
    }
}

let instagramJSON = """
[
  {
    "media": [
      {
        "title" : "\\u00d0\\u0094\\u00d0\\u00be\\u00d1\\u0080\\u00d0\\u00be\\u00d0\\u00b3\\u00d0\\u00be\\u00d0\\u00b9 \\u00d0\\u00b4\\u00d1\\u0080\\u00d1\\u0083\\u00d0\\u00b3"
      }
    ]
  }
]
"""

let badData = instagramJSON.data(using: .utf8)!
let result = try JSONDecoder().decode([BlogPost].self, from: badData)
print(result[0].media[0].title)

输出:

亲爱的朋友


请注意,此解决方案适用于所提供的示例.Instagram对某些字符的编码方式可能会导致该解决方案在某些情况下失败.如果没有更多的数据,我就不能确定.如果您遇到此代码不能正确处理的示例,请发表带有相关详细信息的 comments .

Swift相关问答推荐

SWIFT并发:合并Taskgroup和AsyncStream?

在`NavigationStack`中使用`SafeAreaInset`修饰符时出现SwiftUI异常行为

不能将符合协议的SWIFT类的实例分配给需要该协议的Objective-C属性

如何在macOS中延迟退出应用程序的退出操作?

仅当 boolean 为真时才实现方法(application:didReceiveRemoteNotification)

从 iPhone 中的安全飞地获取真正的随机数?

用户输入以更改通过点击生成的形状大小

我如何从 UIAlertController 导航到新屏幕(swiftUI)

如何仅将 SwiftUI 不透明度应用于父视图?

在 SwiftUI 中,如何在 UIView 内或作为 UIView 使用 UIHostingController?

iOS Swift - 如何更改表格视图的背景 colored颜色 ?

在 iOS 中使用 Swift 保存 PDF 文件并显示它们

是 swift 中另一个日期的同一周、月、年的日期

iOS/Swift:如何检测 UITextField 上的touch 动作

如何从一个可观察的数组创建一个可观察的数组?

如何快速识别字符串中的大写和小写字符?

swift 3 如何获取明天和昨天的日期(注意特殊情况)新月或新年

如何在 Swift 中重写 setter

在 Swift 中从服务器播放视频文件

UITableView 布局在 push segue 和 return 上搞砸了. (iOS 8、Xcode beta 5、Swift)