📜 ⬆️ ⬇️

JSON in Swift 2.0 without anesthesia

Working with JSON is too common and a daily lesson to pay much attention to. Nevertheless, the implementation of some things in Swift looks too complicated and causes toothache every time you see it.

Recently, reading a post about SwiftyVK , I found there a link to an article about OptJSON that allows you to greatly simplify working with JSON in Swift. And although the approach described in the article is really interesting, I still had the feeling that it was still too complicated.

I tried to simplify the OptJSON library a little more, and this is what happened:
')
let obj = json?["workplan"]?["presets"]?[1]?["id"] as? Int 

The source code of the library can be viewed at the link on GitHub :

After downloading the only OptJSON.swift file, I added it to an empty, newly created test project for Apple TV. Xcode cursed that the Swift version in the file was too old and suggested updating the code. I did not mind. In fact, the fixes touched only the removal of hash characters (#).

I also included in the project JSON-config, which I used earlier in another project and tried to write a test code to extract some value in the old-fashioned way:

 if let data = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("config", ofType: "json")!) { do { let obj = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] let a = obj?["workplan"] as? [String: AnyObject] let b = a?["presets"] as? [AnyObject] print(b) let c = b?[1] as? [String: AnyObject] print(c) let d = c?["id"] as? Int print(d) } catch { print("Error!") } } 

Yes, in Objective C it was really much easier. Now we will try to use OptJSON for the same purposes:

If long
 if let data = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("config", ofType: "json")!) { do { let obj = try NSJSONSerialization.JSONObjectWithData(data, options: []) let v = JSON(obj) let a = v?[key:"workplan"] let b = a?[key:"presets"] print(b) let c = b?[index:1] print(c) let d = c?[key:"id"] print(d) } catch { print("Error!") } } 


Or in short:

 if let data = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("config", ofType: "json")!) { do { let obj = try NSJSONSerialization.JSONObjectWithData(data, options: []) let d = JSON(obj)?[key:"workplan"]?[key:"presets"]?[index:1]?[key:"id"] print(d) } catch { print("Error!") } } 

Already not bad! But all the same, the feeling that everything can be made simpler does not leave. And you can! I got into OptJSON.swift and, first of all, I was surprised by the construction:

 public func JSON(object: AnyObject?) -> JSONValue? { if let some: AnyObject = object { switch some { case let null as NSNull: return null case let number as NSNumber: return number case let string as NSString: return string case let array as NSArray: return array case let dict as NSDictionary: return dict default: return nil } } else { return nil } } 

Without hesitation, I replaced it with

 public func JSONValue(object: AnyObject?) -> JSONValue? { if let some = object as? JSONValue { return some } else { return nil } } 

And if there is no difference, why pay more? The next step was to remove the named parameters that are so messy when calling a subscript:

[key:"presets"]?[index:0]?


No sooner said than done! Xcode swore that he didn’t like the return type JSONValue? instead of the expected subscript AnyObject? .. Well, along the way we pull down the wrapper of the returned values ​​in JSON (). The code took on something like the following:

 extension NSArray : JSONValue { public subscript( key: String) -> JSONValue? { return nil } public subscript( index: Int) -> JSONValue? { return index < count && index >= 0 ? self[index] : nil } } extension NSDictionary : JSONValue { public subscript( key: String) -> JSONValue? { return self[key] } public subscript( index: Int) -> JSONValue? { return nil } } 

Having started the project, I understood why the author decided to use the named parameters, namely, the execution of the function went into deep recursion, trying to call self [key]. But in the end, why are the NSArray and NSDictionary classes used for the extension, and the alien objectForKeyedSubscript is used to retrieve the object ?! After all, there are the objectForKey: and objectAtIndex: methods native to these classes: we use them:

 extension NSArray : JSONValue { public subscript( key: String) -> JSONValue? { return nil } public subscript( index: Int) -> JSONValue? { return index < count && index >= 0 ? self.objectAtIndex(index) as? JSONValue : nil } } extension NSDictionary : JSONValue { public subscript( key: String) -> JSONValue? { return self.objectForKey(key) as? JSONValue } public subscript( index: Int) -> JSONValue? { return nil } } 

And once such a binge went, then we basically don’t need the JSON () function for wrapping objects, just enough as? JSONValue. Replace its insides for something more convenient, for example, loading a JSON object from a string, NSData, or from the contents of an NSURL, and at the same time get rid of the need to use a new-fashioned try-catch:

 public func JSON(object: AnyObject?, options: NSJSONReadingOptions = []) -> JSONValue? { let data: NSData if let aData = object as? NSData { data = aData } else if let string = object as? String, aData = string.dataUsingEncoding(NSUTF8StringEncoding) { data = aData } else if let url = object as? NSURL, aData = NSData(contentsOfURL: url) { data = aData } else { return nil } if let json = try? NSJSONSerialization.JSONObjectWithData(data, options: options) { return json as? JSONValue } return nil } 

After these transformations, the final code takes the following form:

 if let v = JSON(NSBundle.mainBundle().URLForResource("config", withExtension: "json")) { let a = v["workplan"] let b = a?["presets"] print(b) let c = b?[1] print(c) let d = c?["id"] print(d) } 

And if shorter, it does:

 let json = JSON(NSBundle.mainBundle().URLForResource("config", withExtension: "json")) let obj = json?["workplan"]?["presets"]?[1]?["id"] as? Int print(obj) 

And no named parameters! Thanks for attention.

View full code of the modified file
 import Foundation public protocol JSONValue: AnyObject { subscript(key: String) -> JSONValue? { get } subscript(index: Int) -> JSONValue? { get } } extension NSNull : JSONValue { public subscript(key: String) -> JSONValue? { return nil } public subscript(index: Int) -> JSONValue? { return nil } } extension NSNumber : JSONValue { public subscript(key: String) -> JSONValue? { return nil } public subscript(index: Int) -> JSONValue? { return nil } } extension NSString : JSONValue { public subscript( key: String) -> JSONValue? { return nil } public subscript( index: Int) -> JSONValue? { return nil } } extension NSArray : JSONValue { public subscript( key: String) -> JSONValue? { return nil } public subscript( index: Int) -> JSONValue? { return index < count && index >= 0 ? self.objectAtIndex(index) as? JSONValue : nil } } extension NSDictionary : JSONValue { public subscript( key: String) -> JSONValue? { return self.objectForKey(key) as? JSONValue } public subscript( index: Int) -> JSONValue? { return nil } } public func JSON(object: AnyObject?, options: NSJSONReadingOptions = []) -> JSONValue? { let data: NSData if let aData = object as? NSData { data = aData } else if let string = object as? String, aData = string.dataUsingEncoding(NSUTF8StringEncoding) { data = aData } else if let url = object as? NSURL, aData = NSData(contentsOfURL: url) { data = aData } else { return nil } if let json = try? NSJSONSerialization.JSONObjectWithData(data, options: options) { return json as? JSONValue } return nil } 

Source: https://habr.com/ru/post/270063/


All Articles