Created
June 17, 2014 02:21
-
-
Save mikeash/f443a8492d78e1d4dd10 to your computer and use it in GitHub Desktop.
This file contains 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
#!/Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -i -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk | |
import Foundation | |
class JSON { | |
struct Path: Printable { | |
enum Element { | |
case Index(Int) | |
case Key(String) | |
var asString: String { | |
get { | |
switch self { | |
case .Index(let value): | |
return String(value) | |
case .Key(let value): | |
return value | |
} | |
} | |
} | |
} | |
let elements: Element[] | |
init(elements: Element[]) { | |
self.elements = elements | |
} | |
init() { | |
self.init(elements: Element[]()) | |
} | |
var description: String { | |
if elements.count == 0 { | |
return "<empty>" | |
} | |
let strings = elements.map { $0.asString } | |
return ".".join(strings) | |
} | |
} | |
struct Error: Printable { | |
let path: Path | |
let desiredType: String | |
let actualType: String | |
var description: String { | |
return "At \(path.description), got \(actualType) but wanted \(desiredType)" | |
} | |
} | |
struct Value { | |
let errorReport: Error -> Void | |
let underlyingObject: NSObject? | |
func err(desiredType: String) -> Void { | |
let path = Path() | |
var actualType = "nil" | |
if let obj = underlyingObject { | |
actualType = NSStringFromClass(obj.classForCoder()) | |
} | |
errorReport(Error(path: path, desiredType: desiredType, actualType: actualType)) | |
} | |
var string: String! { | |
if let underlyingString = underlyingObject as? NSString { | |
return underlyingString | |
} | |
err("string") | |
return nil | |
} | |
var number: Double! { | |
if let underlyingNumber = underlyingObject as? NSNumber { | |
return underlyingNumber.doubleValue() | |
} | |
err("number") | |
return nil | |
} | |
var dictionary: Dictionary! { | |
if let underlyingDictionary = underlyingObject as? NSDictionary { | |
return Dictionary(underlyingDictionary, errorReport) | |
} | |
err("dictionary") | |
return nil | |
} | |
var array: Array! { | |
if let underlyingArray = underlyingObject as? NSArray { | |
return Array(underlyingArray, errorReport) | |
} | |
err("array") | |
return nil | |
} | |
func mappedArray<T>(parser: (Dictionary, (Void -> T) -> Void) -> Void) -> T[]! { | |
if let array = self.array { | |
var returnArray = T[]() | |
var reportedError = false | |
for value in array { | |
let reportingValue = Value(errorReport: { reportedError = true; value.errorReport($0) }, underlyingObject: value.underlyingObject) | |
if let dict = reportingValue.dictionary { | |
parser(dict) { | |
thenBlock in | |
if !reportedError { | |
let result = thenBlock() | |
returnArray.append(result) | |
} | |
} | |
} | |
if reportedError { | |
break | |
} | |
} | |
if !reportedError { | |
return returnArray | |
} | |
} | |
return nil | |
} | |
} | |
struct Dictionary { | |
let errorReport: Error -> Void | |
let underlyingDictionary: NSDictionary | |
init(_ underlyingDictionary: NSDictionary, _ errorReport: Error -> Void) { | |
self.underlyingDictionary = underlyingDictionary | |
self.errorReport = errorReport | |
} | |
subscript(key: String) -> Value { | |
return Value( | |
errorReport: { | |
(error: Error) in | |
let newError = Path.Element.Key(key) + error | |
self.errorReport(newError) | |
}, | |
underlyingObject: underlyingDictionary[key] as? NSObject | |
) | |
} | |
} | |
struct Array : Sequence { | |
typealias GeneratorType = Gen | |
let errorReport: Error -> Void | |
let underlyingArray: NSArray | |
init(_ underlyingArray: NSArray, _ errorReport: Error -> Void) { | |
self.underlyingArray = underlyingArray | |
self.errorReport = errorReport | |
} | |
subscript(index: Int) -> Value { | |
return Value( | |
errorReport: { | |
(error: Error) in | |
let newError = Path.Element.Index(index) + error | |
self.errorReport(newError) | |
}, | |
underlyingObject: underlyingArray[index] as? NSObject | |
) | |
} | |
var length: Int { | |
return underlyingArray.count() | |
} | |
class Gen : Generator { | |
typealias Element = Value | |
var index = 0 | |
let array: Array | |
init(_ array: Array) { | |
self.array = array | |
} | |
func next() -> Value? { | |
if index >= array.length { | |
return nil | |
} | |
return array[index++] | |
} | |
} | |
func generate() -> Gen { | |
return Gen(self) | |
} | |
} | |
class func extract<T>(obj: AnyObject?, parser: (Dictionary, (Void -> T) -> Void) -> Void) -> (T?, Error[]?) { | |
var errors = Error[]() | |
let value = Value(errorReport: { errors.append($0) }, underlyingObject: obj as? NSObject) | |
var result: T? | |
if let dict = value.dictionary { | |
parser(dict) { | |
thenBlock in | |
if errors.count == 0 { | |
result = thenBlock() | |
} | |
} | |
} | |
if errors.count == 0 { | |
return (result, nil) | |
} else { | |
return (nil, errors) | |
} | |
} | |
} | |
func + (left: JSON.Path.Element, right: JSON.Path) -> JSON.Path { | |
var newElements = right.elements | |
newElements.insert(left, atIndex: 0) | |
return JSON.Path(elements: newElements) | |
} | |
func + (left: JSON.Path.Element, right: JSON.Error) -> JSON.Error { | |
return JSON.Error(path: (left + right.path), desiredType: right.desiredType, actualType: right.actualType) | |
} | |
// === EXAMPLE === | |
let jsonIn = [ | |
"name": "Squids McGee", | |
"age": 33.3, | |
"pets": [ | |
[ | |
"name": "Checkers", | |
"kind": "dog" | |
], | |
[ | |
"name": "Steve", | |
"kind": "steve" | |
] | |
] | |
] | |
let jsonData = NSJSONSerialization.dataWithJSONObject(jsonIn, options: NSJSONWritingOptions(0), error: nil) | |
let jsonOut : AnyObject! = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions(0), error: nil) | |
struct Pet { | |
let name: String | |
let kind: String | |
} | |
struct Person { | |
let name: String | |
let age: Double | |
let pets: Pet[] | |
} | |
func ParsePet(dict: JSON.Dictionary, then: (Void -> Pet) -> Void) { | |
let name = dict["name"].string | |
let kind = dict["kind"].string | |
then { | |
return Pet(name: name, kind: kind) | |
} | |
} | |
func ParsePerson(dict: JSON.Dictionary, then: (Void -> Person) -> Void) { | |
let name = dict["name"].string | |
let age = dict["age"].number | |
let pets = dict["pets"].mappedArray(ParsePet) | |
then { | |
return Person(name: name, age: age, pets: pets) | |
} | |
} | |
let (maybePerson: Person?, errors) = JSON.extract(jsonOut, ParsePerson) | |
if let person = maybePerson { | |
let pet = person.pets[0] | |
println(person.name, person.age, pet.name) | |
} | |
if let realErrors = errors { | |
println(realErrors.map{ $0.description }) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment