Swift 4元组迭代

时间:2018-12-21 10:40:01

标签: swift

我正在尝试在Swift 4中通过json从Bing检索墙纸

https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=en-US

但是images属性是带有其他对象的元组。我无法迭代此元组并达到url值,以便可以将它们收集到数组中。

(
        {
        bot = 1;
        copyright = "For the winter solstice, Santa Fe's Farolito Walk (\U00a9 Julien McRoberts/Danita Delimont)";
        copyrightlink = "http://www.bing.com/search?q=santa+fe+new+mexico&form=hpcapt&filters=HpDate:%2220181221_0800%22";
        drk = 1;
        enddate = 20181222;
        fullstartdate = 201812210800;
        hs =         (
        );
        hsh = 6ba85f1fbab1b9a290a19af763ca404d;
        quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181221_AdobeSantaFe%22&FORM=HPQUIZ";
        startdate = 20181221;
        title = "All is bright in Santa Fe";
        top = 1;
        url = "/az/hprichbg/rb/AdobeSantaFe_EN-US4037753534_1920x1080.jpg";
        urlbase = "/az/hprichbg/rb/AdobeSantaFe_EN-US4037753534";
        wp = 1;
    },
        {
        bot = 1;
        copyright = "Nabana-no-Sato gardens at Nagashima Spa Land in Kuwana, Japan (\U00a9 Julian Krakowiak/Alamy)";
        copyrightlink = "http://www.bing.com/search?q=nabana+no+sato+nagashima+spa+land&form=hpcapt&filters=HpDate:%2220181220_0800%22";
        drk = 1;
        enddate = 20181221;
        fullstartdate = 201812200800;
        hs =         (
        );
        hsh = eda366ca6eee5c653a59d8f16a54ae63;
        quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181220_WinterIllumination%22&FORM=HPQUIZ";
        startdate = 20181220;
        title = "Winter illuminations in Nabana-no-Sato";
        top = 1;
        url = "/az/hprichbg/rb/WinterIllumination_EN-US0071328313_1920x1080.jpg";
        urlbase = "/az/hprichbg/rb/WinterIllumination_EN-US0071328313";
        wp = 0;
    },
        {
        bot = 1;
        copyright = "The Charles Bridge in Prague, Czech Republic (\U00a9 borchee/E+/Getty Images)";
        copyrightlink = "http://www.bing.com/search?q=prague&form=hpcapt&filters=HpDate:%2220181219_0800%22";
        drk = 1;
        enddate = 20181220;
        fullstartdate = 201812190800;
        hs =         (
        );
        hsh = 37c19c3f45952fd81c429e5e92c386e9;
        quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181219_PragueChristmas%22&FORM=HPQUIZ";
        startdate = 20181219;
        title = "Snow falls on Bohemia";
        top = 1;
        url = "/az/hprichbg/rb/PragueChristmas_EN-US8649790921_1920x1080.jpg";
        urlbase = "/az/hprichbg/rb/PragueChristmas_EN-US8649790921";
        wp = 1;
    },
        {
        bot = 1;
        copyright = "For the anniversary of the premiere of 'The Nutcracker,' a scene of the Moscow Ballet performing the popular dance (\U00a9 Tytus Zmijewski/Epa/Shutterstock)";
        copyrightlink = "http://www.bing.com/search?q=the+nutcracker&form=hpcapt&filters=HpDate:%2220181218_0800%22";
        drk = 1;
        enddate = 20181219;
        fullstartdate = 201812180800;
        hs =         (
        );
        hsh = b6303e82458de5692e94b26cb332fbd1;
        quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181218_NutcrackerSeason%22&FORM=HPQUIZ";
        startdate = 20181218;
        title = "A holiday tradition is born";
        top = 1;
        url = "/az/hprichbg/rb/NutcrackerSeason_EN-US8373379424_1920x1080.jpg";
        urlbase = "/az/hprichbg/rb/NutcrackerSeason_EN-US8373379424";
        wp = 0;
    },
        {
        bot = 1;
        copyright = "Wilbur Wright gliding down Big Kill Devil Hill in Kitty Hawk, North Carolina (\U00a9 Library of Congress)";
        copyrightlink = "http://www.bing.com/search?q=wright+brothers+first+flight&form=hpcapt&filters=HpDate:%2220181217_0800%22";
        drk = 1;
        enddate = 20181218;
        fullstartdate = 201812170800;
        hs =         (
        );
        hsh = 5a132d06c5e7b66f0a1ec9f434a0dca1;
        quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181217_WrightGlider%22&FORM=HPQUIZ";
        startdate = 20181217;
        title = "Wright brothers fly into history";
        top = 1;
        url = "/az/hprichbg/rb/WrightGlider_EN-US10185286591_1920x1080.jpg";
        urlbase = "/az/hprichbg/rb/WrightGlider_EN-US10185286591";
        wp = 0;
    },
        {
        bot = 1;
        copyright = "Holiday decorations on a canal in Murano, Italy (\U00a9 John Warburton-Lee/DanitaDelimont.com)";
        copyrightlink = "http://www.bing.com/search?q=murano+island&form=hpcapt&filters=HpDate:%2220181216_0800%22";
        drk = 1;
        enddate = 20181217;
        fullstartdate = 201812160800;
        hs =         (
        );
        hsh = c4cc26f7a803502632d940f9466e2b7c;
        quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181216_MuranoChristmas%22&FORM=HPQUIZ";
        startdate = 20181216;
        title = "Murano aglow";
        top = 1;
        url = "/az/hprichbg/rb/MuranoChristmas_EN-US10759540271_1920x1080.jpg";
        urlbase = "/az/hprichbg/rb/MuranoChristmas_EN-US10759540271";
        wp = 1;
    },
        {
        bot = 1;
        copyright = "The Stoneman Bridge on the Merced River in Yosemite National Park (\U00a9 Ron_Thomas/E+/Getty Images)";
        copyrightlink = "http://www.bing.com/search?q=yosemite+national+park&form=hpcapt&filters=HpDate:%2220181215_0800%22";
        drk = 1;
        enddate = 20181216;
        fullstartdate = 201812150800;
        hs =         (
        );
        hsh = 6a49f8fc62b09ce83305ac0a13000a7a;
        quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181215_YosemiteBridge%22&FORM=HPQUIZ";
        startdate = 20181215;
        title = "Season of solitude in Yosemite";
        top = 1;
        url = "/az/hprichbg/rb/YosemiteBridge_EN-US10544416282_1920x1080.jpg";
        urlbase = "/az/hprichbg/rb/YosemiteBridge_EN-US10544416282";
        wp = 1;
    },
        {
        bot = 1;
        copyright = "A female northern cardinal (\U00a9 Matthew Studebaker/Minden Pictures)";
        copyrightlink = "http://www.bing.com/search?q=northern+cardinal&form=hpcapt&filters=HpDate:%2220181214_0800%22";
        drk = 1;
        enddate = 20181215;
        fullstartdate = 201812140800;
        hs =         (
        );
        hsh = 0dcf20ffcfd5413167161ad20b412fb5;
        quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181214_CardinalBerries%22&FORM=HPQUIZ";
        startdate = 20181214;
        title = "The Christmas Bird Count begins";
        top = 1;
        url = "/az/hprichbg/rb/CardinalBerries_EN-US11262203078_1920x1080.jpg";
        urlbase = "/az/hprichbg/rb/CardinalBerries_EN-US11262203078";
        wp = 1;
    }
)

如何在Swift 4中处理此JSON元组?

2 个答案:

答案 0 :(得分:1)

quicktype.io的帮助下,让我们使用以下结构:

struct Result: Codable {
    let images: [Image]
    let tooltips: Tooltips
}

struct Image: Codable {
    let startdate, fullstartdate, enddate, url: String
    let urlbase, copyright: String
    let copyrightlink: String
    let title, quiz: String
    let wp: Bool
    let hsh: String
    let drk, top, bot: Int
    let hs: [JSONAny]
}

struct Tooltips: Codable {
    let loading, previous, next, walle: String
    let walls: String
}

// MARK: Encode/decode helpers

class JSONNull: Codable, Hashable {

    public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
        return true
    }

    public var hashValue: Int {
        return 0
    }

    public init() {}

    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if !container.decodeNil() {
            throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encodeNil()
    }
}

class JSONCodingKey: CodingKey {
    let key: String

    required init?(intValue: Int) {
        return nil
    }

    required init?(stringValue: String) {
        key = stringValue
    }

    var intValue: Int? {
        return nil
    }

    var stringValue: String {
        return key
    }
}

class JSONAny: Codable {
    let value: Any

    static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
        let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
        return DecodingError.typeMismatch(JSONAny.self, context)
    }

    static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
        let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
        return EncodingError.invalidValue(value, context)
    }

    static func decode(from container: SingleValueDecodingContainer) throws -> Any {
        if let value = try? container.decode(Bool.self) {
            return value
        }
        if let value = try? container.decode(Int64.self) {
            return value
        }
        if let value = try? container.decode(Double.self) {
            return value
        }
        if let value = try? container.decode(String.self) {
            return value
        }
        if container.decodeNil() {
            return JSONNull()
        }
        throw decodingError(forCodingPath: container.codingPath)
    }

    static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
        if let value = try? container.decode(Bool.self) {
            return value
        }
        if let value = try? container.decode(Int64.self) {
            return value
        }
        if let value = try? container.decode(Double.self) {
            return value
        }
        if let value = try? container.decode(String.self) {
            return value
        }
        if let value = try? container.decodeNil() {
            if value {
                return JSONNull()
            }
        }
        if var container = try? container.nestedUnkeyedContainer() {
            return try decodeArray(from: &container)
        }
        if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
            return try decodeDictionary(from: &container)
        }
        throw decodingError(forCodingPath: container.codingPath)
    }

    static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
        if let value = try? container.decode(Bool.self, forKey: key) {
            return value
        }
        if let value = try? container.decode(Int64.self, forKey: key) {
            return value
        }
        if let value = try? container.decode(Double.self, forKey: key) {
            return value
        }
        if let value = try? container.decode(String.self, forKey: key) {
            return value
        }
        if let value = try? container.decodeNil(forKey: key) {
            if value {
                return JSONNull()
            }
        }
        if var container = try? container.nestedUnkeyedContainer(forKey: key) {
            return try decodeArray(from: &container)
        }
        if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
            return try decodeDictionary(from: &container)
        }
        throw decodingError(forCodingPath: container.codingPath)
    }

    static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
        var arr: [Any] = []
        while !container.isAtEnd {
            let value = try decode(from: &container)
            arr.append(value)
        }
        return arr
    }

    static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
        var dict = [String: Any]()
        for key in container.allKeys {
            let value = try decode(from: &container, forKey: key)
            dict[key.stringValue] = value
        }
        return dict
    }

    static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
        for value in array {
            if let value = value as? Bool {
                try container.encode(value)
            } else if let value = value as? Int64 {
                try container.encode(value)
            } else if let value = value as? Double {
                try container.encode(value)
            } else if let value = value as? String {
                try container.encode(value)
            } else if value is JSONNull {
                try container.encodeNil()
            } else if let value = value as? [Any] {
                var container = container.nestedUnkeyedContainer()
                try encode(to: &container, array: value)
            } else if let value = value as? [String: Any] {
                var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
                try encode(to: &container, dictionary: value)
            } else {
                throw encodingError(forValue: value, codingPath: container.codingPath)
            }
        }
    }

    static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
        for (key, value) in dictionary {
            let key = JSONCodingKey(stringValue: key)!
            if let value = value as? Bool {
                try container.encode(value, forKey: key)
            } else if let value = value as? Int64 {
                try container.encode(value, forKey: key)
            } else if let value = value as? Double {
                try container.encode(value, forKey: key)
            } else if let value = value as? String {
                try container.encode(value, forKey: key)
            } else if value is JSONNull {
                try container.encodeNil(forKey: key)
            } else if let value = value as? [Any] {
                var container = container.nestedUnkeyedContainer(forKey: key)
                try encode(to: &container, array: value)
            } else if let value = value as? [String: Any] {
                var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
                try encode(to: &container, dictionary: value)
            } else {
                throw encodingError(forValue: value, codingPath: container.codingPath)
            }
        }
    }

    static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
        if let value = value as? Bool {
            try container.encode(value)
        } else if let value = value as? Int64 {
            try container.encode(value)
        } else if let value = value as? Double {
            try container.encode(value)
        } else if let value = value as? String {
            try container.encode(value)
        } else if value is JSONNull {
            try container.encodeNil()
        } else {
            throw encodingError(forValue: value, codingPath: container.codingPath)
        }
    }

    public required init(from decoder: Decoder) throws {
        if var arrayContainer = try? decoder.unkeyedContainer() {
            self.value = try JSONAny.decodeArray(from: &arrayContainer)
        } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
            self.value = try JSONAny.decodeDictionary(from: &container)
        } else {
            let container = try decoder.singleValueContainer()
            self.value = try JSONAny.decode(from: container)
        }
    }

    public func encode(to encoder: Encoder) throws {
        if let arr = self.value as? [Any] {
            var container = encoder.unkeyedContainer()
            try JSONAny.encode(to: &container, array: arr)
        } else if let dict = self.value as? [String: Any] {
            var container = encoder.container(keyedBy: JSONCodingKey.self)
            try JSONAny.encode(to: &container, dictionary: dict)
        } else {
            var container = encoder.singleValueContainer()
            try JSONAny.encode(to: &container, value: self.value)
        }
    }
}

我们可以通过以下方式获取url属性:

//Create the URL
guard let url = URL(string: "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=en-US") else {
    fatalError("Invalid URL")
}

//Fetch the data from the URL
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) -> Void in
    guard let jsonData = data, error == nil else {
        fatalError("Data error")
    }

    let jsonDecoder = JSONDecoder()

    do {
        let result = try jsonDecoder.decode(Result.self, from: jsonData)
        let images = result.images
        let urls = images.map { $0.url }
        urls.forEach { print($0) }
    }
    catch {
        print(error)
    }
}).resume()

此打印:

/az/hprichbg/rb/AdobeSantaFe_EN-US4037753534_1920x1080.jpg
/az/hprichbg/rb/WinterIllumination_EN-US0071328313_1920x1080.jpg
/az/hprichbg/rb/PragueChristmas_EN-US8649790921_1920x1080.jpg
/az/hprichbg/rb/NutcrackerSeason_EN-US8373379424_1920x1080.jpg
/az/hprichbg/rb/WrightGlider_EN-US10185286591_1920x1080.jpg
/az/hprichbg/rb/MuranoChristmas_EN-US10759540271_1920x1080.jpg
/az/hprichbg/rb/YosemiteBridge_EN-US10544416282_1920x1080.jpg
/az/hprichbg/rb/CardinalBerries_EN-US11262203078_1920x1080.jpg

答案 1 :(得分:1)

那不是元组。 这就是NSArray的打印方式(这就是description的实现结果)。更像是Objective-C。

( 
 content...
)

我强烈建议不要在生产中使用强行解包(使用!),而改用if let / guard let(因为如果这样做会导致崩溃,展开失败)。我使用了强制展开更直接,就是这样,说明了逻辑。

let dataJSON = initialJSONData
//Your JSON is a Dictionary at top level
let json = try! JSONSerialization.jsonObject(with: dataJSON, options: []) as! [String: Any]
//The value of a images key is an Array of Dictionaries
let imagesArray = json["images"] as! [[String: Any]]
let imagesURLStr = imagesArray.flatMap{ $0["url"] as? String }

现在,如果您使用的是Swift 4+,我建议您使用Codable:

struct Result: Codable {
    let images: [Image]

    struct Image: Codable {
        let url: String
    }
}

要调用它:

let jsonDecoder = JSONDecoder()
let result = try! jsonDecoder.decode(Result.self, from: dataJSON)
let images = result.images
let imagesURLStr = images.map{ $0.url }

侧面说明:
此代码未经测试,仅在此处编写,因此可能会出现拼写错误,这是一个较小的编译器问题,但是它不应是不可修复的。