Skip to content

Instantly share code, notes, and snippets.

@jarsen jarsen/JaSON.swift
Last active Nov 10, 2016

Embed
What would you like to do?
JSON Value Extraction in Swift. Blog post here http://jasonlarsen.me/2015/10/16/no-magic-json-pt3.html
import Foundation
//
// MARK: - JSONError Type
//
public enum JSONError: ErrorType, CustomStringConvertible {
case KeyNotFound(key: JSONKeyType)
case NullValue(key: JSONKeyType)
case TypeMismatch(expected: Any, actual: Any)
case TypeMismatchWithKey(key: JSONKeyType, expected: Any, actual: Any)
public var description: String {
switch self {
case let .KeyNotFound(key):
return "Key not found: \(key.stringValue)"
case let .NullValue(key):
return "Null Value found at: \(key.stringValue)"
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: - JSONKeyType
//
public protocol JSONKeyType: Hashable {
var stringValue: String { get }
}
extension String: JSONKeyType {
public var stringValue: String {
return self
}
}
//
// MARK: - JSONValueType
//
public protocol JSONValueType {
typealias ValueType = Self
static func JSONValue(object: Any) throws -> ValueType
}
extension JSONValueType {
public static func JSONValue(object: Any) throws -> ValueType {
guard let objectValue = object as? ValueType else {
throw JSONError.TypeMismatch(expected: ValueType.self, actual: object.dynamicType)
}
return objectValue
}
}
//
// MARK: - JSONValueType Implementations
//
extension String: JSONValueType {}
extension Int: JSONValueType {}
extension UInt: JSONValueType {}
extension Float: JSONValueType {}
extension Double: JSONValueType {}
extension Bool: JSONValueType {}
extension Array where Element: JSONValueType {
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: JSONValueType {
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: JSONValueType {
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
}
}
//
// MARK: - JSONObjectConvertible
//
public protocol JSONObjectConvertible : JSONValueType {
typealias ConvertibleType = Self
init(json: JSONObject) throws
}
extension JSONObjectConvertible {
public static func JSONValue(object: Any) throws -> ConvertibleType {
guard let json = object as? JSONObject else {
throw JSONError.TypeMismatch(expected: JSONObject.self, actual: object.dynamicType)
}
guard let value = try self.init(json: json) as? ConvertibleType else {
throw JSONError.TypeMismatch(expected: ConvertibleType.self, actual: object.dynamicType)
}
return value
}
}
//
// MARK: - JSONObject
//
public typealias JSONObject = [String: AnyObject]
extension Dictionary where Key: JSONKeyType {
private func anyForKey(key: Key) throws -> Any {
let pathComponents = key.stringValue.characters.split(".").map(String.init)
var accumulator: Any = self
for component in pathComponents {
if let componentData = accumulator as? [Key: Value], value = componentData[component as! Key] {
accumulator = value
continue
}
throw JSONError.KeyNotFound(key: key)
}
if let _ = accumulator as? NSNull {
throw JSONError.NullValue(key: key)
}
return accumulator
}
public func JSONValueForKey<A: JSONValueType>(key: Key) 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 JSONValueForKey<A: JSONValueType>(key: Key) throws -> [A] {
let any = try anyForKey(key)
return try Array<A>.JSONValue(any)
}
public func JSONValueForKey<A: JSONValueType>(key: Key) throws -> A? {
do {
return try self.JSONValueForKey(key) as A
}
catch JSONError.KeyNotFound {
return nil
}
catch JSONError.NullValue {
return nil
}
catch {
throw error
}
}
}
//
// MARK: - Tests
//
struct User : JSONObjectConvertible {
let name: String
let email: String
init(json: JSONObject) throws {
name = try json.JSONValueForKey("name")
email = try json.JSONValueForKey("email")
}
}
var json: JSONObject = ["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@email.com"], "users": [["name": "Jason", "email": "email@email.com"], ["name": "Bob", "email": "bob@email.com"]]]
do {
var str: String = try json.JSONValueForKey("str")
// var foo1: String = try json.JSONValueForKey("foo")
var foo2: Int = try json.JSONValueForKey("foo")
var foo3: Int? = try json.JSONValueForKey("foo")
var foo4: Int? = try json.JSONValueForKey("bar")
var arr: [Int] = try json.JSONValueForKey("array")
var obj: JSONObject? = try json.JSONValueForKey("object")
let innerfoo: Int = try obj!.JSONValueForKey("foo")
let innerfoo2: Int = try json.JSONValueForKey("object.foo")
let bool: Bool = try json.JSONValueForKey("bool")
let url: NSURL = try json.JSONValueForKey("url")
let urls: [NSURL] = try json.JSONValueForKey("urls")
let user: User = try json.JSONValueForKey("user")
let users: [User] = try json.JSONValueForKey("users")
}
catch {
print("\(error)")
}
@bwhiteley

This comment has been minimized.

Copy link

bwhiteley commented Oct 21, 2015

        if let _ = result as? NSNull {
            throw JSONError.NullValue(key: key)
        }

Needs to be moved from JSONValueForKey to anyForKey just above the return.

@brianmullen

This comment has been minimized.

Copy link

brianmullen commented Oct 21, 2015

You should add support for optional arrays:

public func JSONValueForKey<A: JSONValueType>(key: Key) throws -> [A]? {
        do {
            return try self.JSONValueForKey(key) as [A]
        }
        catch JSONError.KeyNotFound {
            return nil
        }
        catch JSONError.NullValue {
            return nil
        }
        catch {
            throw error
        }
    }

So, you can do the following without an error being thrown:

let oUrls: [NSURL]? = try json.JSONValueForKey("optionalUrls")
@brianmullen

This comment has been minimized.

Copy link

brianmullen commented Oct 21, 2015

line 54:

throw JSONError.TypeMismatch(expected: JSONValue.self, actual: object.dynamicType)

should be:

throw JSONError.TypeMismatch(expected: ValueType.self, actual: object.dynamicType)
@MarkQSchultz

This comment has been minimized.

Copy link

MarkQSchultz commented Feb 2, 2016

Proposal:

Add case InvalidValue(value: Any) and case InvalidValueWithKey(key: JSONKeyType, value: Any) to JSONError. This is useful in situations in which a value is transformed into another type, such as when converting a String to NSURL.

Example:

extension NSURL: JSONValueType {
    public static func JSONValue(object: Any) throws -> NSURL {
        guard let urlString = object as? String else {
            throw JSONError.TypeMismatch(expected: self, actual: object.dynamicType)
        }

        guard let objectValue = NSURL(string: urlString) else {
            throw JSONError.InvalidValue(value: urlString)
        }

        return objectValue
    }
}

This allows for a more accurate error when the type is actually the expected type (String), but the value is invalid for constructing the new type. In my case, I got a URL string that had a space at the end. There was not a type mismatch, but an invalid value. However, the error thrown was JSONError.TypeMismatch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.