首页 > 解决方案 > 快速解析 JSON 时出现 typeMismatch 错误

问题描述

我正在调用我的 API 并获得我的模型设置的确切响应。一切都应该正常工作。但由于某种原因,我收到以下错误。

typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "answer", intValue: nil), CodingKeys(stringValue: "quiz", intValue: nil), CodingKeys(stringValue: "category", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a string/data instead.", underlyingError: nil))

我从服务器收到以下响应。

[{"_id":null,"max":1,"answer":{"_id":"612749f150e4691d7d8be86d","wordCount":19,"isDeleted":false,"type":"Real","text":"randome text for 43","quiz":{"_id":"61235bc6d0dd5339b7ff56d3","scheduled_at":"2021-08-31T06:39:30.134Z","timeUsed":96,"type":"Scheduled","isDeleted":false,"category":"61235bc5d0dd5339b7ff563b","name":"Delectus quia architecto voluptates at.","__v":0,"created_at":"2021-08-23T08:26:46.227Z","updated_at":"2021-08-23T08:26:46.227Z"},"user":{"_id":"61235bc4d0dd5339b7ff5600","isBlocked":true,"isDeleted":true,"education":"Clg_Graduate","maretialStatus":"Widowed","gender":"Female","city":"East Jordanmouth","userType":"Normal","password":"$2b$10$YPCcVpX4Osj0UQ5SsqAfAe5lubzCxKn0u.QkWbsg/8OHS.FJuEqNS","email":"Gertrude_Keeling@hotmail.com","lname":"Daniel","fname":"Marcelino","name":"Fae Ullrich","favAnimal":"Rabit","__v":0,"created_at":"2021-08-23T08:26:44.945Z","updated_at":"2021-08-23T08:26:44.945Z"},"created_at":"2021-08-26T07:59:45.439Z","updated_at":"2021-08-26T07:59:45.439Z","__v":0}}]

这些是我的模型

class WinnerByMonth: Codable {
//    let id: String
    let max: Int
    let answer: Answer

    enum CodingKeys: String, CodingKey {
//        case id = "_id"
        case max, answer
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.max = try container.decodeIfPresent(Int.self, forKey: .max) ?? 0
        self.answer = try container.decodeIfPresent(Answer.self, forKey: .answer) ?? Answer()
    }
}

class Answer: Codable {
    let id: String
    let wordCount: Int
    let isDeleted: Bool
    let user: User
    let quiz: Quiz
    let text: String
    let createdAt, updatedAt: String

    enum CodingKeys: String, CodingKey {
        case id = "_id"
        case wordCount, isDeleted, user, quiz, text
        case createdAt = "created_at"
        case updatedAt = "updated_at"
    }
    
    init() {
        self.id = ""
        self.wordCount = 0
        self.user = User()
        self.isDeleted = false
        self.quiz = Quiz()
        self.text = ""
        self.createdAt = ""
        self.updatedAt = ""
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decodeIfPresent(String.self, forKey: .id) ?? ""
        self.wordCount = try container.decodeIfPresent(Int.self, forKey: .wordCount) ?? 0
        self.user = try container.decodeIfPresent(User.self, forKey: .user) ?? User()
        self.isDeleted = try container.decodeIfPresent(Bool.self, forKey: .isDeleted) ?? false
        self.quiz = try container.decodeIfPresent(Quiz.self, forKey: .quiz) ?? Quiz()
        self.text = try container.decodeIfPresent(String.self, forKey: .text) ?? ""
        self.createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
        self.updatedAt = try container.decodeIfPresent(String.self, forKey: .updatedAt) ?? ""
    }
}

class Quiz: Codable {
    let scheduledAt: String
    let timeUsed: Int
    let type: String
    let isDeleted: Bool
    let id: String
    let category: Category
    let name: String
    let createdAt, updatedAt: String

    enum CodingKeys: String, CodingKey {
        case scheduledAt = "scheduled_at"
        case timeUsed, type, isDeleted
        case id = "_id"
        case category, name
        case createdAt = "created_at"
        case updatedAt = "updated_at"
    }
    
    init() {
        self.scheduledAt = ""
        self.timeUsed = 0
        self.type = ""
        self.isDeleted = false
        self.id = ""
        self.category = Category()
        self.name = ""
        self.createdAt = ""
        self.updatedAt = ""
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.scheduledAt = try container.decodeIfPresent(String.self, forKey: .scheduledAt) ?? ""
        self.timeUsed = try container.decodeIfPresent(Int.self, forKey: .timeUsed) ?? 0
        self.type = try container.decodeIfPresent(String.self, forKey: .type) ?? ""
        self.isDeleted = try container.decodeIfPresent(Bool.self, forKey: .isDeleted) ?? false
        self.id = try container.decodeIfPresent(String.self, forKey: .id) ?? ""
        self.category = try container.decodeIfPresent(Category.self, forKey: .category) ?? Category()
        self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
        self.createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
        self.updatedAt = try container.decodeIfPresent(String.self, forKey: .updatedAt) ?? ""
    }
}

class User: Codable {
    var _id: String = ""
    var fname: String = ""
    var lname: String = ""
    var profilePic: String = ""
    var city: String = ""
    var gender: String = ""
    var ageGroup: String = ""
    var martialStatus: String = ""
    var favAnimal: String = ""
    var education: String = ""
    var isProfileCompleted: Bool = false
    var charity: Charity = Charity()
    
    enum CodingKeys: String, CodingKey {
        case _id
        case fname
        case lname
        case profilePic
        case city
        case gender
        case ageGroup = "age"
        case martialStatus = "maretialStatus"
        case favAnimal
        case education
        case isProfileCompleted
    }
    
    init() {
        
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.fname = try container.decodeIfPresent(String.self, forKey: .fname) ?? ""
        self.lname = try container.decodeIfPresent(String.self, forKey: .lname) ?? ""
        self._id = try container.decodeIfPresent(String.self, forKey: ._id) ?? ""
        self.city = try container.decodeIfPresent(String.self, forKey: .city) ?? ""
        self.gender = try container.decodeIfPresent(String.self, forKey: .gender) ?? ""
        self.ageGroup = try container.decodeIfPresent(String.self, forKey: .ageGroup) ?? ""
        self.martialStatus = try container.decodeIfPresent(String.self, forKey: .martialStatus) ?? ""
        self.profilePic = try container.decodeIfPresent(String.self, forKey: .profilePic) ?? ""
        self.favAnimal = try container.decodeIfPresent(String.self, forKey: .favAnimal) ?? ""
        self.education = try container.decodeIfPresent(String.self, forKey: .education) ?? ""
        self.isProfileCompleted = try container.decodeIfPresent(Bool.self, forKey: .isProfileCompleted) ?? false
    }
}

这是我的解析方式

func execute<T: Codable>(endpoint: Endpoint, completion: ((T?, _ E: NetworkStatus.Code) -> Void)?) {
        
        let session = RequestHandler.alamofireManager
        session.request(endpoint.path, method: endpoint.method, parameters: endpoint.params,
                        encoding: endpoint.encoding, headers: nil,
                        interceptor: self).validate().response { (response) in
                            if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
                                print("Data Path: \(endpoint.path)")
                                print("Data: \(utf8Text)")
                            }
                            switch response.result {
                            case .success( _):
                                do {
                                    let result = try JSONDecoder().decode(T.self, from: response.data!)
                                    completion?(result, NetworkStatus.Code.success)
                                } catch {
                                    print(error)
                                    completion?(nil, NetworkStatus.Code.unParsable)
                                }
                            case .failure(let error):
                                if error.responseCode == 401 {
                                    App.isLoggedIn = false
                                    completion?(nil, NetworkStatus.Code.unauthorized)
                                } else {
                                    completion?(nil, NetworkStatus.Code.notFound)
                                }
                            }
        }
    }

这是我调用函数的方式

static func getWinnersByMonth(year: Int, month: Int, completion: @escaping (_ result: [WinnerByMonth]?, _ error: Any?) -> Void) {
        let handler = RequestHandler()
        handler.execute(endpoint: LeaderBoardManger.GetWinnersByMonth(year: year, month: month), completion: completion)
    }

标签: iosjsonswiftcodablejsondecoder

解决方案


错误清楚地表明json 中第一个的category属性包含和字符串 ( ),但您将其取消清除为:quizAnswer"category":"61235bc5d0dd5339b7ff563b"Category

...
    let category: Category // should be String
...

因此,以下 ling 引发了该错误:

...
   self.category = try container.decodeIfPresent(Category.self, forKey: .category) ?? Category() // it exist, but it's not a Category. it is a String
...

推荐阅读