I have to parse json server response into a swift object. I use this code :
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
}
}
I want to make the "guard" statement as short as possible. First, I added ?? "nil"
so if one of the keys doesn't exist, Double("nil")
gets nil
and guard statement can handle. For NSDate, I made an extension with a convenience init?
returning nil if its input is nil, so I can do the same.
My question is, can i do it even shorter by assigning directly to self.latitude the values right in the guard
statement ? I tried that :
guard self.latitude = Double(infobrut["latitude"] ?? "nil"), ...
It says it cannot cast from Double?
to Double
. Is there any way to make this guard even shorter and avoiding me to assign lat, lng, acc and dtm buffering variables ?
First, you should of course try to fix the JSON, since this JSON is malformed. Strings are not numbers in JSON. Assuming you cannot correct this broken JSON, the tool you want is flatMap
, which converts T?? to T? (which is what guard-let expects).
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
}
I saw a lot of comments that Codable won't work here, but it absolutely will, and it's really what you should use. Here's one way (this is a little sloppy about its error messages, but it's simple):
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
}
}
Or you can get really fancy with an internal helpful function and get rid of all the temporary variables and optionals through the power of 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, this is a great example of how throws
really shines and should be used much more than it commonly is.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With