SwiftyJSON/Alamofire 未将字符串解析为 UTF8
SwiftyJSON/Alamofire not parsing string to UTF8
简介
你好!在我的应用程序中,我正在向 YouTubeDataAPI 发出请求。 api 能够使用 UTF8 编码的字符串(包括特殊字符)进行响应。但是,我无法接收数据作为 utf8 数据。
为了将响应数据解析为对象,我使用了Swift的可编码协议。
这是我的请求的样子
enum VideoPart: String {
case snippet = "snippet"
case statistics = "statistics"
case contentDetails = "contentDetails"
}
private static func fetchDetailsAfterSearch(forVideo videoId: String, parts: [VideoPart], onDone: @escaping (JSON) -> Void) {
let videoParts = parts.map({ [=11=].rawValue })
let apiUrl = URL(string: "https://www.googleapis.com/youtube/v3/videos")
let headers: HTTPHeaders = ["X-Ios-Bundle-Identifier": Bundle.main.bundleIdentifier ?? ""]
let parameters: Parameters = ["part": videoParts.joined(separator: ","), "id": videoId, "key": apiKey]
Alamofire.request(apiUrl!, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in
if let responseData = response.data {
onDone(JSON(responseData))
}
}
}
static func searchVideos(forQuery query: String, limit: Int = 20, onDone: @escaping ([YTVideo]) -> Void) {
let apiUrl = URL(string: "https://www.googleapis.com/youtube/v3/search")!
let headers: HTTPHeaders = ["X-Ios-Bundle-Identifier": Bundle.main.bundleIdentifier ?? ""]
let parameters: Parameters = ["q": query, "part": "snippet", "maxResults": limit, "relevanceLanguage": "en", "type": "video", "key": apiKey]
let group = DispatchGroup()
group.enter()
var videos: [YTVideo] = [] // the parsed videos are stored here
Alamofire.request(apiUrl, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in
if let responseData = response.data { // is there a response data?
let resultVideos = JSON(responseData)["items"].arrayValue
resultVideos.forEach({ (v) in // loop through each video and fetch more exact data, based on the videoId
let videoId = v["id"]["videoId"].stringValue
group.enter()
YTDataService.fetchDetailsAfterSearch(forVideo: videoId, parts: [VideoPart.statistics, VideoPart.contentDetails], onDone: {(details) in
// MARK: parse the data of the api to the YTVideo Object
let videoSnippet = v["snippet"]
let videoDetails = details["items"][0]
var finalJSON: JSON = JSON()
finalJSON = finalJSON.merged(other: videoSnippet)
finalJSON = finalJSON.merged(other: videoDetails)
if let video = try? YTVideo(data: finalJSON.rawData()) {
videos.append(video)
}
group.leave()
})
})
group.leave()
}
}
group.notify(queue: .main) {
onDone(videos)
}
}
代码解释:
由于 api 只有 returns 视频的片段,我必须为每个视频发出另一个 api 请求以获取更多详细信息。此 请求 是在 每个视频 的 for 循环中进行的。此调用然后 returns 一个数据对象 解析为 JSON 对象(通过 SwiftyJSON)。
然后将这两个响应合并到一个 JSON 对象中。之后, finalJson
用于初始化一个 YTVideo
对象。正如我已经说过的,class 是可编码的,并根据需要自动解析 json - class 的结构可以在下面找到。
从API发回的数据:
{
"statistics" : {
"favoriteCount" : "0",
"dislikeCount" : "942232",
"likeCount" : "8621179",
"commentCount" : "516305",
"viewCount" : "2816892915"
},
"publishedAt" : "2014-08-18T21:18:00.000Z",
"contentDetails" : {
"caption" : "false",
"licensedContent" : true,
"definition" : "hd",
"duration" : "PT4M2S",
"dimension" : "2d",
"projection" : "rectangular"
},
"channelId" : "UCANLZYMidaCbLQFWXBC95Jg",
"kind" : "youtube#video",
"id" : "nfWlot6h_JM",
"liveBroadcastContent" : "none",
"etag" : "\"8jEFfXBrqiSrcF6Ee7MQuz8XuAM\/ChcYFUcK77KQsdMIp5DyWCHvX9I\"",
"title" : "Taylor Swift - Shake It Off",
"channelTitle" : "TaylorSwiftVEVO",
"description" : "Music video by Taylor Swift performing Shake It Off. (C) 2014 Big Machine Records, LLC. New single ME! (feat. Brendon Urie of Panic! At The Disco) available ...",
"thumbnails" : {
"high" : {
"width" : 480,
"url" : "https:\/\/i.ytimg.com\/vi\/nfWlot6h_JM\/hqdefault.jpg",
"height" : 360
},
"medium" : {
"url" : "https:\/\/i.ytimg.com\/vi\/nfWlot6h_JM\/mqdefault.jpg",
"width" : 320,
"height" : 180
},
"default" : {
"url" : "https:\/\/i.ytimg.com\/vi\/nfWlot6h_JM\/default.jpg",
"width" : 120,
"height" : 90
}
}
}
这是我的YTVideo
class
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let yTVideo = try YTVideo(json)
import Foundation
// MARK: - YTVideo
struct YTVideo: Codable {
let statistics: Statistics
let publishedAt: String
let contentDetails: ContentDetails
let channelID, kind, id, liveBroadcastContent: String
let etag, title, channelTitle, ytVideoDescription: String
let thumbnails: Thumbnails
enum CodingKeys: String, CodingKey {
case statistics, publishedAt, contentDetails
case channelID = "channelId"
case kind, id, liveBroadcastContent, etag, title, channelTitle
case ytVideoDescription = "description"
case thumbnails
}
}
// MARK: YTVideo convenience initializers and mutators
extension YTVideo {
init(data: Data) throws {
self = try newJSONDecoder().decode(YTVideo.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
statistics: Statistics? = nil,
publishedAt: String? = nil,
contentDetails: ContentDetails? = nil,
channelID: String? = nil,
kind: String? = nil,
id: String? = nil,
liveBroadcastContent: String? = nil,
etag: String? = nil,
title: String? = nil,
channelTitle: String? = nil,
ytVideoDescription: String? = nil,
thumbnails: Thumbnails? = nil
) -> YTVideo {
return YTVideo(
statistics: statistics ?? self.statistics,
publishedAt: publishedAt ?? self.publishedAt,
contentDetails: contentDetails ?? self.contentDetails,
channelID: channelID ?? self.channelID,
kind: kind ?? self.kind,
id: id ?? self.id,
liveBroadcastContent: liveBroadcastContent ?? self.liveBroadcastContent,
etag: etag ?? self.etag,
title: title ?? self.title,
channelTitle: channelTitle ?? self.channelTitle,
ytVideoDescription: ytVideoDescription ?? self.ytVideoDescription,
thumbnails: thumbnails ?? self.thumbnails
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - ContentDetails
struct ContentDetails: Codable {
let caption: String
let licensedContent: Bool
let definition, duration, dimension, projection: String
}
// MARK: ContentDetails convenience initializers and mutators
extension ContentDetails {
init(data: Data) throws {
self = try newJSONDecoder().decode(ContentDetails.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
caption: String? = nil,
licensedContent: Bool? = nil,
definition: String? = nil,
duration: String? = nil,
dimension: String? = nil,
projection: String? = nil
) -> ContentDetails {
return ContentDetails(
caption: caption ?? self.caption,
licensedContent: licensedContent ?? self.licensedContent,
definition: definition ?? self.definition,
duration: duration ?? self.duration,
dimension: dimension ?? self.dimension,
projection: projection ?? self.projection
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Statistics
struct Statistics: Codable {
let favoriteCount, dislikeCount, likeCount, commentCount: String
let viewCount: String
}
// MARK: Statistics convenience initializers and mutators
extension Statistics {
init(data: Data) throws {
self = try newJSONDecoder().decode(Statistics.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
favoriteCount: String? = nil,
dislikeCount: String? = nil,
likeCount: String? = nil,
commentCount: String? = nil,
viewCount: String? = nil
) -> Statistics {
return Statistics(
favoriteCount: favoriteCount ?? self.favoriteCount,
dislikeCount: dislikeCount ?? self.dislikeCount,
likeCount: likeCount ?? self.likeCount,
commentCount: commentCount ?? self.commentCount,
viewCount: viewCount ?? self.viewCount
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Thumbnails
struct Thumbnails: Codable {
let high, medium, thumbnailsDefault: Default
enum CodingKeys: String, CodingKey {
case high, medium
case thumbnailsDefault = "default"
}
}
// MARK: Thumbnails convenience initializers and mutators
extension Thumbnails {
init(data: Data) throws {
self = try newJSONDecoder().decode(Thumbnails.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
high: Default? = nil,
medium: Default? = nil,
thumbnailsDefault: Default? = nil
) -> Thumbnails {
return Thumbnails(
high: high ?? self.high,
medium: medium ?? self.medium,
thumbnailsDefault: thumbnailsDefault ?? self.thumbnailsDefault
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Default
struct Default: Codable {
let width: Int
let url: String
let height: Int
}
// MARK: Default convenience initializers and mutators
extension Default {
init(data: Data) throws {
self = try newJSONDecoder().decode(Default.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
width: Int? = nil,
url: String? = nil,
height: Int? = nil
) -> Default {
return Default(
width: width ?? self.width,
url: url ?? self.url,
height: height ?? self.height
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Helper functions for creating encoders and decoders
func newJSONDecoder() -> JSONDecoder {
let decoder = JSONDecoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
decoder.dateDecodingStrategy = .iso8601
}
return decoder
}
func newJSONEncoder() -> JSONEncoder {
let encoder = JSONEncoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
encoder.dateEncodingStrategy = .iso8601
}
return encoder
}
我目前拥有的:
解析和一切工作正常,但 Youtube-Video-Title 未以 utf8 格式显示(见下图)。
我想要的
我必须进行哪些更改才能将来自 YouTube API 的数据显示为有效的 utf8 编码字符串?我尝试了几种 utf8 编码,但其中 none 对我有效:
- Solution 2
如有任何帮助,我们将不胜感激!
这不是 UTF-8 或解析问题。您的代码正确解析并显示了给定的字符串。问题似乎是您使用的字符串是 HTML-encoded。现在,我认为您共享的代码(QuickType 没有为我加载)不足以让我们知道您使用哪些属性来获取 HTML-encoded 视频标题。可能是 plain-text 一个,或者你应该自己处理解码——我无法从文档中得知。
简而言之,如果 HTML-encoded 字符串是您唯一的选择,请查看 decoding HTML entities 而不是 unicode-related 问题。
api 响应确实包含 html 个编码字符。请看下面的截图:
结论:api 文档没有声明返回的文本是纯文本/html 编码。但是,根据演示控制台结果,标题是 html 编码的。
希望对您有所帮助:
extension String {
func htmlToUtf8() -> String{
//chuyển đổi kết quả từ JSON htmlString sang Utf8
let encodedData = self.data(using: .utf8)
let attributedOptions : [NSAttributedString.DocumentReadingOptionKey : Any ] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue ]
do {
let attributedString = try NSAttributedString(data: encodedData!, options: attributedOptions, documentAttributes: nil)
let decodedString = attributedString.string
return decodedString
} catch {
// error ...
}
return String()
}
}
然后:
let jsonTitle = "ERIK - 'Em Kh\U00f4ng Sai, Ch\U00fang Ta Sai' (Official Lyric Video)"
let videoTitle = jsonTitle.htmlToUtf8()
print(videoTitle) //"ERIK - 'Em Không Sai, Chúng Ta Sai' (Official Lyric Video)"
我来自越南,所以我们经常使用 utf8。
简介
你好!在我的应用程序中,我正在向 YouTubeDataAPI 发出请求。 api 能够使用 UTF8 编码的字符串(包括特殊字符)进行响应。但是,我无法接收数据作为 utf8 数据。
为了将响应数据解析为对象,我使用了Swift的可编码协议。
这是我的请求的样子
enum VideoPart: String {
case snippet = "snippet"
case statistics = "statistics"
case contentDetails = "contentDetails"
}
private static func fetchDetailsAfterSearch(forVideo videoId: String, parts: [VideoPart], onDone: @escaping (JSON) -> Void) {
let videoParts = parts.map({ [=11=].rawValue })
let apiUrl = URL(string: "https://www.googleapis.com/youtube/v3/videos")
let headers: HTTPHeaders = ["X-Ios-Bundle-Identifier": Bundle.main.bundleIdentifier ?? ""]
let parameters: Parameters = ["part": videoParts.joined(separator: ","), "id": videoId, "key": apiKey]
Alamofire.request(apiUrl!, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in
if let responseData = response.data {
onDone(JSON(responseData))
}
}
}
static func searchVideos(forQuery query: String, limit: Int = 20, onDone: @escaping ([YTVideo]) -> Void) {
let apiUrl = URL(string: "https://www.googleapis.com/youtube/v3/search")!
let headers: HTTPHeaders = ["X-Ios-Bundle-Identifier": Bundle.main.bundleIdentifier ?? ""]
let parameters: Parameters = ["q": query, "part": "snippet", "maxResults": limit, "relevanceLanguage": "en", "type": "video", "key": apiKey]
let group = DispatchGroup()
group.enter()
var videos: [YTVideo] = [] // the parsed videos are stored here
Alamofire.request(apiUrl, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in
if let responseData = response.data { // is there a response data?
let resultVideos = JSON(responseData)["items"].arrayValue
resultVideos.forEach({ (v) in // loop through each video and fetch more exact data, based on the videoId
let videoId = v["id"]["videoId"].stringValue
group.enter()
YTDataService.fetchDetailsAfterSearch(forVideo: videoId, parts: [VideoPart.statistics, VideoPart.contentDetails], onDone: {(details) in
// MARK: parse the data of the api to the YTVideo Object
let videoSnippet = v["snippet"]
let videoDetails = details["items"][0]
var finalJSON: JSON = JSON()
finalJSON = finalJSON.merged(other: videoSnippet)
finalJSON = finalJSON.merged(other: videoDetails)
if let video = try? YTVideo(data: finalJSON.rawData()) {
videos.append(video)
}
group.leave()
})
})
group.leave()
}
}
group.notify(queue: .main) {
onDone(videos)
}
}
代码解释:
由于 api 只有 returns 视频的片段,我必须为每个视频发出另一个 api 请求以获取更多详细信息。此 请求 是在 每个视频 的 for 循环中进行的。此调用然后 returns 一个数据对象 解析为 JSON 对象(通过 SwiftyJSON)。
然后将这两个响应合并到一个 JSON 对象中。之后, finalJson
用于初始化一个 YTVideo
对象。正如我已经说过的,class 是可编码的,并根据需要自动解析 json - class 的结构可以在下面找到。
从API发回的数据:
{
"statistics" : {
"favoriteCount" : "0",
"dislikeCount" : "942232",
"likeCount" : "8621179",
"commentCount" : "516305",
"viewCount" : "2816892915"
},
"publishedAt" : "2014-08-18T21:18:00.000Z",
"contentDetails" : {
"caption" : "false",
"licensedContent" : true,
"definition" : "hd",
"duration" : "PT4M2S",
"dimension" : "2d",
"projection" : "rectangular"
},
"channelId" : "UCANLZYMidaCbLQFWXBC95Jg",
"kind" : "youtube#video",
"id" : "nfWlot6h_JM",
"liveBroadcastContent" : "none",
"etag" : "\"8jEFfXBrqiSrcF6Ee7MQuz8XuAM\/ChcYFUcK77KQsdMIp5DyWCHvX9I\"",
"title" : "Taylor Swift - Shake It Off",
"channelTitle" : "TaylorSwiftVEVO",
"description" : "Music video by Taylor Swift performing Shake It Off. (C) 2014 Big Machine Records, LLC. New single ME! (feat. Brendon Urie of Panic! At The Disco) available ...",
"thumbnails" : {
"high" : {
"width" : 480,
"url" : "https:\/\/i.ytimg.com\/vi\/nfWlot6h_JM\/hqdefault.jpg",
"height" : 360
},
"medium" : {
"url" : "https:\/\/i.ytimg.com\/vi\/nfWlot6h_JM\/mqdefault.jpg",
"width" : 320,
"height" : 180
},
"default" : {
"url" : "https:\/\/i.ytimg.com\/vi\/nfWlot6h_JM\/default.jpg",
"width" : 120,
"height" : 90
}
}
}
这是我的YTVideo
class
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let yTVideo = try YTVideo(json)
import Foundation
// MARK: - YTVideo
struct YTVideo: Codable {
let statistics: Statistics
let publishedAt: String
let contentDetails: ContentDetails
let channelID, kind, id, liveBroadcastContent: String
let etag, title, channelTitle, ytVideoDescription: String
let thumbnails: Thumbnails
enum CodingKeys: String, CodingKey {
case statistics, publishedAt, contentDetails
case channelID = "channelId"
case kind, id, liveBroadcastContent, etag, title, channelTitle
case ytVideoDescription = "description"
case thumbnails
}
}
// MARK: YTVideo convenience initializers and mutators
extension YTVideo {
init(data: Data) throws {
self = try newJSONDecoder().decode(YTVideo.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
statistics: Statistics? = nil,
publishedAt: String? = nil,
contentDetails: ContentDetails? = nil,
channelID: String? = nil,
kind: String? = nil,
id: String? = nil,
liveBroadcastContent: String? = nil,
etag: String? = nil,
title: String? = nil,
channelTitle: String? = nil,
ytVideoDescription: String? = nil,
thumbnails: Thumbnails? = nil
) -> YTVideo {
return YTVideo(
statistics: statistics ?? self.statistics,
publishedAt: publishedAt ?? self.publishedAt,
contentDetails: contentDetails ?? self.contentDetails,
channelID: channelID ?? self.channelID,
kind: kind ?? self.kind,
id: id ?? self.id,
liveBroadcastContent: liveBroadcastContent ?? self.liveBroadcastContent,
etag: etag ?? self.etag,
title: title ?? self.title,
channelTitle: channelTitle ?? self.channelTitle,
ytVideoDescription: ytVideoDescription ?? self.ytVideoDescription,
thumbnails: thumbnails ?? self.thumbnails
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - ContentDetails
struct ContentDetails: Codable {
let caption: String
let licensedContent: Bool
let definition, duration, dimension, projection: String
}
// MARK: ContentDetails convenience initializers and mutators
extension ContentDetails {
init(data: Data) throws {
self = try newJSONDecoder().decode(ContentDetails.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
caption: String? = nil,
licensedContent: Bool? = nil,
definition: String? = nil,
duration: String? = nil,
dimension: String? = nil,
projection: String? = nil
) -> ContentDetails {
return ContentDetails(
caption: caption ?? self.caption,
licensedContent: licensedContent ?? self.licensedContent,
definition: definition ?? self.definition,
duration: duration ?? self.duration,
dimension: dimension ?? self.dimension,
projection: projection ?? self.projection
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Statistics
struct Statistics: Codable {
let favoriteCount, dislikeCount, likeCount, commentCount: String
let viewCount: String
}
// MARK: Statistics convenience initializers and mutators
extension Statistics {
init(data: Data) throws {
self = try newJSONDecoder().decode(Statistics.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
favoriteCount: String? = nil,
dislikeCount: String? = nil,
likeCount: String? = nil,
commentCount: String? = nil,
viewCount: String? = nil
) -> Statistics {
return Statistics(
favoriteCount: favoriteCount ?? self.favoriteCount,
dislikeCount: dislikeCount ?? self.dislikeCount,
likeCount: likeCount ?? self.likeCount,
commentCount: commentCount ?? self.commentCount,
viewCount: viewCount ?? self.viewCount
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Thumbnails
struct Thumbnails: Codable {
let high, medium, thumbnailsDefault: Default
enum CodingKeys: String, CodingKey {
case high, medium
case thumbnailsDefault = "default"
}
}
// MARK: Thumbnails convenience initializers and mutators
extension Thumbnails {
init(data: Data) throws {
self = try newJSONDecoder().decode(Thumbnails.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
high: Default? = nil,
medium: Default? = nil,
thumbnailsDefault: Default? = nil
) -> Thumbnails {
return Thumbnails(
high: high ?? self.high,
medium: medium ?? self.medium,
thumbnailsDefault: thumbnailsDefault ?? self.thumbnailsDefault
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Default
struct Default: Codable {
let width: Int
let url: String
let height: Int
}
// MARK: Default convenience initializers and mutators
extension Default {
init(data: Data) throws {
self = try newJSONDecoder().decode(Default.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
width: Int? = nil,
url: String? = nil,
height: Int? = nil
) -> Default {
return Default(
width: width ?? self.width,
url: url ?? self.url,
height: height ?? self.height
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
// MARK: - Helper functions for creating encoders and decoders
func newJSONDecoder() -> JSONDecoder {
let decoder = JSONDecoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
decoder.dateDecodingStrategy = .iso8601
}
return decoder
}
func newJSONEncoder() -> JSONEncoder {
let encoder = JSONEncoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
encoder.dateEncodingStrategy = .iso8601
}
return encoder
}
我目前拥有的:
解析和一切工作正常,但 Youtube-Video-Title 未以 utf8 格式显示(见下图)。
我想要的
我必须进行哪些更改才能将来自 YouTube API 的数据显示为有效的 utf8 编码字符串?我尝试了几种 utf8 编码,但其中 none 对我有效:
- Solution 2
如有任何帮助,我们将不胜感激!
这不是 UTF-8 或解析问题。您的代码正确解析并显示了给定的字符串。问题似乎是您使用的字符串是 HTML-encoded。现在,我认为您共享的代码(QuickType 没有为我加载)不足以让我们知道您使用哪些属性来获取 HTML-encoded 视频标题。可能是 plain-text 一个,或者你应该自己处理解码——我无法从文档中得知。
简而言之,如果 HTML-encoded 字符串是您唯一的选择,请查看 decoding HTML entities 而不是 unicode-related 问题。
api 响应确实包含 html 个编码字符。请看下面的截图:
结论:api 文档没有声明返回的文本是纯文本/html 编码。但是,根据演示控制台结果,标题是 html 编码的。
希望对您有所帮助:
extension String {
func htmlToUtf8() -> String{
//chuyển đổi kết quả từ JSON htmlString sang Utf8
let encodedData = self.data(using: .utf8)
let attributedOptions : [NSAttributedString.DocumentReadingOptionKey : Any ] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue ]
do {
let attributedString = try NSAttributedString(data: encodedData!, options: attributedOptions, documentAttributes: nil)
let decodedString = attributedString.string
return decodedString
} catch {
// error ...
}
return String()
}
}
然后:
let jsonTitle = "ERIK - 'Em Kh\U00f4ng Sai, Ch\U00fang Ta Sai' (Official Lyric Video)"
let videoTitle = jsonTitle.htmlToUtf8()
print(videoTitle) //"ERIK - 'Em Không Sai, Chúng Ta Sai' (Official Lyric Video)"
我来自越南,所以我们经常使用 utf8。