有没有办法更简洁地使用保护声明?

时间:2017-04-24 02:33:53

标签: swift

我使用Gloss进行JSON实例化。这是一个示例类:

public class MyObj: Decodable
{
    let id_user         : String
    let contact_addr1   : String
    let contact_addr2   : String?
    let contact_city    : String
    let contact_state   : String
    let contact_zip     : String
    let points          : Int

    // Deserialization
    required public init?(json: JSON)
    {
        guard let id_user : String = "somekey" <~~ json else {
            assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
            return nil
        }
        guard let contact_addr1 : String = "somekey" <~~ json else {
            assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
            return nil
        }
        guard let contact_city : String = "somekey" <~~ json else {
            assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
            return nil
        }
        guard let contact_state : String = "somekey" <~~ json else {
            assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
            return nil
        }
        guard let contact_zip : String = "somekey" <~~ json else {
            assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
            return nil
        }
        guard let points : Int = "somekey" <~~ json else {
            assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
            return nil
        }

        self.id_user        = id_user
        self.contact_addr1  = contact_addr1
        self.contact_addr2  = "somekey"     <~~ json
        self.contact_city   = contact_city
        self.contact_state  = contact_state
        self.contact_zip    = contact_zip
        self.contact_points = points
    }
}

我有很多模型类。他们之间有数百名成员。为每个人编写一个多行保护语句真的让我的代码变得简单。有什么方法可以将保护功能封装成更简洁的东西吗?也许是一个函数或类似的东西:

shortGuard("memberName", "jsonKey")

也许有办法防范一串字符串键?

2 个答案:

答案 0 :(得分:1)

有很多方法可以实现这一目标。它们都归结为编写一个包装函数来将键映射到值。以下是我想到的一些简单示例,但正如我所说,根据您的追求,有很多方法可以做到这一点:

enum JSONError: Error {
    case keyNotFound(String)
}

extension JSON {
    func values<T>(for keys: [String]) throws -> [T] {
        var values = [T]()

        for key in keys {
            guard let value: T = key <~~ self else {
                throw JSONError.keyNotFound(key)
            }

            values.append(value)
        }

        return values
    }

    func values<T>(for keys: [String], closure: ((_ key: String, _ value: T) -> Void)) throws {
        for key in keys {
            guard let value: T = key <~~ self else {
                throw JSONError.keyNotFound(key)
            }

            closure(key, value)
        }
    }
}

首先验证所有密钥,然后才能使用它们中的任何一个,如果没有密钥则会抛出。您可以像这样使用它:

do {
    let keys = ["foo", "bar"]

    // The type of the values constant is important.
    // In this example we're saying look for values of type Int.
    let values: [Int] = try json.values(for: keys)

    for (index, key) in keys.enumerated() {
        print("value for \(key): \(values[index])")
    }
} catch JSONError.keyNotFound(let key) {
    assertionFailure("key not found \(key)")
}

第二个将key,value对传递给一个闭包,因为它们出现在你的keys数组中,并且会抛出它找不到的第一个闭包。

do {
    let keys = ["foo", "bar"]

    // The type of the closure's value argument is important.
    // In this example we're saying look for values of type String.
    try json.values(for: keys) { (key, value: String) in
        print("value for key \(key) is \(value)")
    }
} catch JSONError.keyNotFound(let key) {
    assertionFailure("key not found \(key)")
}

使用init?()函数中的第一个版本,我们有类似的内容:

public struct MyObj: Decodable {
    public let id_user         : String
    public let contact_addr1   : String
    public let contact_addr2   : String?
    public let points          : Int

    public init?(json: S) {
        do {
            let stringKeys = ["id_user", "contact_addr1"]
            let stringValues: [String] = try json.values(for: stringKeys)

            id_user = stringValues[0]
            contact_addr1 = stringValues[1]

            // this isn't required, so just extract with no error if it fails
            contact_addr2 = "contact_addr2" <~~ json

            let intKeys = ["points"]
            let intValues: [Int] = try json.values(for: intKeys)

            points = intValues[0]
        } catch JSONError.keyNotFound(let key) {
            assertionFailure("key \(key) not found in JSON")
            return nil
        } catch {
            return nil
        }
    }
}

答案 1 :(得分:1)

我没有使用过Gloss,考虑到它很简单,可以安全地解析JSON而不需要额外的库,或者使用不熟悉的语法。

选项1:

您可以在一个guard语句中对可选的展开进行分组。

示例:

public struct MyObj {
    let id_user         : String
    let contact_addr1   : String
    let contact_addr2   : String?
    let points          : Int

    public init?(json: Any) {
        guard 
            let entities = json as? [String : Any],
            let id_user = entities["some key"] as? String,
            let contact_addr1 = entities["some key"] as? String,
            let points = entities["some key"] as? Int
        else {
            assertionFailure("...")
            return nil
        }

        self.id_user        = id_user
        self.contact_addr1  = contact_addr1
        self.contact_addr2  = entities["some key"] as? String
        self.contact_points = points
    }
}

选项2:

另一种方法是完全消除guard语句,让解析器在解析过程中抛出错误,并使用可选的try将结果转换为nil。

示例:

// Helper object for parsing values from a dictionary. 
// A similar pattern could be used for arrays. i.e. array.stringAt(10)
struct JSONDictionary {
    let values: [String : Any]
    init(_ json: Any) throws {
        guard let values = json as? [String : Any] else {
            throw MyError.expectedDictionary
        }
        self.values = values
    }

    func string(_ key: String) throws -> String {
        guard let value = values[key] as? String else {
            throw MyError.expectedString(key)
        }
        return value
    }

    func integer(_ key: String) throws -> Int {
        guard let value = values[key] as? Int else {
            throw MyError.expectedInteger(key)
        }
        return value
    }
}

分析器:

public struct MyObj {
    let id_user         : String
    let contact_addr1   : String
    let contact_addr2   : String?
    let points          : Int

    public init(json: Any) throws {
        // Instantiate the helper object. 
        // Ideally the JSONDictionary would be passed by the caller.
        let dictionary = try JSONDictionary(json),
        self.id_user = try dictionary.string("some key"),
        self.contact_addr1 = try dictionary.string("some key"),
        self.points = try dictionary.integer("some key")

        // Results in an optional if the string call throws an exception
        self.contact_addr2 = try? dictionary.string("some key")
    }
}

用法:

// Instantiate MyObj from myJSON. 
// myObject will be nil if parsing fails.
let myObject = try? MyObj(json: myJSON)