【问题标题】:How to decode a JSON property with different types? [duplicate]如何解码不同类型的 JSON 属性? [复制]
【发布时间】:2018-11-04 16:14:08
【问题描述】:

我有一个 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 协议解析 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)
        }
    }
}

谢谢。

【问题讨论】:

  • 为什么DetailModels 的几乎所有属性都是可选的?仅当它们可能不存在于 JSON 中时才将它们设为可选。此外,将rating 设为Double 并创建自定义init(from:) 方法,如链接问答中所述,以处理从StringDouble 的转换。也不要使用responseJSON,也不要混合使用JSONSerializationJSONDecoder,只需将responseAlamofire取为Data并直接用JSONDecoder解析。使用您当前的代码,您会无缘无故地解析 JSON 两次。
  • 不,您没有按照那里提供的答案进行操作。您应该首先浏览整个答案。然后,您将了解为您的案例实施什么。您刚刚浏览了答案并选择了第一个示例。但是第一个例子并没有真正解决这个问题。阅读整个答案。
  • 谢谢你们,我通过多种方式解决了这个问题:)

标签: json swift alamofire swift4 decodable


【解决方案1】:

您必须实现自己的func encode(to encoder: Encoder) throwsinit(from decoder: Decoder) throws,它们都是Codable 协议的属性。然后将您的 rating 变量更改为 enum

看起来像这样:

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: String 更改为 rating: 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))
    }
}

【讨论】:

  • 谢谢,解决了我的问题!!!
  • 为这个人点赞:D
  • @Zonily Jame,如何在使用此转换后将其 0 或 1 或 2 等评级与 int 进行比较?我无法将枚举与 int 进行比较?谢谢
  • 类型化枚举有 rawValues 你可以从那里开始。
  • @Zonily Jame,这就是我需要的,我今天遇到了同样的问题,花了我很多时间。在我的情况下,评级可能是 object1 或 object2 或 object3,这三种类型包含额外的许多属性rating: Movie or TV or Actor,那么我该如何处理呢?
猜你喜欢
  • 1970-01-01
  • 2022-01-07
  • 2021-10-30
  • 1970-01-01
  • 2017-11-20
  • 2021-08-25
  • 2019-07-28
相关资源
最近更新 更多