Skip to content

Instantly share code, notes, and snippets.

@mikeash
Created August 7, 2015 00:05
Show Gist options
  • Save mikeash/0f13e940a7109a6729ff to your computer and use it in GitHub Desktop.
Save mikeash/0f13e940a7109a6729ff to your computer and use it in GitHub Desktop.
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