迅捷:警卫“让自己=……”的捷径?

时间:2018-11-19 19:40:16

标签: ios swift optional

我正在使用JSON中的数据,我必须将它们解析为一个swift对象。我使用以下代码:

struct MyGPSCoords {

    var latitude:Double
    var longitude:Double
    var accuracy:Int
    var datetime:NSDate

    init?(infobrutFromJson_:[String:String]?)
    {
        guard let infobrut = infobrutFromJson_ else {
            // first time the user sign up to the app, php server returns "null" in Json 
            return nil
        }

        guard
        let lat:Double = Double(infobrut["latitude"] ?? "nil"),
        let lng = Double(infobrut["longitude"] ?? "nil"),
        let acc = Int(infobrut["accuracy"] ?? "nil"),
        let dtm = NSDate(timeIntervalSince1970: Double(infobrut["time"] ?? "nil"))
        else {
            print("warning : unable to parse data from server. Returning nil");
            return nil ; // position not NIL but format not expected => = nil
        }
        self.latitude = lat
        self.longitude = lng
        self.accuracy = acc
        self.datetime = dtm
    }


}

我想使“后卫”声明尽可能短。例如,我加了?? “ nil”,因此如果其中一个键不存在,则Double(“ nil”)= nil并且guard语句可以处理。对于NSDate,我使用方便的init进行了扩展?如果其参数为nil,则返回nil,所以我可以一样。

现在我的问题是,我可以通过直接向self.latitude分配guard语句中的值来使它更短吗?当我尝试这个:

guard self.latitude = Double(infobrut["latitude"] ?? "nil"), ... 

它说不能从Double投射?因此,有什么方法可以使此保护措施更短,并避免我分配lat,lng,acc和dtm缓冲变量?

4 个答案:

答案 0 :(得分:5)

首先,您当然应该尝试修复JSON,因为此JSON格式不正确。字符串不是JSON中的数字。假设您无法更正此损坏的JSON,则所需的工具为flatMap,它将转换T?到T? (这就是卫兵的期望)。

guard
    let lat = infobrut["latitude"].flatMap(Double.init),
    let lng = infobrut["longitude"].flatMap(Double.init),
    let acc = infobrut["accuracy"].flatMap(Int.init),
    let dtm = infobrut["time"].flatMap(TimeInterval.init).flatMap(Date.init(timeIntervalSince1970:))
    else {
        print("warning : unable to parse data from server. Returning nil")
        return nil // position not NIL but format not expected => = nil
}

我看到很多评论认为Codable在这里不起作用,但绝对可以,这确实是您应该使用的。这是一种方法(这对它的错误消息有些草率,但是很简单):

struct MyGPSCoords: Decodable {

    var latitude:Double
    var longitude:Double
    var accuracy:Int
    var datetime:Date

    enum CodingKeys: String, CodingKey {
        case latitude, longitude, accuracy, datetime
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        guard
            let lat = Double(try container.decode(String.self, forKey: .latitude)),
            let lng = Double(try container.decode(String.self, forKey: .longitude)),
            let acc = Int(try container.decode(String.self, forKey: .accuracy)),
            let dtm = TimeInterval(try container.decode(String.self,
                                                        forKey: .datetime)).flatMap(Date.init(timeIntervalSince1970:))
        else {
            throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Could not decode"))
        }

        self.latitude = lat
        self.longitude = lng
        self.accuracy = acc
        self.datetime = dtm
    }

}

或者您可以真正喜欢内部有用的函数,并通过throws的功能摆脱所有临时变量和可选变量。

struct MyGPSCoords: Decodable {

    var latitude:Double
    var longitude:Double
    var accuracy:Int
    var datetime:Date

    enum CodingKeys: String, CodingKey {
        case latitude, longitude, accuracy, datetime
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        func decodeBrokenJSON<T>(_ type: T.Type,
                                 forKey key: CodingKeys) throws -> T
            where T: Decodable & LosslessStringConvertible {
                return try T.init(container.decode(String.self, forKey: key)) ?? {
                    throw DecodingError.dataCorruptedError(forKey: key,
                                                           in: container,
                                                           debugDescription: "Could not decode \(key)")
                    }()
        }

        self.latitude = try decodeBrokenJSON(Double.self, forKey: .latitude)
        self.longitude = try decodeBrokenJSON(Double.self, forKey: .longitude)
        self.accuracy = try decodeBrokenJSON(Int.self, forKey: .accuracy)
        self.datetime = Date(timeIntervalSince1970: try decodeBrokenJSON(TimeInterval.self, forKey: .datetime))
    }

}

(IMO,这是throws真正光芒四射的一个很好的例子,应该比通常更多地使用。)

答案 1 :(得分:3)

其他解决方案似乎过于复杂。简单地做到

struct MyGPSCoords: Codable {

    var latitude: Double?
    var longitude: Double?
    var accuracy: Int?
    var datetime: Date?

    var isValid {
        return [latitude, longitude, accuracy, datetime].filter { $0 == nil }.isEmpty
    }
}

// jsonData is whatever payload you get back from the URL request.
let coords = JSONDecoder().decode(jsonData, type: MyGPSCoords.self)

if !coords.isValid {
    print("warning : unable to parse data from server.")
}

由于所有属性均为Optional,因此如果其中一个或多个属性丢失,解析不会失败。与原始代码中的isValid子句相比,guard let...检查要简单得多。

编辑:如果按照Rob Napier的建议,所有JSON值都编码为String,那么这是构建MyGPSCoords的另一种方式:

struct MyGPSCoords: Codable {

    // These are the Codable properties
    fileprivate var latitudeString: String?
    fileprivate var longitudeString: String?
    fileprivate var accuracyString: String?
    fileprivate var datetimeString: String?

    // Default constant to use as a default check for validity
    let invalid = Double.leastNonzeroMagnitude

    // And these are the derived properties that you want users to use
    var latitude: Double {
        return Double(latitudeString ?? "\(invalid)") ?? invalid
    }

    var longitude: Double {
        return Double(longitudeString ?? "\(invalid)") ?? invalid
    }

    var accuracy: Int {
        return Int(accuracyString ?? "\(invalid)") ?? Int(invalid)
    }

    var date: Date {
        return <whatever-formatter-output-you-need>
    }

    var isValid {
        return [latitudeString, longitudeString, accuracyString, datetimeString].filter { $0 == nil }.isEmpty
               && latitude != invalid && longitude != invalid
               && accuracy != Int(invalid) /* && however you compare dates */
    }
}

答案 2 :(得分:2)

您无法执行的操作。即使错误消息有点误导,编译器已经告诉您了。您可以使用创建新变量的guard let,也可以使用带有布尔表达式的guard。在您的情况下,没有let,因此编译器尝试解析布尔表达式。相反,它会看到分配并产生类型不匹配的错误消息。如果类型匹配(如guard self.latitude = 12.0),则错误消息会更清楚:error: use of '=' in a boolean context, did you mean '=='?

答案 3 :(得分:0)

我知道这个问题很老,但是我不得不承认我不太了解Swift中内置的Decodable / Decoder系统(尤其是“ Container”的概念,无法弄清楚它到底代表了什么) )

无论如何,我制作了自己的解码器,该解码器能够以与Android完全相同的方式处理这种情况(解码JSONObject)。我像这样扩展了Dictionary:

protocol Decodable {
    init(from raw:[String:Any]) throws
}


extension Dictionary where Key == String
{

    enum DecodableError : Error {
        case unknownKey(key:String)
        case keyWrongType(key:String, expectedType:String, actualValue:String)
        case nullValueAtKey(key:String)
    }
    
    func getSafe<T>(_ key:String, forType t:T.Type) throws -> T
    {
        if(self[key] != nil)
        {
            if(self[key] is NSNull) // corresponds to the JSON null value (by experience)
            {
                throw DecodableError.nullValueAtKey(key:key)
            }
            else if(self[key] is T) // for raw type
            {
                return self[key] as! T
            }
            
            // try to parse self[key] to provided type if it's decodable
            else if(self[key] is [String:Any] && t is Decodable.Type)
            {
                return try (t as! Decodable.Type).init(from: self[key] as! [String:Any]) as! T
            }
            
            throw DecodableError.keyWrongType(key: key,
                    expectedType: String(describing: T.self),
                    actualValue: String(describing:self[key]!))
            
        }
            
        throw DecodableError.unknownKey(key:key)
        
    }
    
    func getSafeOpt<T>(_ key:String, forType t:T.Type) throws -> T?
    {
        if(self[key] != nil)
        {
            if(self[key] is NSNull)
            {
                return nil
            }
            return try getSafe(key, forType: t)
        
        }
            
        throw DecodableError.unknownKey(key:key)
    }
    
}

我这样使用它:

struct Position : Decodable {
    
    
    let latitude:Double
    let longitude:Double
    let accuracy:Int?
    let member:Member

    init(from raw:[String:Any]) throws
    {
    
        // getSafe throw exception whenever node are json<null> or if node doesn't exist 
        latitude = try raw.getSafe("lat", forType: Double.self)
        longitude = try raw.getSafe("lng", forType: Double.self)

        // getSafeOpt returns nil if the JSON value of node acc is null, 
        // but it still throw an exception if there is no "acc" node 
        accuracy = try raw.getSafeOpt("acc", forType: Int.self)

        // you can use it to decode other objects that implement my Decodable protocol too : 
        member = try raw.getSafeOpt("member", forType: Member.self)
    }
 } 
 
do {
   try app.position = Position(from: rawDic)
}
catch {
    print("Unable to parse Position : \(error) ")
    return
}

这还不能处理JSON数组,我将在以后进行处理;如果您希望添加JSON数组处理机制,请随时更新我的​​答案。