json - 如何解码不同类型的 JSON 属性?

标签 json swift alamofire swift4 decodable

<分区>

我有一个JSON

{
"tvShow": {
    "id": 5348,
    "name": "Supernatural",
    "permalink": "supernatural",
    "url": "http://www.episodate.com/tv-show/supernatural",
    "description": "Supernatural is an American fantasy horror television series created by Eric Kripke. It was first broadcast on September 13, 2005, on The WB and subsequently became part of successor The CW's lineup. Starring Jared Padalecki as Sam Winchester and Jensen Ackles as Dean Winchester, the series follows the two brothers as they hunt demons, ghosts, monsters, and other supernatural beings in the world. The series is produced by Warner Bros. Television, in association with Wonderland Sound and Vision. Along with Kripke, executive producers have been McG, Robert Singer, Phil Sgriccia, Sera Gamble, Jeremy Carver, John Shiban, Ben Edlund and Adam Glass. Former executive producer and director Kim Manners died of lung cancer during production of the fourth season.<br>The series is filmed in Vancouver, British Columbia and surrounding areas and was in development for nearly ten years, as creator Kripke spent several years unsuccessfully pitching it. The pilot was viewed by an estimated 5.69 million viewers, and the ratings of the first four episodes prompted The WB to pick up the series for a full season. Originally, Kripke planned the series for three seasons but later expanded it to five. The fifth season concluded the series' main storyline, and Kripke departed the series as showrunner. The series has continued on for several more seasons with Sera Gamble and Jeremy Carver assuming the role of showrunner.",
    "description_source": "http://en.wikipedia.org/wiki/Supernatural_(U.S._TV_series)#Spin-off_series",
    "start_date": "2005-09-13",
    "end_date": null,
    "country": "US",
    "status": "Running",
    "runtime": 60,
    "network": "The CW",
    "youtube_link": "6ZlnmAWL59I",
    "image_path": "https://static.episodate.com/images/tv-show/full/5348.jpg",
    "image_thumbnail_path": "https://static.episodate.com/images/tv-show/thumbnail/5348.jpg",
    "rating": "9.6747",
    "rating_count": "249",
    "countdown": null
}
}

rating在不同连续剧中的取值为Int("rating": 0)或String("rating": "9.6747").

我正在使用 Codable/Decodable 协议(protocol)解析 JSON:

struct DetailModel : Decodable {

var id : Int?
var name : String?
var permalink : String?
var url : String?
var description : String
var description_source : String?
var start_date : String?
var end_date : String?
var country : String?
var status : String?
var runtime : Int?
var network : String?
var youtube_link : String?
var image_path : String?
var image_thumbnail_path : String?
var rating: String
var rating_count : String?
var countdown : String?
var genres : [String]?
var pictures : [String]?
var episodes : [EpisodesModel]?
}

如果 rating == String,我的代码可以工作,并且我拥有来自 JSON 的所有变量,但如果 rating == Int,则所有都是 nil。我应该怎么做才能一次解析所有类型的变量 rating IntString

我的解码函数:

    func searchSerialDetail(id: Int, completion: @escaping (DetailModel?) -> Void){

    let parameters: [String: Any] = ["q": id]

    Alamofire.request(DetailNetworkLayer.url, method: .get, parameters: parameters).response { (jsonResponse) in

        if let jsonValue =  jsonResponse.data {
            let jsonDecoder = JSONDecoder()
                let detail = try? jsonDecoder.decode(DetailModel.self, from: jsonValue)
                completion(detail)
        }
    }
}

谢谢。

最佳答案

您必须实现自己的 func encode(to encoder: Encoder) throwsinit(from decoder: Decoder) throws这是 Codable 的两个属性协议(protocol)。然后更改您的 rating变量转换为枚举

看起来像这样:

enum Rating: Codable {
    case int(Int)
    case string(String)

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .int(let v): try container.encode(v)
        case .string(let v): try container.encode(v)
        }
    }

    init(from decoder: Decoder) throws {
        let value = try decoder.singleValueContainer()

        if let v = try? value.decode(Int.self) {
            self = .int(v)
            return
        } else if let v = try? value.decode(String.self) {
            self = .string(v)
            return
        }

        throw Rating.ParseError.notRecognizedType(value)
    }

    enum ParseError: Error {
        case notRecognizedType(Any)
    }
}

然后在您的 DetailModel 上只需更改 rating: Stringrating: Rating

这行得通,我已经用这些 JSON 字符串进行了测试。

// int rating
{   
    "rating": 200,
    "bro": "Success"
}

// string rating
{
    "rating": "200",
    "bro": "Success"
}

编辑:我找到了一种更好更快捷的实现方式 init(from decoder: Decoder) throws ,这会产生更好的错误消息,通过使用它,您现在可以省略 ParseError枚举。

init(from decoder: Decoder) throws {
    let value = try decoder.singleValueContainer()
    do {
        self = .int(try value.decode(Int.self))
    } catch DecodingError.typeMismatch {
        self = .string(try value.decode(String.self))
    }
}

关于json - 如何解码不同类型的 JSON 属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50526204/

相关文章:

Python/Django - 在服务器上解析 JSON 时出现问题

javascript - 使用Jquery GET输出JSON数据

安卓 M + 改造 + JSON : Can't make field constructor accessible

swift - 您能否仅在另一个 XCTest 完成后才运行 XCTest(如果是这样,是否可以以之前的测试通过为条件)?

php - Alamofire打印“由于错误,无法序列化JSON:”

json - Postman 数组架构验证

ios - AlamofireImage 没有释放内存

ios - 在 swift 中加载和提取 JSON 文件的更有效方法?

ios - Tableview 不重新加载数据

swift - 如何在 Swift 中使用 Alamofireimage 在 Collection View 中下载图像并保持单元格中的图像高度?