-
-
Save mingsai/5ccf0ea005621bc21eb4 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
// | |
// _JSONReading.swift | |
// Swerver | |
// | |
// Created by Julius Parishy on 12/12/15. | |
// Copyright © 2015 Julius Parishy. All rights reserved. | |
// | |
import Foundation | |
extension NSJSONSerialization { | |
internal class func _impl_swerver_JSONObjectWithData(data: NSData, options opt: NSJSONReadingOptions) throws -> AnyObject { | |
class Node { | |
enum Type { | |
case Dictionary | |
case Array | |
case String | |
case Number | |
} | |
init(_ type: Type) { | |
self.type = type | |
switch type { | |
case .Dictionary: | |
dictionaryValue = NSMutableDictionary() | |
case .Array: | |
arrayValue = NSMutableArray() | |
case .String: | |
closed = true | |
break | |
case .Number: | |
closed = true | |
break | |
} | |
} | |
var parent: Node? = nil | |
var nextDictionaryKey: NSString? | |
var closed = false | |
var type: Type | |
var dictionaryValue: NSMutableDictionary? = nil | |
var arrayValue: NSMutableArray? = nil | |
var stringValue: NSString? = nil | |
var numberValue: NSNumber? = nil | |
func equalTo(node: Node) -> Bool { | |
switch (type, node.type) { | |
case (.Dictionary, .Dictionary): return dictionaryValue?.isEqual(node.dictionaryValue) ?? false | |
case (.Array, .Array): return arrayValue?.isEqual(node.arrayValue) ?? false | |
case (.String, .String): return stringValue == node.stringValue | |
case (.Number, .Number): return numberValue == node.numberValue | |
default: return false | |
} | |
} | |
} | |
enum TokenType { | |
case Undetermined /* wtfbbq */ | |
case ObjectOpen /* { */ | |
case ObjectClose /* } */ | |
case ArrayOpen /* [ */ | |
case ArrayClose /* ] */ | |
case Key /* \"str\" or str */ | |
case Value /* \"str\", str, or 3 */ | |
case Colon /* : */ | |
case MaybeNext /* ',' or end-of-value */ | |
case Quote /* " */ | |
} | |
if let string = NSString(bytes: data.bytes, length: data.length, encoding: NSUTF8StringEncoding) { | |
var rootNode: Node? | |
var currentNode: Node? | |
var nextExpectedToken: TokenType = .Undetermined | |
let scanner = NSScanner(string: string.bridge()) | |
let invalidUnquotedToken = { | |
(string: NSString) -> Bool in | |
let alphaNumeric = NSCharacterSet.alphanumericCharacterSet() | |
for i in 0..<string.length { | |
if !alphaNumeric.characterIsMember(string.characterAtIndex(i)) { | |
return false | |
} | |
} | |
return true | |
} | |
repeat { | |
switch nextExpectedToken { | |
/* | |
* Before we even have the root node | |
*/ | |
case .Undetermined: | |
if rootNode != nil { | |
continue | |
} | |
if scanner.scanString("{", intoString: nil) { | |
rootNode = Node(.Dictionary) | |
nextExpectedToken = .Key | |
} else if scanner.scanString("[", intoString: nil) { | |
rootNode = Node(.Array) | |
nextExpectedToken = .Value | |
} else { | |
throw Error.InvalidInput(message: "Fragments are unsupported.") | |
} | |
currentNode = rootNode | |
/* | |
* Looking for a dictionary key | |
*/ | |
case .Key: | |
var string: NSString? = nil | |
if scanner.scanString("\"", intoString: nil) { | |
var key: NSString? = nil | |
if scanner.scanUpToString("\"", intoString: &key) { | |
scanner.scanString("\"", intoString: nil) | |
currentNode?.nextDictionaryKey = key | |
nextExpectedToken = .Colon | |
} else { | |
throw Error.InvalidInput(message: "Expected quote to end key.") | |
} | |
} else if scanner.scanUpToString(":", intoString: &string) { | |
if let string = string { | |
if invalidUnquotedToken(string) { | |
throw Error.InvalidInput(message: "Invalid key in dictionary.") | |
} | |
currentNode?.nextDictionaryKey = string | |
nextExpectedToken = .Colon | |
} else { | |
throw Error.InvalidInput(message: "Expected dictionary key.") | |
} | |
} else { | |
throw Error.InvalidInput(message: "Expected dictionary key.") | |
} | |
/* | |
* Values, for dictionaries or for arrays | |
*/ | |
case .Value: | |
var string: NSString? = nil | |
let parsedValue: AnyObject? | |
var floatValue: CFloat = 0 | |
var intValue: CInt = 0 | |
if scanner.scanFloat(&floatValue) { | |
parsedValue = NSNumber(double: Double(floatValue)) | |
nextExpectedToken = .MaybeNext | |
} else if scanner.scanInt(&intValue) { | |
parsedValue = NSNumber(integer: Int(intValue)) | |
nextExpectedToken = .MaybeNext | |
} else if scanner.scanString("\"", intoString: nil) { | |
var value: NSString? = nil | |
scanner.scanUpToString("\"", intoString: &value) | |
if let value = value { | |
scanner.scanString("\"", intoString: nil) | |
parsedValue = value | |
nextExpectedToken = .MaybeNext | |
} else { | |
throw Error.InvalidInput(message: "Expected value.") | |
} | |
} else if scanner.scanString("{", intoString: nil) { | |
let parent = currentNode | |
currentNode = Node(.Dictionary) | |
currentNode?.parent = parent | |
nextExpectedToken = .Key | |
parsedValue = nil | |
} else if scanner.scanString("[", intoString: nil) { | |
let parent = currentNode | |
currentNode = Node(.Array) | |
currentNode?.parent = parent | |
nextExpectedToken = .Value | |
parsedValue = nil | |
} else if scanner.scanUpToString(",", intoString: &string) { | |
if let string = string where invalidUnquotedToken(string) { | |
throw Error.InvalidInput(message: "Value contains invalid characters") | |
} | |
scanner.scanString(",", intoString: nil) | |
parsedValue = string | |
nextExpectedToken = .MaybeNext | |
} else if scanner.scanUpToString("}", intoString: &string) { | |
if let string = string where invalidUnquotedToken(string) { | |
throw Error.InvalidInput(message: "Value contains invalid characters") | |
} | |
scanner.scanString("}", intoString: nil) | |
parsedValue = string | |
nextExpectedToken = .Undetermined | |
} else if scanner.scanUpToString("]", intoString: &string) { | |
if let string = string where invalidUnquotedToken(string) { | |
throw Error.InvalidInput(message: "Value contains invalid characters") | |
} | |
scanner.scanString("]", intoString: nil) | |
parsedValue = string | |
nextExpectedToken = .Undetermined | |
} else { | |
throw Error.InvalidInput(message: "Invalid end of value.") | |
} | |
if let current = currentNode { | |
switch current.type { | |
case .Dictionary: | |
if let key = current.nextDictionaryKey, value = parsedValue { | |
current.dictionaryValue?.setObject(value, forKey: key) | |
} | |
case .Array: | |
if let value = parsedValue { | |
current.arrayValue?.addObject(value) | |
} | |
default: | |
throw Error.InvalidInput(message: "Invalid value.") | |
} | |
} else { | |
throw Error.InvalidInput(message: "Invalid value.") | |
} | |
if nextExpectedToken == .Undetermined, let parent = currentNode?.parent { | |
currentNode = parent | |
} | |
/* | |
* Look for a comma or the end of the current context. | |
*/ | |
case .MaybeNext: | |
if let current = currentNode { | |
if scanner.scanString(",", intoString: nil) { | |
switch current.type { | |
case .Dictionary: | |
nextExpectedToken = .Key | |
case .Array: | |
nextExpectedToken = .Value | |
default: | |
throw Error.InvalidInput(message: "Unexpected ','") | |
} | |
} else if scanner.scanString("]", intoString: nil) { | |
if let current = currentNode, parent = current.parent, key = parent.nextDictionaryKey { | |
if current.type != .Array { | |
throw Error.InvalidInput(message: "Unexpected ']'") | |
} | |
switch parent.type { | |
case .Dictionary: | |
if let array = current.arrayValue { | |
parent.dictionaryValue?.setObject(array, forKey: key) | |
current.closed = true | |
currentNode = parent | |
nextExpectedToken = .MaybeNext | |
} else { | |
throw Error.InvalidInput(message: "Unexpected nested type.") | |
} | |
case .Array: | |
if let array = current.arrayValue { | |
parent.arrayValue?.addObject(array) | |
current.closed = true | |
currentNode = parent | |
nextExpectedToken = .MaybeNext | |
} else { | |
throw Error.InvalidInput(message: "Unexpected nested type.") | |
} | |
default: | |
throw Error.InvalidInput(message: "Unexpected end of dictionary.") | |
} | |
} else { | |
if let root = rootNode, current = currentNode where current.equalTo(root) { | |
currentNode?.closed = true | |
} | |
continue | |
} | |
} else if scanner.scanString("}", intoString: nil) { | |
if let current = currentNode, parent = current.parent { | |
if current.type != .Dictionary { | |
throw Error.InvalidInput(message: "Unexpected '}'") | |
} | |
switch parent.type { | |
case .Dictionary: | |
if let key = parent.nextDictionaryKey, dictionary = current.dictionaryValue { | |
parent.dictionaryValue?.setObject(dictionary, forKey: key) | |
current.closed = true | |
currentNode = parent | |
nextExpectedToken = .MaybeNext | |
} else { | |
throw Error.InvalidInput(message: "Unexpected nested type.") | |
} | |
case .Array: | |
if let dictionary = current.dictionaryValue { | |
parent.arrayValue?.addObject(dictionary) | |
current.closed = true | |
currentNode = parent | |
nextExpectedToken = .MaybeNext | |
} else { | |
throw Error.InvalidInput(message: "Unexpected nested type.") | |
} | |
default: | |
throw Error.InvalidInput(message: "Unexpected end of dictionary.") | |
} | |
} else { | |
if let root = rootNode, current = currentNode where current.equalTo(root) { | |
currentNode?.closed = true | |
} | |
continue | |
} | |
} else if scanner.scanLocation != scanner.string.bridge().length - 1 { | |
throw Error.InvalidInput(message: "Unexpected end of context.") | |
} | |
} else { | |
throw Error.InvalidInput(message: "Unexpected end of context.") | |
} | |
/* | |
* Colon for dictionary key:value separating | |
*/ | |
case .Colon: | |
var result: NSString? = nil | |
if scanner.scanString(":", intoString: &result) == false { | |
throw Error.InvalidInput(message: "Expected ':'") | |
} | |
nextExpectedToken = .Value | |
default: | |
break | |
} | |
} while(!scanner.atEnd && scanner.scanLocation != scanner.string.bridge().length - 1) | |
if let currentNode = currentNode where currentNode.closed == false { | |
throw Error.InvalidInput(message: "Unexpected end of file ") | |
} | |
if let rootNode = rootNode { | |
switch rootNode.type { | |
case .Dictionary: | |
if let dict = rootNode.dictionaryValue { | |
return dict | |
} | |
case .Array: | |
if let arr = rootNode.arrayValue { | |
return arr | |
} | |
default: break | |
} | |
throw Error.InvalidInput(message: "Invalid root object or unexpected end of data") | |
} else { | |
throw Error.InvalidInput(message: "Could not find root object in data") | |
} | |
} else { | |
throw Error.InvalidInput(message: "Invalid data") | |
} | |
} | |
} |
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
// | |
// JSON.swift | |
// Swerver | |
// | |
// Created by Julius Parishy on 12/12/15. | |
// Copyright © 2015 Julius Parishy. All rights reserved. | |
// | |
import Foundation | |
extension NSJSONSerialization { | |
enum Error : ErrorType { | |
case Unimplemented | |
case InvalidInput(message: String) | |
} | |
public class func swerver_isValidJSONObject(rootObject: AnyObject) -> Bool { | |
var isValid: ((AnyObject, Bool) -> Bool)! = nil | |
isValid = { | |
(obj: AnyObject, rootObject: Bool) -> Bool in | |
if let array = obj as? NSArray { | |
for i in 0..<array.count { | |
let obj = array.objectAtIndex(i) | |
if !isValid(obj, false) { | |
return false | |
} | |
} | |
} else if let dict = obj as? NSDictionary { | |
for keyAny in dict.keyEnumerator() { | |
if let key = keyAny as? NSObject { | |
if !(key is NSString) { | |
return false | |
} | |
if let obj = dict.objectForKey(key) { | |
if !isValid(obj, false) { | |
return false | |
} | |
} else { | |
return false | |
} | |
} else { | |
return false | |
} | |
} | |
} else { | |
if (obj is NSString) || (obj is NSNumber) { | |
return !rootObject | |
} else { | |
return false | |
} | |
} | |
return true | |
} | |
return isValid(rootObject, true) | |
} | |
public class func swerver_dataWithJSONObject(obj: AnyObject, options opt: NSJSONWritingOptions) throws -> NSData { | |
throw Error.Unimplemented | |
} | |
public class func swerver_JSONObjectWithData(data: NSData, options opt: NSJSONReadingOptions) throws -> AnyObject { | |
return try _impl_swerver_JSONObjectWithData(data, options: opt) | |
} | |
public class func swerver_writeJSONObject(obj: AnyObject, toStream stream: NSOutputStream, options opt: NSJSONWritingOptions, error: NSErrorPointer) -> Int { | |
return 0 | |
} | |
public class func swerver_JSONObjectWithStream(stream: NSInputStream, options opt: NSJSONReadingOptions) throws -> AnyObject { | |
throw Error.Unimplemented | |
} | |
} |
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
// | |
// JSONTests.swift | |
// Swerver | |
// | |
// Created by Julius Parishy on 12/12/15. | |
// Copyright © 2015 Julius Parishy. All rights reserved. | |
// | |
import XCTest | |
class JSONTests: XCTestCase { | |
override func setUp() { | |
super.setUp() | |
// Put setup code here. This method is called before the invocation of each test method in the class. | |
} | |
override func tearDown() { | |
// Put teardown code here. This method is called after the invocation of each test method in the class. | |
super.tearDown() | |
} | |
func test_swerver_isValidJSONObject() { | |
XCTAssertFalse(NSJSONSerialization.swerver_isValidJSONObject("json fragment")) | |
XCTAssertFalse(NSJSONSerialization.swerver_isValidJSONObject(3)) | |
XCTAssertTrue(NSJSONSerialization.swerver_isValidJSONObject([])) | |
XCTAssertTrue(NSJSONSerialization.swerver_isValidJSONObject([:])) | |
let expectTrue = { | |
(str: String, obj: AnyObject) -> Void in | |
XCTAssertTrue(NSJSONSerialization.swerver_isValidJSONObject(obj), "Expected: \(str)") | |
} | |
let expectFalse = { | |
(str: String, obj: AnyObject) -> Void in | |
XCTAssertFalse(NSJSONSerialization.swerver_isValidJSONObject(obj), "Expected: \(str)") | |
} | |
expectTrue("valid dict", [ | |
"test" : 30 | |
]) | |
expectFalse("invalid dict key", [ | |
42 : 30 | |
]) | |
expectTrue("valid embedded dict", [ | |
"validKey" : [ | |
"validEmbeddedKey" : "validObject" | |
] | |
]) | |
expectTrue("valid embedded array", [ | |
"validKey" : [ "valid1", "valid2" ] | |
]) | |
expectFalse("invalid embedded dict", [ | |
"validKey" : [ | |
3 : "invalidEmbeddedObject" | |
] | |
]) | |
expectFalse("invalid embedded dict due to invalid value for key", [ | |
"validKey" : [ | |
"validEmbddedKey" : NSDate() | |
] | |
]) | |
expectFalse("invalid embedded array ", [ | |
"validKey" : [ | |
"validEmbddedKey" : [ NSDate() ] | |
] | |
]) | |
expectTrue("valid doubly nested dict ", [ | |
"validKey" : [ | |
"validEmbddedKey" : [ | |
"embedded2" : "hi", | |
] | |
] | |
]) | |
expectFalse("invalid doubly nested dict ", [ | |
"validKey" : [ | |
"validEmbddedKey" : [ | |
"embedded2" : NSDate(), | |
] | |
] | |
]) | |
} | |
func test_swerver_JSONObjectWithData() { | |
let makeData = { | |
(str: String) -> NSData? in | |
if let bytes = str.cStringUsingEncoding(NSUTF8StringEncoding) { | |
return NSData(bytes: bytes, length: bytes.count) | |
} else { | |
XCTFail("Bad String") | |
return nil | |
} | |
} | |
let expectTrue = { | |
(message: String, JSONString: String, expectedObject: AnyObject) in | |
if let data = makeData(JSONString) { | |
do { | |
let result = try NSJSONSerialization.swerver_JSONObjectWithData(data, options: NSJSONReadingOptions(rawValue: 0)) | |
XCTAssertEqual(result as? NSObject, expectedObject as? NSObject, message) | |
} catch NSJSONSerialization.Error.InvalidInput(let m) { | |
XCTFail(m) | |
} catch let error as NSError { | |
XCTFail("NSJSONSerialization threw: \(error.localizedDescription)") | |
} | |
} else { | |
XCTFail("Bad String") | |
} | |
} | |
let expectThrows = { | |
(message: String, JSONString: String) in | |
if let data = makeData(JSONString) { | |
do { | |
try NSJSONSerialization.swerver_JSONObjectWithData(data, options: NSJSONReadingOptions(rawValue: 0)) | |
XCTFail("\(message)\nExpected throw on:\n\(JSONString)") | |
} catch { | |
XCTAssert(true) | |
} | |
} else { | |
XCTFail("Bad String") | |
} | |
} | |
expectTrue("valid single entry dict", "{\"key\":\"value\"}", [ | |
"key" : "value" | |
]) | |
expectTrue("valid multi entry dict", "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":\"value3\"}", [ | |
"key1" : "value1", | |
"key2" : "value2", | |
"key3" : "value3" | |
]) | |
expectTrue("valid single element array", "[42]", [ | |
42 | |
]) | |
expectTrue("valid multi element array", "[42,43,44]", [ | |
42, 43, 44 | |
]) | |
expectTrue("valid nested dict", "{\"key1\":\"value1\",\"nested\":{\"key2\":\"value2\",\"key3\":\"value3\"}}", [ | |
"key1" : "value1", | |
"nested" : [ | |
"key2" : "value2", | |
"key3" : "value3" | |
] | |
]) | |
expectTrue("valid array of dicts", "[{\"key1\":\"value1\"},{\"key2\":\"value2\"},{\"key3\":\"value3\"}]", [ | |
[ "key1" : "value1" ], | |
[ "key2" : "value2" ], | |
[ "key3" : "value3" ], | |
]) | |
expectTrue("valid array of dicts with first dict value being dict", "[{\"key1\": {\"value1\":[1,2,3]}},{\"key2\":\"value2\"},{\"key3\":\"value3\"}]", [ | |
[ "key1" : [ "value1" : [ 1, 2, 3] ] ], | |
[ "key2" : "value2" ], | |
[ "key3" : "value3" ], | |
]) | |
expectThrows("missing coma", "{\"key1\":\"value1\" \"key2\":\"value2\",\"key3\":\"value3\"}") | |
expectThrows("missing closing curly brace", "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":\"value3\"") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment