Showing posts with label JSON. Show all posts
Showing posts with label JSON. Show all posts

Saturday, January 27, 2018

Catching nil as Error

Github gist.

I've posted this as an answer on stackoverflow before, and the information was highly appreciated by some people. The question was like: “How to catch any error, specially unexpectedly found nil in Swift?" Though the author was asking about catching system errors, I decided to post my super-handy Unwrap Snippet there.
struct UnwrapError: Error, CustomStringConvertible { let optional: T? public var description: String { return "Found nil while unwrapping \(String(describing: optional))!" } } public func unwrap(_ optional: T?) throws -> T { if let real = optional { return real } else { throw UnwrapError(optional: optional) } }
The idea of this approach is to replace multiple if let and guard let statements with a single do-try-catch block. You can:
do { // Parse JSON and assign variables which were defined somewhere above let dictionary = try unwrap(JSONSerialization.jsonObject( with: data, options: .allowFragments) as? [String: Any]) isAdsEnabled = try unwrap(dictionary["isAdsEnabled"] as? Bool) // While calling function which require a non-optional parameter in one line of code imageView.image = try UIImage(data: unwrap(receivedData)) // And also you can simplify the building of multipart data var data = Data() data += try unwrap(dispositionString.data(using: .utf8)) data += someContent data += try unwrap("\r\n".data(using: .utf8)) } catch error { // Handle error // ... // and exit return nil }
Those who make apps with web backend will definitely like.

Friday, January 19, 2018

Safer parsing with JSONSerialization in Swift

Github gist.

Most people use the following snippet to get a value from an object provided by the JSONSerialization:
guard let name = jsonDictionary["name"] as? String else { return } ...
It's not obvious, but this code is unsafe. It can easily fail if the "name" value will be "111". In this case it may be decoded as NSNumber and casting to String will always fail. The same thing with numbers:
guard let id = jsonDictionary["id"] as? Int else { return } // id can be a "3" String! ... A value like "3" can be decoded both as NSString and NSNumber (more often).

I didn't find any tutorial that would take this issue into account. All of them recommend to use as? to unknown JSON object to basic type. So I post here the correct snippets that will provide stable and predictable results in all possible cases.

JSON helpers

extension String { init?(jsonObject: Any?) { guard jsonObject is NSNull == false else { return nil } let aNSObject = jsonObject as? NSObject if let description = aNSObject?.description { self = description } else { return nil } } } extension Int { var boolValue: Bool { return self != 0 } init?(jsonObject: Any?) { if let number = jsonObject as? NSNumber { self = number.intValue } else if let string = jsonObject as? NSString { self = string.integerValue } else { return nil } } } extension Bool { init?(jsonObject: Any?) { if let integer = Int(jsonObject: jsonObject) { self = integer.boolValue } else { return nil } } } extension Double { init?(jsonObject: Any?) { if let number = jsonObject as? NSNumber { self = number.doubleValue } else if let string = jsonObject as? NSString { self = string.doubleValue } else { return nil } } }
All objects produced by JSONSerialization are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.

How to Record Calls on iPhone