我正在try 返回JSON数据,但我不知道如何操作.我收到这样的错误信息:"无法将类型为‘[PLANT]’的返回表达式转换为返回类型‘PLANT’".我有点理解这个错误消息,它意味着我无法将一种类型转换为另一种类型.但不确定如何解决这个问题.请看我的代码如下:

import UIKit
import SwiftUI

struct PlantResponse: Codable {
    let data : [Plant]
}

struct Plant: Codable {
    let id: Int
    let commonName: String         // camelCase
    let slug: String
    let scientificName: String     // camelCase
    let year: Int
    let bibliography: String
    let author: String
    let status: String
    let rank: String
    let familyCommonName: String   // camelCase
    let family: String
    let genusId: Int               // camelCase
    let genus: String
    let imageUrl: String           // camelCase
    let synonyms: [String]         // an array of strings
    let links: Links               // a custom type, defined below
    let Plant: [Plant]
}

extension Plant {
    struct Links: Codable {
        let `self`: String
        let plant: String
        let genus: String
    }
}

struct ContentView: View {
    @State var plant: Plant?
    
    var body: some View {
        VStack(alignment: .leading) {
            if let plant {
                Text("There is data")
                    .font(.title)
            } else {
                Text("No data available")
            }
        }
        .padding(20.0)
        .task {
            do {
                plant = try await fetchPlantsFromAPI()
            } catch {
                plant = nil
            }
        }
    }
}

func fetchPlantsFromAPI() async throws -> Plant {
    let url = URL(string: "https://trefle.io/api/v1/plants? token=d321f518jTdU1t-doZQif3jpzzW9V0mk3nLnDssF1vY&filter[common_name]=beach%20strawberry")!
    do {
   
        let (data, _) = try await URLSession.shared.data(from: url)

        let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
        let decoded = try! decoder.decode(PlantResponse.self, from: data)
        print(decoded.data)
        return decoded.data //Cannot convert return expression of type '[Plant]' to return type 'Plant'
    } catch {
        throw error
    }
}

#Preview {
    ContentView()
}

任何帮助都将不胜感激.

推荐答案

错误是指出API返回的数组为Plant,即[Plant],因此请相应更改方法签名:

func fetchPlantsFromAPI() async throws -> [Plant] {…}

然后将@State变量改为数组:

struct ContentView: View {
    @State var plants: [Plant] = []

    var body: some View {
        List(plants) { plant in
            Text(plant.commonName)
        }
        .padding(20.0)
        .task {
            do {
                plants = try await fetchPlantsFromAPI()
            } catch {
                print(error)
                plants = []
            }
        }
    }
}

我宣布PlantIdentifiableSendable:

struct Plant: Codable, Identifiable, Sendable {…}

测试这个,我看到了一些额外的问题:

  1. 我会使用URLComponents来确保查询正确转义:

    func fetchPlantsFromAPI() async throws -> [Plant] {
        guard var components = URLComponents(string: "https://trefle.io/api/v1/plants") else {
            throw URLError(.badURL)
        }
    
        components.queryItems = [
            URLQueryItem(name: "token", value: "…"),
            URLQueryItem(name: "filter[common_name]", value: "beach strawberry")
        ]
    
        guard let url = components.url else {
            throw URLError(.badURL)
        }
    
        let (data, _) = try await URLSession.shared.data(from: url)
    
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        let decoded = try decoder.decode(PlantResponse.self, from: data)
        return decoded.data
    }
    
  2. 我会避免try!.正如你在上面修改过的例子中看到的,try就足够了.如果出现编码错误,您不希望应用程序崩溃.

  3. 如果您在catch中所做的一切只是重新抛出错误,那么在do-try-catch中没有意义.同样,我已经从上面的第一点中删除了这一点.

  4. 您已将Plant属性添加到Plant类型.你应该删除:

    struct Plant: Codable, Identifiable, Sendable {
        let id: Int
        let commonName: String         // camelCase
        let slug: String
        let scientificName: String     // camelCase
        let year: Int
        let bibliography: String
        let author: String
        let status: String
        let rank: String
        let familyCommonName: String   // camelCase
        let family: String
        let genusId: Int               // camelCase
        let genus: String
        let imageUrl: String           // camelCase
        let synonyms: [String]         // an array of strings
        let links: Links               // a custom type, defined below
    //    let Plant: [Plant]
    }
    

    如果我们看一下JSON,植物数组中没有Plant键:

    {
        "data": [
            {
                "id": 263319,
                "common_name": "Beach strawberry",
                "slug": "fragaria-chiloensis",
                "scientific_name": "Fragaria chiloensis",
                "year": 1768,
                "bibliography": "Gard. Dict. ed. 8 : n.° 4 (1768)",
                "author": "(L.) Mill.",
                "status": "accepted",
                "rank": "species",
                "family_common_name": "Rose family",
                "genus_id": 12147,
                "image_url": "https://bs.plantnet.org/image/o/8ee87e6f94833055db1c7df5fc07761852b7b1eb",
                "synonyms": [
                    "Fragaria vesca var. chiloensis",
                    "Potentilla chiloensis"
                ],
                "genus": "Fragaria",
                "family": "Rosaceae",
                "links": {
                    "self": "/api/v1/species/fragaria-chiloensis",
                    "plant": "/api/v1/plants/fragaria-chiloensis",
                    "genus": "/api/v1/genus/fragaria"
                }
            }
        ],
        "links": {
            "self": "/api/v1/plants?filter%5Bcommon_name%5D=beach+strawberry",
            "first": "/api/v1/plants?filter%5Bcommon_name%5D=beach+strawberry\u0026page=1",
            "last": "/api/v1/plants?filter%5Bcommon_name%5D=beach+strawberry\u0026page=1"
        },
        "meta": {
            "total": 1
        }
    }
    
  5. 我会解码API错误,这样我们就可以优雅地处理它们.例如,

    struct ErrorResponse: Codable {
        let error: Bool
        let messages: String
    }
    
    enum PlantError: Error {
        case apiError(Int, String)
        case invalidResponse(URLResponse)
    }
    
    func fetchPlantsFromAPI() async throws -> [Plant] {
        guard var components = URLComponents(string: "https://trefle.io/api/v1/plants") else {
            throw URLError(.badURL)
        }
    
        components.queryItems = [
            URLQueryItem(name: "token", value: "…"),
            URLQueryItem(name: "filter[common_name]", value: "beach strawberry")
        ]
    
        guard let url = components.url else {
            throw URLError(.badURL)
        }
    
        let (data, response) = try await URLSession.shared.data(from: url)
    
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
    
        guard let httpResponse = response as? HTTPURLResponse else {
            throw PlantError.invalidResponse(response)
        }
    
        guard 200...299 ~= httpResponse.statusCode else {
            let errorObject = try decoder.decode(ErrorResponse.self, from: data)
            throw PlantError.apiError(httpResponse.statusCode, errorObject.messages)
        }
    
        return try decoder
            .decode(PlantResponse.self, from: data)
            .data
    }
    
  6. FWIW,我倾向于使API代码泛型,这样它就可以与任何端点一起工作(这样我们就不必到处重复这些代码).也许:

    struct SuccessResponse<T: Decodable>: Decodable {
        let data: T
    }
    
    struct ErrorResponse: Decodable {
        let error: Bool
        let messages: String
    }
    
    struct Plant: Codable, Identifiable, Sendable {
        let id: Int
        let commonName: String         // camelCase
        let slug: String
        let scientificName: String     // camelCase
        let year: Int
        let bibliography: String
        let author: String
        let status: String
        let rank: String
        let familyCommonName: String   // camelCase
        let family: String
        let genusId: Int               // camelCase
        let genus: String
        let imageUrl: String           // camelCase
        let synonyms: [String]         // an array of strings
        let links: Plant.Links         // a custom type, defined below
    }
    
    extension Plant {
        struct Links: Codable {
            let `self`: String
            let plant: String
            let genus: String
        }
    }
    
    enum ApiError: Error {
        case apiError(Int, String)
        case invalidResponse(URLResponse)
    }
    
    func urlForPlantSearch(_ string: String) throws -> URL {
        guard var components = URLComponents(string: "https://trefle.io/api/v1/plants") else {
            throw URLError(.badURL)
        }
    
        components.queryItems = [
            URLQueryItem(name: "token", value: apiToken),
            URLQueryItem(name: "filter[common_name]", value: string)
        ]
    
        guard let url = components.url else {
            throw URLError(.badURL)
        }
    
        return url
    }
    
    func fetchPlantsFromAPI() async throws -> [Plant] {
        let url = try urlForPlantSearch("beach strawberry")
    
        return try await fetchFromAPI(url: url)
    }
    
    // Make this generic so we can reuse this code with any query
    
    private func fetchFromAPI<T: Decodable>(url: URL) async throws -> T {
        let (data, response) = try await URLSession.shared.data(from: url)
    
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
    
        guard let httpResponse = response as? HTTPURLResponse else {
            throw ApiError.invalidResponse(response)
        }
    
        guard 200...299 ~= httpResponse.statusCode else {
            let errorObject = try decoder.decode(ErrorResponse.self, from: data)
            throw ApiError.apiError(httpResponse.statusCode, errorObject.messages)
        }
    
        return try decoder
            .decode(SuccessResponse<T>.self, from: data)
            .data
    }
    

Swift相关问答推荐

运行异步任务串行运行

如何在AudioKit中实现自定义音效 node ?

使用MKLocalSearch获取坐标

String.Index encodedOffset已弃用,但建议的替代方案不起作用

在NavigationStack上设置拐角半径:SwiftUI中的无响应内容视图区

swift:只要框架名称中有一个带有框架名称的公共实体,就指定框架中公共 struct 的路径

';NSInternal不一致异常';,原因:';可见导航栏Xcode 15.0 Crash请求布局

当将新值推送到 NavigationStack 时,SwiftUI 的 navigationDestination 已在堆栈的早期声明

子视图未在父视图 SwiftUI 中触发禁用状态更新

从 actor 的 init 方法调用方法

创建 ViewModel 时 iOS 模拟器上的黑屏

单击按钮后的计时器发布者初始化计时器

Reality Composer Tap Trigger 问题

如何将使用\u{ea12}创建的 Swift 字符串转换回其十六进制值/字符串ea12?

VStack SwiftUI 中的动态内容高度

Swift 2.0 方法不能标记为@objc,因为参数的类型不能在 Objective-C 中表示

将项目引用添加到 Swift iOS XCode 项目并调试

如何快速截取 UIView 的屏幕截图?

Swift 3:小数到 Int

如何在 Swift 中重写 setter