You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift JSON解码报错:hourly_rate键不存在问题求助

解码Laravel API返回JSON时的keyNotFound错误

API返回的JSON数据

[
    {
        "id": 1,
        "name": "instructor",
        "hourly_rate": 35.01,
        "bio": "Patient and experienced driving instructor specialising in new drivers",
        "profile_image": null,
        "rating": 4.8,
        "total_reviews": 25,
        "location": {
            "latitude": 51.5074,
            "longitude": -0.1278
        },
        "qualifications": [
            "ADI Certificate",
            "Grade A Instructor"
        ],
        "years_experience": 5,
        "gender": "female",
        "transmission": "Manual",
        "vehicle_type": null,
        "lesson_types": [
            "standard",
            "intensive",
            "pass_plus"
        ],
        "is_available": true
    }
]

Swift Instructor Codable模型

struct Instructor: Identifiable, Codable {
    let id: Int
    let name: String
    let rating: Double
    let hourlyRate: Double
    let experience: Int
    let profileImage: String?
    let description: String
    let availability: [Availability]
    let gender: InstructorGender
    let lessonTypes: [LessonType]
    let transmission: TransmissionType
    let vehicleType: String?
    let qualifications: [String]
    let isAvailable: Bool
    
    struct Availability: Codable {
        let dayOfWeek: Int
        let startTime: Date
        let endTime: Date
    }
    
    enum TransmissionType: String, Codable {
        case manual = "Manual"
        case automatic = "Automatic"
    }
    
    enum LessonType: String, Codable {
        case standard = "standard"
        case intensive = "intensive"
        case passPlus = "pass_plus"
    }
    
    enum InstructorGender: String, Codable {
        case male
        case female
        
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            let rawValue = try container.decode(String.self).lowercased()
            switch rawValue {
            case "male": self = .male
            case "female": self = .female
            default:
                throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid gender value: \(rawValue)")
            }
        }
    }
    
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case rating
        case hourlyRate = "hourly_rate"
        case experience = "years_experience"
        case profileImage = "profile_image"
        case description = "bio"
        case availability
        case gender
        case lessonTypes = "lesson_types"
        case transmission
        case vehicleType = "vehicle_type"
        case qualifications
        case isAvailable = "is_available"
    }
}

解码器与请求代码

private let jsonDecoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return decoder
    }()
    

func getBookings(token: String) async throws -> [Booking] {
        guard let url = URL(string: "\(baseURL)/bookings") else {
            throw NetworkError.invalidURL
        }

        let data = try await sendAuthenticatedRequestWithData(to: url, method: .get, token: token)

        if let jsonString = String(data: data, encoding: .utf8) {
            print("📱 Raw Bookings JSON:", jsonString)
        }

        do {
            let bookings = try jsonDecoder.decode([Booking].self, from: data)
            print("📱 Successfully decoded \(bookings.count) bookings")
            return bookings
        } catch {
            print("❌ Decoding error: \(error)")
            throw error
        }
    }

错误信息(中文翻译)

未找到键CodingKeys(stringValue: "hourly_rate", intValue: nil),解码路径为[_JSONKey(stringValue: "Index 0", intValue: 0)],调试描述:没有与键"hourly_rate"关联的值。


问题分析与解决方案

核心问题

  1. 键解码策略冲突:同时设置了decoder.keyDecodingStrategy = .convertFromSnakeCase和手动CodingKeys映射,自动转换逻辑与手动映射冲突,导致解码器无法正确匹配字段。
  2. 模型字段与JSON不匹配:Instructor模型中的availability字段在API返回的JSON中不存在,会触发解码失败。
  3. 请求接口与模型不对应:调用的/bookings接口返回的是教练数据,却用[Booking].self解码,完全不匹配。

解决方案

  • 统一键解码策略:保留.convertFromSnakeCase,简化CodingKeys,只保留非自动转换的映射(比如bio→description):

    enum CodingKeys: String, CodingKey {
        case id, name, rating, hourlyRate, experience, profileImage
        case description = "bio"
        case availability, gender, lessonTypes, transmission, vehicleType, qualifications, isAvailable
    }
    

    此时hourly_rate会自动转为hourlyRate,无需手动映射。

  • 处理不存在的字段:将模型中的availability改为可选类型,或直接删除该字段:

    let availability: [Availability]? // 改为可选类型
    
  • 匹配接口与模型:修改请求方法,调用教练数据接口并解码[Instructor].self:

    func getInstructors(token: String) async throws -> [Instructor] {
        guard let url = URL(string: "\(baseURL)/instructors") else {
            throw NetworkError.invalidURL
        }
    
        let data = try await sendAuthenticatedRequestWithData(to: url, method: .get, token: token)
    
        if let jsonString = String(data: data, encoding: .utf8) {
            print("📱 Raw Instructors JSON:", jsonString)
        }
    
        do {
            let instructors = try jsonDecoder.decode([Instructor].self, from: data)
            print("📱 Successfully decoded \(instructors.count) instructors")
            return instructors
        } catch {
            print("❌ Decoding error: \(error)")
            throw error
        }
    }
    

内容的提问来源于stack exchange,提问作者user3524311

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 01:34:57