Вступление
Привет! В своем приложении я делаю запросы к 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({ $0.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 возвращает только фрагмент видео, мне нужно сделать еще один запрос api для каждого видео, чтобы получить более подробную информацию. Этот запрос выполняется внутри цикла for для каждого видео. Затем этот вызов возвращает объект данных, который анализируется на объект JSON (с помощью SwiftyJSON).
Затем эти два ответа объединяются в один объект JSON. После этого finalJson
используется для инициализации объекта YTVideo
. Как я уже сказал, класс кодируется и автоматически анализирует json в соответствии со своими потребностями - структуру класса можно найти ниже.
Данные, которые отправляются обратно из 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
класс
// 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 (см. Изображение ниже).
Что я хочу
Какие изменения мне нужно внести, чтобы данные из API YouTube отображались в виде допустимой строки в кодировке utf8? Я пробовал несколько кодировок utf8, но у меня ни одна из них не сработала:
SwiftyJSON
, а неCodable
. Оба API по умолчанию правильно декодируют данные в кодировке UTF8. - person vadian   schedule 12.09.2019if let responseData = response.data, let escapedReponseString = String(data: responseData, encoding: .utf8), let responseString = CheckLastLinkToInterpretThem, let dataToUseForSwiftJSON = responseString.encoding(utf8)
- person Larme   schedule 12.09.2019'
не является проблемой кодировки UTF-8: это кодировка HTML. Так что поищите больше в направлении таких ответов: stackoverflow.com/questions/25607247/ - person Kiril S.   schedule 12.09.2019YTVideo
. - person linus_hologram   schedule 12.09.2019