Created
August 7, 2015 00:05
-
-
Save mikeash/0f13e940a7109a6729ff to your computer and use it in GitHub Desktop.
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 | |
protocol Initable { | |
init() | |
} | |
extension String: Initable {} | |
extension Double: Initable {} | |
protocol AnyValueSettable { | |
func setValue(value: Any) throws | |
} | |
enum JSONError: ErrorType, CustomStringConvertible { | |
case BadType(expected: Any.Type, actual: Any.Type) | |
case MissingKey(String) | |
var description: String { | |
switch self { | |
case .BadType(let expected, let actual): | |
return "Bad type (expected=\(expected) actual=\(actual))" | |
case .MissingKey(let key): | |
return "Missing key \(key)" | |
} | |
} | |
} | |
class JSON<T: Initable>: AnyValueSettable { | |
var value = T() | |
func setValue(value: Any) throws { | |
guard let typedValue = value as? T else { throw JSONError.BadType(expected: T.self, actual: value.dynamicType) } | |
self.value = typedValue | |
} | |
} | |
typealias JSONString = JSON<String> | |
typealias JSONNumber = JSON<Double> | |
func JSONParse<T>(json: [String: AnyObject], var _ targetF: Void -> T) throws -> T { | |
typealias EmptyF = () -> () | |
let block = unsafeBitCast(targetF, EmptyF.self) | |
var objcBlock: @convention(block) () -> () = block | |
let address = withUnsafePointer(&objcBlock, { ptr -> UnsafePointer<Void> in | |
let ptrPtr = UnsafePointer<UnsafePointer<Void>>(ptr) | |
return ptrPtr[1] | |
}) | |
let name = nameForCodePointer(address) | |
let fieldNames = parseMangledName(name) | |
let target = targetF() | |
let mirror = Mirror(reflecting: target) | |
for (name, (label: _, value: value)) in zip(fieldNames, mirror.children) { | |
guard let jsonValue = json[name] else { throw JSONError.MissingKey(name) } | |
let writeableValue = value as! AnyValueSettable | |
try writeableValue.setValue(jsonValue) | |
} | |
return target | |
} | |
func nameForCodePointer(ptr: UnsafePointer<Void>) -> UnsafePointer<Int8> { | |
var info = Dl_info() | |
dladdr(ptr, &info) | |
return info.dli_sname | |
} | |
func parseMangledName(name: UnsafePointer<Int8>) -> [String] { | |
var cursor = name | |
func ascii(char: StaticString) -> Int8 { | |
return Int8(char.utf8Start.memory) | |
} | |
func getDigit() -> Int? { | |
if cursor.memory >= ascii("0") && cursor.memory <= ascii("9") { | |
return Int(cursor.memory - ascii("0")) | |
} else { | |
return nil | |
} | |
} | |
var strings: [String] = [] | |
while cursor.memory != 0 { | |
var length = 0 | |
while let digit = getDigit() { | |
length *= 10 | |
length += digit | |
cursor++ | |
} | |
if length == 0 { | |
if cursor.memory == ascii("F") { | |
strings = [] | |
} | |
cursor++ | |
continue | |
} | |
let piece = String(bytesNoCopy: UnsafeMutablePointer<Void>(cursor), length: length, encoding: NSUTF8StringEncoding, freeWhenDone: false) | |
strings.append(piece!) | |
cursor += length | |
} | |
return strings | |
} | |
struct Person { | |
let name: String | |
let age: Int | |
let quest: String | |
} | |
func parsePerson(json: [String: AnyObject]) throws -> Person { | |
let parsed = try JSONParse(json, {( | |
name: JSONString(), | |
age: JSONNumber(), | |
quest: JSONString() | |
)}) | |
let person = Person(name: parsed.name.value, age: Int(parsed.age.value), quest: parsed.quest.value) | |
return person | |
} | |
do { | |
let person = try parsePerson(["name": "Bob Dole", "age": 42, "quest": "Find the Holy Grail and get drunk"]) | |
print(person) | |
} catch { | |
print(error) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment