JSON in Swift 2.0 without anesthesia
Working with JSON is too much of a daily routine to give it a lot of attention. However, the implementation of some things in Swift seems too complicated and causes toothache every time you see it.
Recently, while reading a post about SwiftyVK , I found a link there to an article about OptJSON , which greatly simplifies working with JSON in Swift. And although the approach described in the article is really interesting, the feeling that it’s all too complicated is not left me.
I tried to simplify the OptJSON library a bit more, and this is what happened:
The source code of the library can be viewed on the link to GitHub :
After downloading the only OptJSON.swift file, I added it to the empty, just created test project for Apple TV. Xcode swore that the version of Swift in the file was too old and suggested updating the code. I did not mind. In fact, the corrections concerned only the removal of hash characters (#).
I also included a JSON-config in the project, which I used earlier in another project and tried to write test code to retrieve some old-fashioned value:
Yes, in Objective-C it really was many times easier. Now try using OptJSON for the same purpose:
Or in short:
Already not bad! But all the same, the feeling that everything can be done easier does not leave. And you can! I climbed into OptJSON.swift and, first of all, the construction surprised me:
Without hesitation, I replaced it with
And if there is no difference, why pay more? The next step was to remove the named parameters, which were so furious when calling subscript: It is said - done! Xcode swore that he doesn't like the return type of JSONValue? instead of the expected subscript, AnyObject? .. Well, along the way, we demolish the wrapper of the returned values in JSON (). The code took about the following form:
By launching the project, I realized why the author decided to use named parameters, namely, the execution of the function went into deep recursion, trying to call self [key]. But in the end, why the NSArray and NSDictionary classes are used for expansion, and the alien objectForKeyedSubscript for extracting an object ?! After all, there are methods native for these classes objectForKey: and objectAtIndex :, use them:
And since such a booze went, then we basically do not need the JSON () function for wrapping objects, is it enough for a simple as? JSONValue. Replace its internals for something more convenient, for example, loading a JSON object from a string, NSData or from the contents of NSURL, and at the same time get rid of the need to use a new-fangled try-catch:
After these transformations, the final code takes the following form:
And if shorter, then at all:
And no named parameters! Thanks for attention.
Recently, while reading a post about SwiftyVK , I found a link there to an article about OptJSON , which greatly simplifies working with JSON in Swift. And although the approach described in the article is really interesting, the feeling that it’s all too complicated is not left me.
I tried to simplify the OptJSON library a bit more, and this is what happened:
let obj = json?["workplan"]?["presets"]?[1]?["id"] as? Int
The source code of the library can be viewed on the link to GitHub :
After downloading the only OptJSON.swift file, I added it to the empty, just created test project for Apple TV. Xcode swore that the version of Swift in the file was too old and suggested updating the code. I did not mind. In fact, the corrections concerned only the removal of hash characters (#).
I also included a JSON-config in the project, which I used earlier in another project and tried to write test code to retrieve some old-fashioned value:
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 really was many times easier. Now try using OptJSON for the same purpose:
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 done easier does not leave. And you can! I climbed into OptJSON.swift and, first of all, the construction surprised me:
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, which were so furious when calling subscript: It is said - done! Xcode swore that he doesn't like the return type of JSONValue? instead of the expected subscript, AnyObject? .. Well, along the way, we demolish the wrapper of the returned values in JSON (). The code took about the following form:
[key:"presets"]?[index:0]?
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 }
}
By launching the project, I realized why the author decided to use named parameters, namely, the execution of the function went into deep recursion, trying to call self [key]. But in the end, why the NSArray and NSDictionary classes are used for expansion, and the alien objectForKeyedSubscript for extracting an object ?! After all, there are methods native for these classes objectForKey: and objectAtIndex :, 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 since such a booze went, then we basically do not need the JSON () function for wrapping objects, is it enough for a simple as? JSONValue. Replace its internals for something more convenient, for example, loading a JSON object from a string, NSData or from the contents of NSURL, and at the same time get rid of the need to use a new-fangled 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, then at all:
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 the complete 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
}