Last active
November 10, 2017 14:49
-
-
Save jarsen/672aa5969689c5864cac to your computer and use it in GitHub Desktop.
JaSON Playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
// | |
// MARK: - JSONError Type | |
// | |
public enum JSONError: ErrorType, CustomStringConvertible { | |
case KeyNotFound(key: String) | |
case NullValue(key: String) | |
case TypeMismatch(expected: Any, actual: Any) | |
case TypeMismatchWithKey(key: String, expected: Any, actual: Any) | |
public var description: String { | |
switch self { | |
case let .KeyNotFound(key): | |
return "Key not found: \(key)" | |
case let .NullValue(key): | |
return "Null Value found at: \(key)" | |
case let .TypeMismatch(expected, actual): | |
return "Type mismatch. Expected type \(expected). Got '\(actual)'" | |
case let .TypeMismatchWithKey(key, expected, actual): | |
return "Type mismatch. Expected type \(expected) at key: \(key). Got '\(actual)'" | |
} | |
} | |
} | |
// | |
// MARK: - JSONValue | |
// | |
public protocol JSONValue { | |
typealias Value = Self | |
static func JSONValue(object: Any) throws -> Value | |
} | |
extension JSONValue { | |
public static func JSONValue(object: Any) throws -> Value { | |
guard let objectValue = object as? Value else { | |
throw JSONError.TypeMismatch(expected: Value.self, actual: object.dynamicType) | |
} | |
return objectValue | |
} | |
} | |
// | |
// MARK: - JSONValue Implementations | |
// | |
extension String: JSONValue {} | |
extension Int: JSONValue {} | |
extension UInt: JSONValue {} | |
extension Float: JSONValue {} | |
extension Double: JSONValue {} | |
extension Bool: JSONValue {} | |
extension Array where Element: JSONValue { | |
public static func JSONValue(object: Any) throws -> [Element] { | |
guard let anyArray = object as? [AnyObject] else { | |
throw JSONError.TypeMismatch(expected: self, actual: object.dynamicType) | |
} | |
return try anyArray.map { try Element.JSONValue($0) as! Element } | |
} | |
} | |
extension Dictionary: JSONValue { | |
public static func JSONValue(object: Any) throws -> [Key: Value] { | |
guard let objectValue = object as? [Key: Value] else { | |
throw JSONError.TypeMismatch(expected: self, actual: object.dynamicType) | |
} | |
return objectValue | |
} | |
} | |
extension NSURL: JSONValue { | |
public static func JSONValue(object: Any) throws -> NSURL { | |
guard let urlString = object as? String, objectValue = NSURL(string: urlString) else { | |
throw JSONError.TypeMismatch(expected: self, actual: object.dynamicType) | |
} | |
return objectValue | |
} | |
} | |
extension JSONObject: JSONValue { | |
public static func JSONValue(object: Any) throws -> JSONObject { | |
guard let dictionary = object as? JSONDictionary else { | |
throw JSONError.TypeMismatch(expected: self, actual: object.dynamicType) | |
} | |
return JSONObject(dictionary: dictionary) | |
} | |
} | |
// | |
// MARK: - JSONObjectConvertible | |
// | |
public protocol JSONObjectConvertible : JSONValue { | |
typealias ConvertibleType = Self | |
init(json: JSONObject) throws | |
} | |
extension JSONObjectConvertible { | |
public static func JSONValue(object: Any) throws -> ConvertibleType { | |
guard let jsonDict = object as? JSONDictionary else { | |
throw JSONError.TypeMismatch(expected: JSONDictionary.self, actual: object.dynamicType) | |
} | |
let json = JSONObject(dictionary: jsonDict) | |
guard let value = try self.init(json: json) as? ConvertibleType else { | |
throw JSONError.TypeMismatch(expected: ConvertibleType.self, actual: object.dynamicType) | |
} | |
return value | |
} | |
} | |
// | |
// MARK: - JSONObjectDeconvertible | |
// | |
public protocol JSONDictionaryConvertible { | |
var jsonDictionary: JSONDictionary { get } | |
} | |
// | |
// MARK: - JSONObject | |
// | |
public typealias JSONDictionary = [String: AnyObject] | |
public struct JSONObject { | |
public var dictionary: JSONDictionary | |
public init?(data: NSData, options: NSJSONReadingOptions = []) throws { | |
guard let dict = try NSJSONSerialization.JSONObjectWithData(data, options: options) as? JSONDictionary else { | |
return nil | |
} | |
self.init(dictionary: dict) | |
} | |
public init(dictionary: JSONDictionary) { | |
self.dictionary = dictionary | |
} | |
private func anyForKey(key: String) throws -> Any { | |
let pathComponents = key.characters.split(".").map(String.init) | |
var accumulator: Any = dictionary | |
for component in pathComponents { | |
if let componentData = accumulator as? JSONDictionary, value = componentData[component] { | |
accumulator = value | |
continue | |
} | |
throw JSONError.KeyNotFound(key: key) | |
} | |
if let _ = accumulator as? NSNull { | |
throw JSONError.NullValue(key: key) | |
} | |
return accumulator | |
} | |
public func valueForKey<A: JSONValue>(key: String) throws -> A { | |
let any = try anyForKey(key) | |
guard let result = try A.JSONValue(any) as? A else { | |
throw JSONError.TypeMismatchWithKey(key: key, expected: A.self, actual: any.dynamicType) | |
} | |
return result | |
} | |
public func valueForKey<A: JSONValue>(key: String) throws -> [A] { | |
let any = try anyForKey(key) | |
return try Array<A>.JSONValue(any) | |
} | |
public func valueForKey<A: JSONValue>(key: String) throws -> A? { | |
do { | |
return try self.valueForKey(key) as A | |
} | |
catch JSONError.KeyNotFound { | |
return nil | |
} | |
catch JSONError.NullValue { | |
return nil | |
} | |
catch { | |
throw error | |
} | |
} | |
} | |
extension JSONObject: CustomStringConvertible { | |
public var description: String { | |
return dictionary.description | |
} | |
} | |
extension JSONObject: CustomDebugStringConvertible { | |
public var debugDescription: String { | |
return dictionary.debugDescription | |
} | |
} | |
//extension JSONObject: DictionaryLiteralConvertible { | |
// | |
//} | |
// | |
// MARK: - Tests | |
// | |
public struct User : JSONObjectConvertible, JSONDictionaryConvertible { | |
public let name: String | |
public let email: String | |
public init(json: JSONObject) throws { | |
name = try json.valueForKey("name") | |
email = try json.valueForKey("email") | |
} | |
public var jsonDictionary: JSONDictionary { | |
return [ | |
"name": name, | |
"email": email | |
] | |
} | |
} | |
var json = JSONObject(dictionary: ["url": "http://apple.com", "foo": (2 as NSNumber), "str": "Hello, World!", "array": [1,2,3,4,7], "object": ["foo": (3 as NSNumber), "str": "Hello, World!"], "bool": (true as NSNumber), "urls": ["http://apple.com", "http://google.com"], "user": ["name": "Jason", "email": "[email protected]"], "users": [["name": "Jason", "email": "[email protected]"], ["name": "Bob", "email": "[email protected]"]]]) | |
do { | |
var str: String = try json.valueForKey("str") | |
// var foo1: String = try json.valueForKey("foo") | |
var foo2: Int = try json.valueForKey("foo") | |
var foo3: Int? = try json.valueForKey("foo") | |
var foo4: Int? = try json.valueForKey("bar") | |
var arr: [Int] = try json.valueForKey("array") | |
var obj: JSONObject? = try json.valueForKey("object") | |
let innerfoo: Int = try obj!.valueForKey("foo") | |
let innerfoo2: Int = try json.valueForKey("object.foo") | |
let bool: Bool = try json.valueForKey("bool") | |
let url: NSURL = try json.valueForKey("url") | |
let urls: [NSURL] = try json.valueForKey("urls") | |
let user: User = try json.valueForKey("user") | |
user.name | |
let users: [User] = try json.valueForKey("users") | |
users.first?.name | |
} | |
catch { | |
print("\(error)") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment