Skip to content

Instantly share code, notes, and snippets.

@mikeash

mikeash/json.swift

Created Jun 17, 2014
Embed
What would you like to do?
#!/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