Skip to content

Instantly share code, notes, and snippets.

@jparishy
Last active February 12, 2016 19:42
Show Gist options
  • Save jparishy/d46f1705d327f731c62e to your computer and use it in GitHub Desktop.
Save jparishy/d46f1705d327f731c62e to your computer and use it in GitHub Desktop.
//
// _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")
}
}
}
//
// 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
}
}
//
// 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