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:

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
}


Also popular now: