我正在使用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缓冲变量?
答案 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数组处理机制,请随时更新我的答案。