Декодер Swift JSON не смог найти ключ в данных JSON от Flickr

#ios #json #swift

Вопрос:

Я наткнулся на ошибку декодирования json в примере в книге. Ошибка гласит:

     2021-05-06 07:01:31.193094 1000 Photorama[1562:29734] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Error fetching interesting photos: keyNotFound(CodingKeys(stringValue: "photos", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "photos", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: "photos", intValue: nil) ("photos").", underlyingError: nil))
 

Однако в данных json, возвращенных с Flickr, ключ «фотографии» действительно существовал. Сокращенная копия данных json выглядит следующим образом:

     {
  "extra": {
    "explore_date": "2021-05-04",
    "next_prelude_interval": 57778
  },
  "photos": {
    "page": 1,
    "pages": 5,
    "perpage": 100,
    "total": 500,
    "photo": [
      {
        "id": "51156301899",
        "owner": "138752302@N05",
        "secret": "31d327f54f",
        "server": "65535",
        "farm": 66,
        "title": "*the power of the sun*",
        "ispublic": 1,
        "isfriend": 0,
        "isfamily": 0,
        "datetaken": "2021-04-20 06:52:11",
        "datetakengranularity": "0",
        "datetakenunknown": "0",
        "url_z": "https://live.staticflickr.com/65535/51156301899_31d327f54f_z.jpg",
        "height_z": 380,
        "width_z": 640
      },
      "stat": "ok"
      }
 

Код программы выглядит следующим образом:
AppDelegate.swift (без изменений)

 import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {



    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }


}
 

Сцена удалена.свифт

 import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
        
        let rootViewController = window!.rootViewController as! UINavigationController
        let photosViewController = rootViewController.topViewController as! PhotosViewController
        photosViewController.store = PhotoStore()
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.
    }


}
 

PhotosViewController.swift

 import UIKit

class PhotosViewController: UIViewController {
    
    @IBOutlet private var imageView: UIImageView!
    var store: PhotoStore!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        // store.fetchInterestingPhotos()
        store.fetchInterestingPhotos {
            (photoResult) in
            
            switch photoResult {
            case let .success(photos):
                print("Successfully found (photos.count) photos")
            case let .failure(error):
                print("Error fetching interesting photos: (error)")
            }
        }
        
    }


}
 

FlickrAPI.swift

 import Foundation

enum EndPoint: String {
    case interestingPhotos = "flickr.interestingness.getList"
}
/*
 https://api.flickr.com/services/rest/?method=flickr.interestingness.getList
 amp;api_key=a6d819499131071f158fd740860a5a88amp;extras=url_z,date_taken
 amp;format=jsonamp;nojsoncallback=1
 */
struct FlickrAPI {
    private static let baseURLString = "https://api.flickr.com/services/rest"
    private static let apiKey = "a6d819499131071f158fd740860a5a88"
    
    // assemble url from baseURLString and query items
    private static func flickrURL (endPoint: EndPoint, parameters: [String: String]?) -> URL {
        var components = URLComponents(string: baseURLString)!
        var queryItems = [URLQueryItem]()
        let baseParams = [
            "method": endPoint.rawValue,
            "format": "json",
            "nojsoncallback": "1",
            "api_key": apiKey
        ]
        for (key, value) in baseParams {
            let item = URLQueryItem(name: key, value: value)
            queryItems.append(item)
        }
        if let addtionalParams = parameters {
            for (key, value) in addtionalParams {
                let item = URLQueryItem(name: key, value: value)
                queryItems.append(item)
            }
        }
        components.queryItems = queryItems
        
        return components.url!
    }
    static var interestingPhotoURL: URL {
        // url_z is a URL shortener (zipper) for convenience and beauty
        return flickrURL(endPoint: .interestingPhotos,
                         parameters: ["extras": "url_z,date_taken"])
    }
    static func photos (fromJSON data: Data) -> Result<[Photo], Error> {
        do {
            let decoder = JSONDecoder()
            let flickrResponse = try decoder.decode(FlickrResponse.self, from: data)
    
            return .success(flickrResponse.photosInfo.photos)
        } catch {
            return .failure(error)
        }
    }
}

struct FlickrResponse: Codable {
    //let photos: FlickrPhotosResponse
    let photosInfo: FlickrPhotosResponse
    
    enum CodingKeys: String, CodingKey {
        case photosInfo = "photos"
    }
}

struct FlickrPhotosResponse: Codable {
    //let photo: [Photo]
    let photos: [Photo]
    
    enum CodingKyes: String, CodingKey {
        case photos = "photo"
    }
}
 

PhotoStore.swift

 import Foundation

class PhotoStore {
    private let session: URLSession = {
        let config = URLSessionConfiguration.default
        return URLSession(configuration: config)
    }()
    
    private func processPhotosRequest (data: Data?, error: Error?) ->
    Result<[Photo], Error> {
        guard let jsonData = data else {
            return .failure(error!)
        }
        return FlickrAPI.photos(fromJSON: jsonData)
    }
    
    func fetchInterestingPhotos (completion: @escaping (Result<[Photo], Error>) -> Void) {
        let url = FlickrAPI.interestingPhotoURL
        let request = URLRequest(url: url)
        let task = session.dataTask(with: request) {
            (data, response, error) in
            
        /*if let jsonData = data {
            if let jsonString = String(data: jsonData, encoding: .utf8) {
                print(jsonString)
            }
            } else if let requestError = error {
                print("Error fetching interest photos: (requestError)")
            } else {
                print("Unexpect error with the request")
            }*/
            let result = self.processPhotosRequest(data: data, error: error)
            completion(result)
        }
        task.resume()
    }
}
 

Фото.swift

 import Foundation

class Photo: Codable {
    let title: String
    let remoteURL: URL
    let photoID: String
    let dateTaken: Date
    
    public init (title: String, remoteURL: URL, photoID: String, dateTaken: Date) {
        self.title = title
        self.remoteURL = remoteURL
        self.photoID = photoID
        self.dateTaken = dateTaken
    }
    
    enum CodingKeys: String, CodingKey {
        case title
        case remoteURL = "url_z"
        case photoID = "id"
        case dateTaken = "datetaken"
    }
}
 

Я хотел бы получить совет, в чем проблема и как ее решить. Большое вам спасибо за вашу помощь!

Комментарии:

1. Попробуйте вставить свой ответ API в app.quicktype. ввод и использование сгенерированного кода

2. Вы предоставляете неверный формат JSON в своем вопросе. Кроме того, пожалуйста, предоставьте только код, необходимый для проверки другими. Нет необходимости включать appdelegate, scenedelegate и т. Д., Если это не нужно

3. Вам нужно научиться читать и интерпретировать сообщения об ошибках, в большинстве случаев это не так сложно, и это необходимые знания для разработчика программного обеспечения

Ответ №1:

Файл photos в json не является массивом. Это предмет.

Вставьте свой json в https://app.quicktype.io для того, чтобы получить правильные кодируемые объекты.

 // MARK: - FlickrPhotosResponse
struct FlickrPhotosResponse: Codable {
    let extra: Extra
    let photos: Photos
}

// MARK: - Extra
struct Extra: Codable {
    let exploreDate: String
    let nextPreludeInterval: Int

    enum CodingKeys: String, CodingKey {
        case exploreDate = "explore_date"
        case nextPreludeInterval = "next_prelude_interval"
    }
}

// MARK: - Photos
struct Photos: Codable {
    let page, pages, perpage, total: Int
    let photo: [Photo]
    let stat: String
}

// MARK: - Photo
struct Photo: Codable {
    let id, owner, secret, server: String
    let farm: Int
    let title: String
    let ispublic, isfriend, isfamily: Int
    let datetaken, datetakengranularity, datetakenunknown: String
    let urlZ: String
    let heightZ, widthZ: Int

    enum CodingKeys: String, CodingKey {
        case id, owner, secret, server, farm, title, ispublic, isfriend, isfamily, datetaken, datetakengranularity, datetakenunknown
        case urlZ = "url_z"
        case heightZ = "height_z"
        case widthZ = "width_z"
    }
}