├── .gitignore ├── .travis.yml ├── JsonSerializer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── JsonSerializer-OSX.xcscheme │ ├── JsonSerializer-iOS.xcscheme │ ├── JsonSerializer-tvOS.xcscheme │ ├── JsonSerializer-watchOS.xcscheme │ └── JsonSerializerTests.xcscheme ├── JsonSerializer ├── Info.plist └── JsonParser+Foundation.swift ├── JsonSerializerTests ├── Info.plist ├── JsonParserTests.swift ├── JsonTests.swift ├── ParseErrorTests.swift ├── StringUtilsTests.swift ├── TemplateIcon2x.png ├── stackoverflow-items.json └── tweets.json ├── LICENSE ├── Makefile ├── Package.swift ├── PureJsonSerializer.podspec ├── README.md ├── Source ├── Json.swift ├── JsonParser.swift ├── JsonSerializer.swift ├── ParseError.swift └── StringUtils.swift ├── SwiftFeed ├── ApiClient.swift ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── DetailViewController.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── MasterViewController.swift └── OverlayIndicator.swift ├── SwiftFeedTests ├── Info.plist └── SwiftFeedTests.swift └── rfc7159.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode7.2 3 | xcode_project: JsonSerializer.xcodeproj 4 | xcode_scheme: JsonSerializerTests 5 | xcode_sdk: iphonesimulator 6 | -------------------------------------------------------------------------------- /JsonSerializer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JsonSerializer.xcodeproj/xcshareddata/xcschemes/JsonSerializer-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /JsonSerializer.xcodeproj/xcshareddata/xcschemes/JsonSerializer-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /JsonSerializer.xcodeproj/xcshareddata/xcschemes/JsonSerializer-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /JsonSerializer.xcodeproj/xcshareddata/xcschemes/JsonSerializer-watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /JsonSerializer.xcodeproj/xcshareddata/xcschemes/JsonSerializerTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /JsonSerializer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /JsonSerializer/JsonParser+Foundation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JsonSerializer+Foundation.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/15. 6 | // Copyright (c) 2014 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Json { 12 | var anyValue: AnyObject { 13 | switch self { 14 | case .ObjectValue(let ob): 15 | var mapped: [String : AnyObject] = [:] 16 | ob.forEach { key, val in 17 | mapped[key] = val.anyValue 18 | } 19 | return mapped as AnyObject 20 | case .ArrayValue(let array): 21 | return array.map { $0.anyValue } as AnyObject 22 | case .booleanValue(let bool): 23 | return bool as AnyObject 24 | case .numberValue(let number): 25 | return number as AnyObject 26 | case .StringValue(let string): 27 | return string as AnyObject 28 | case .nullValue: 29 | return NSNull() 30 | } 31 | } 32 | 33 | var foundationDictionary: [String : AnyObject]? { 34 | return anyValue as? [String : AnyObject] 35 | } 36 | 37 | var foundationArray: [AnyObject]? { 38 | return anyValue as? [AnyObject] 39 | } 40 | } 41 | 42 | extension Json { 43 | public static func from(_ any: AnyObject) -> Json { 44 | switch any { 45 | // If we're coming from foundation, it will be an `NSNumber`. 46 | //This represents double, integer, and boolean. 47 | case let number as Double: 48 | return .numberValue(number) 49 | case let string as String: 50 | return .StringValue(string) 51 | case let object as [String : AnyObject]: 52 | return from(object) 53 | case let array as [AnyObject]: 54 | return .ArrayValue(array.map(from)) 55 | case _ as NSNull: 56 | return .nullValue 57 | default: 58 | fatalError("Unsupported foundation type") 59 | } 60 | return .nullValue 61 | } 62 | 63 | public static func from(_ any: [String : AnyObject]) -> Json { 64 | var mutable: [String : Json] = [:] 65 | any.forEach { key, val in 66 | mutable[key] = .from(val) 67 | } 68 | return .from(mutable) 69 | } 70 | } 71 | 72 | extension Json { 73 | public static func deserialize(_ data: Data) throws -> Json { 74 | let startPointer = (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count) 75 | let bufferPointer = UnsafeBufferPointer(start: startPointer, count: data.count) 76 | return try deserialize(bufferPointer) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /JsonSerializerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /JsonSerializerTests/JsonParserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JsonParserTests.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/08. 6 | // Copyright (c) 2014 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class JsonDeserializerTests: XCTestCase { 12 | 13 | func testEmptyArray() { 14 | let json = try! Json.deserialize("[]") 15 | XCTAssertEqual(json.description, "[]") 16 | } 17 | 18 | func testEmptyArrayWithSpaces() { 19 | let json = try! Json.deserialize(" [ ] ") 20 | XCTAssertEqual(json.description, "[]") 21 | } 22 | 23 | func testArray() { 24 | let json = try! Json.deserialize("[true,false,null]") 25 | XCTAssertEqual(json.description, "[true,false,null]") 26 | } 27 | 28 | func testArrayWithSpaces() { 29 | let json = try! Json.deserialize("[ true , false , null ]") 30 | XCTAssertEqual(json.description, "[true,false,null]") 31 | } 32 | 33 | func testEmptyObject() { 34 | let json = try! Json.deserialize("{}") 35 | XCTAssertEqual(json.description, "{}") 36 | } 37 | 38 | func testEmptyObjectWithSpace() { 39 | let json = try! Json.deserialize(" { } ") 40 | XCTAssertEqual(json.description, "{}") 41 | } 42 | 43 | func testObject() { 44 | let json = try! Json.deserialize("{\"foo\":[\"bar\",\"baz\"]}") 45 | XCTAssertEqual(json.description, "{\"foo\":[\"bar\",\"baz\"]}") 46 | } 47 | 48 | func testObjectWithWhiteSpaces() { 49 | let json = try! Json.deserialize(" { \"foo\" : [ \"bar\" , \"baz\" ] } ") 50 | XCTAssertEqual(json.description, "{\"foo\":[\"bar\",\"baz\"]}") 51 | } 52 | 53 | 54 | func testString() { 55 | let json = try! Json.deserialize("[\"foo [\\t] [\\r] [\\n]] [\\\\] bar\"]") 56 | XCTAssertEqual(json.description, "[\"foo [\\t] [\\r] [\\n]] [\\\\] bar\"]") 57 | } 58 | 59 | func testStringWithMyltiBytes() { 60 | let json = try! Json.deserialize("[\"こんにちは\"]") 61 | XCTAssertEqual(json[0]!.stringValue, "こんにちは") 62 | XCTAssertEqual(json.description, "[\"こんにちは\"]") 63 | } 64 | 65 | func testStringWithMyltiUnicodeScalars() { 66 | let json = try! Json.deserialize("[\"江戸前🍣\"]") 67 | XCTAssertEqual(json[0]!.stringValue!, "江戸前🍣") 68 | XCTAssertEqual(json[0]!.description, "\"江戸前🍣\"") 69 | XCTAssertEqual(json.description, "[\"江戸前🍣\"]") 70 | } 71 | 72 | func testNumberOfInt() { 73 | let json = try! Json.deserialize("[0, 10, 234]") 74 | XCTAssertEqual(json.description, "[0,10,234]") 75 | } 76 | 77 | func testNumberOfFloat() { 78 | let json = try! Json.deserialize("[3.14, 0.035]") 79 | XCTAssertEqual(json.description, "[3.14,0.035]") 80 | } 81 | 82 | func testNumberOfExponent() { 83 | let json = try! Json.deserialize("[1e2, 1e-2, 3.14e+01]") 84 | XCTAssertEqual(json[0]!.intValue, 100) 85 | XCTAssertEqual(json[1]!.doubleValue, 0.01) 86 | XCTAssertEqual("\(json[2]!.doubleValue!)", "31.4") 87 | } 88 | 89 | func testUnicodeEscapeSequences() { 90 | let json = try! Json.deserialize("[\"\\u003c \\u003e\"]") 91 | XCTAssertEqual(json[0]!.stringValue!, "< >") 92 | } 93 | 94 | func testUnicodeEscapeSequencesWith32bitsUnicodeScalar() { 95 | let json = try! Json.deserialize("[\"\\u0001\\uF363\"]") 96 | XCTAssertEqual(json[0]!.stringValue, "\u{0001F363}") 97 | } 98 | 99 | func testUnicodeEscapeSequencesWithTwo16bitsUnicodeScalar() { 100 | let json = try! Json.deserialize("[\"\\u00015\\uF363\"]") 101 | XCTAssertEqual(json[0]!.stringValue, "\u{0001}5\u{F363}") 102 | } 103 | 104 | func testTwitterJson() { 105 | let json = try! Json.deserialize(complexJsonExample("tweets")) 106 | XCTAssertEqual(json["statuses"]![0]!["id_str"]!.stringValue, "250075927172759552") 107 | } 108 | 109 | func testStackexchangeJson() { 110 | let json = try! Json.deserialize(complexJsonExample("stackoverflow-items")) 111 | XCTAssertEqual(json["items"]![0]!["view_count"]!.intValue, 18711) 112 | } 113 | 114 | 115 | func testPerformanceExampleWithNSData() { 116 | let jsonSource = complexJsonExample("tweets") 117 | 118 | self.measureBlock { 119 | let _ = try! Json.deserialize(jsonSource) 120 | } 121 | } 122 | 123 | func testPerformanceExampleWithString() { 124 | let jsonSource = String(data: complexJsonExample("tweets"), encoding: NSUTF8StringEncoding)! 125 | 126 | self.measureBlock { 127 | let _ = try! Json.deserialize(jsonSource) 128 | } 129 | } 130 | 131 | func testPerformanceExampleInJSONSerialization() { 132 | let jsonSource = complexJsonExample("tweets") 133 | self.measureBlock { 134 | let _: AnyObject? = try! NSJSONSerialization 135 | .JSONObjectWithData(jsonSource, options: .MutableContainers) 136 | } 137 | } 138 | 139 | func complexJsonExample(name: String) -> NSData { 140 | let bundle = NSBundle(forClass: self.dynamicType) 141 | let path = bundle.pathForResource(name, ofType: "json")! 142 | return NSData(contentsOfFile: path)! 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /JsonSerializerTests/JsonTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JsonTests.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/15. 6 | // Copyright (c) 2014 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class JsonTests: XCTestCase { 12 | 13 | func testConvenienceConvertions() { 14 | do { 15 | let json = try Json.deserialize("[\"foo bar\", true, false]") 16 | XCTAssertEqual(json.description, "[\"foo bar\",true,false]") 17 | 18 | XCTAssertEqual(json[0]!.stringValue!, "foo bar") 19 | XCTAssertEqual(json[1]!.boolValue!, true) 20 | XCTAssertEqual(json[2]!.boolValue!, false) 21 | 22 | XCTAssertEqual(json[3]?.stringValue, nil, "out of range") 23 | XCTAssertEqual(json[3]?[0]?.stringValue, nil, "no such item") 24 | XCTAssertEqual(json["no such property"]?.stringValue, nil, "no such property") 25 | XCTAssertEqual(json["no"]?["such"]?["property"]?.stringValue, nil, "no such properties") 26 | } catch { 27 | XCTFail("\(error)") 28 | } 29 | } 30 | 31 | 32 | func testConvertFromNilLiteral() { 33 | let value: Json = nil 34 | XCTAssertEqual(value, Json.NullValue) 35 | } 36 | 37 | func testConvertFromBooleanLiteral() { 38 | let a: Json = true 39 | XCTAssertEqual(a, Json.from(true)) 40 | 41 | let b: Json = false 42 | XCTAssertEqual(b, Json.from(false)) 43 | } 44 | 45 | func testConvertFromIntegerLiteral() { 46 | let a: Json = 42 47 | XCTAssertEqual(a, Json.from(42)) 48 | } 49 | 50 | func testConvertFromFloatLiteral() { 51 | let a: Json = 3.14 52 | XCTAssertEqual(a, Json.from(3.14)) 53 | } 54 | 55 | func testConvertFromStringLiteral() { 56 | let a: Json = "foo" 57 | XCTAssertEqual(a, Json.from("foo")) 58 | } 59 | 60 | func testConvertFromArrayLiteral() { 61 | let a: Json = [nil, true, 10, "foo"] 62 | 63 | let expected = try! Json.deserialize("[null, true, 10, \"foo\"]") 64 | XCTAssertEqual(a, expected) 65 | } 66 | 67 | func testConvertFromDictionaryLiteral() { 68 | let array: Json = ["foo": 10, "bar": true] 69 | 70 | let expected = try! Json.deserialize("{ \"foo\": 10, \"bar\": true }") 71 | XCTAssertEqual(array, expected) 72 | } 73 | 74 | func testPrintable() { 75 | let x: CustomStringConvertible = Json.from(true) 76 | 77 | XCTAssertEqual(x.description, "true", "Printable#description") 78 | } 79 | 80 | func testDebugPrintable() { 81 | let x: CustomDebugStringConvertible = Json.from(true) 82 | 83 | XCTAssertEqual(x.debugDescription, "true", "DebugPrintable#debugDescription") 84 | } 85 | 86 | func testPrlettySerializer() { 87 | let x = Json.from([true, [ "foo": 1, "bar": 2 ], false]) 88 | XCTAssertEqual(x.debugDescription, 89 | "[\n" + 90 | " true,\n" + 91 | " {\n" + 92 | " \"bar\": 2,\n" + 93 | " \"foo\": 1 },\n" + 94 | " false ]", 95 | "PrettyJsonSerializer") 96 | } 97 | 98 | let e: Json = nil 99 | let b0: Json = false 100 | let b1: Json = true 101 | let n0: Json = 0 102 | let n1: Json = 10 103 | let s0: Json = "" 104 | let s1: Json = "foo" 105 | let a0: Json = [] 106 | let a1: Json = [true, "foo"] 107 | let o0: Json = [:] 108 | let o1: Json = ["foo": [10, false]] 109 | 110 | 111 | func testNullValueEquality() { 112 | XCTAssertEqual(e, e) 113 | XCTAssertNotEqual(e, b0) 114 | XCTAssertNotEqual(e, n0) 115 | XCTAssertNotEqual(e, s0) 116 | XCTAssertNotEqual(e, a0) 117 | XCTAssertNotEqual(e, o0) 118 | } 119 | 120 | func testBooleanValueEquality() { 121 | XCTAssertEqual(b0, b0) 122 | XCTAssertEqual(b1, b1) 123 | XCTAssertNotEqual(b0, e) 124 | XCTAssertNotEqual(b0, b1) 125 | XCTAssertNotEqual(b0, s0) 126 | XCTAssertNotEqual(b0, a0) 127 | XCTAssertNotEqual(b0, o0) 128 | } 129 | 130 | func testNumberValueEquality() { 131 | XCTAssertEqual(n0, n0) 132 | XCTAssertEqual(n1, n1) 133 | XCTAssertNotEqual(n0, e) 134 | XCTAssertNotEqual(n0, b0) 135 | XCTAssertNotEqual(n0, n1) 136 | XCTAssertNotEqual(n0, s0) 137 | XCTAssertNotEqual(n0, a0) 138 | XCTAssertNotEqual(n0, o0) 139 | } 140 | 141 | func testStringValueEquality() { 142 | XCTAssertEqual(s0, s0) 143 | XCTAssertEqual(s1, s1) 144 | XCTAssertNotEqual(s0, e) 145 | XCTAssertNotEqual(s0, b0) 146 | XCTAssertNotEqual(s0, n0) 147 | XCTAssertNotEqual(s0, s1) 148 | XCTAssertNotEqual(s0, a0) 149 | XCTAssertNotEqual(s0, o0) 150 | } 151 | 152 | func testArrayValueEquality() { 153 | XCTAssertEqual(a0, a0) 154 | XCTAssertEqual(a1, a1) 155 | XCTAssertNotEqual(a0, e) 156 | XCTAssertNotEqual(a0, b0) 157 | XCTAssertNotEqual(a0, n0) 158 | XCTAssertNotEqual(a0, s0) 159 | XCTAssertNotEqual(a0, a1) 160 | XCTAssertNotEqual(a0, o0) 161 | } 162 | 163 | func testObjectValueEquality() { 164 | XCTAssertEqual(o0, o0) 165 | XCTAssertEqual(o1, o1) 166 | XCTAssertNotEqual(o0, e) 167 | XCTAssertNotEqual(o0, b0) 168 | XCTAssertNotEqual(o0, n0) 169 | XCTAssertNotEqual(o0, s0) 170 | XCTAssertNotEqual(o0, a0) 171 | XCTAssertNotEqual(s0, o1) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /JsonSerializerTests/ParseErrorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseErrorTests.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/11. 6 | // Copyright (c) 2014 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ParseErrorTests: XCTestCase { 12 | 13 | func testEmptyString() { 14 | do { 15 | let _ = try Json.deserialize("") 16 | XCTFail("not reached") 17 | } catch let error as InsufficientTokenError { 18 | XCTAssertEqual(error.lineNumber, 1, "lineNumbeer") 19 | XCTAssertEqual(error.columnNumber, 1, "columnNumbeer") 20 | } catch { 21 | XCTFail("Unexpected error: \(error)") 22 | } 23 | } 24 | 25 | func testUnexpectedToken() { 26 | do { 27 | let _ = try Json.deserialize("?") 28 | XCTFail("not reached") 29 | } catch let error as UnexpectedTokenError { 30 | XCTAssertEqual(error.lineNumber, 1, "lineNumbeer") 31 | XCTAssertEqual(error.columnNumber, 1, "columnNumbeer") 32 | } catch { 33 | XCTFail("Unexpected error: \(error)") 34 | } 35 | } 36 | 37 | func testSourceLocation() { 38 | do { 39 | let _ = try Json.deserialize("[\n ?") 40 | XCTFail("not reached") 41 | } catch let error as UnexpectedTokenError { 42 | XCTAssertEqual(error.lineNumber, 2, "lineNumbeer") 43 | XCTAssertEqual(error.columnNumber, 5, "columnNumbeer") 44 | } catch { 45 | XCTFail("Unexpected error: \(error)") 46 | } 47 | } 48 | 49 | func testExtraTokens() { 50 | do { 51 | let _ = try Json.deserialize("[] []") 52 | XCTFail("not reached") 53 | } catch let error as ExtraTokenError { 54 | XCTAssertEqual(error.lineNumber, 1, "lineNumbeer") 55 | XCTAssertEqual(error.columnNumber, 4, "columnNumbeer") 56 | } catch { 57 | XCTFail("Unexpected error: \(error)") 58 | } 59 | } 60 | 61 | func testInvalidNumber() { 62 | do { 63 | let _ = try Json.deserialize("[ 10. ]") 64 | XCTFail("not reached") 65 | } catch let error as InvalidNumberError { 66 | XCTAssertEqual(error.lineNumber, 1, "lineNumbeer") 67 | XCTAssertEqual(error.columnNumber, 6, "columnNumbeer") 68 | } catch { 69 | XCTFail("Unexpected error: \(error)") 70 | } 71 | } 72 | 73 | 74 | func testMissingDoubleQuote() { 75 | do { 76 | let _ = try Json.deserialize("[ \"foo, null ]") 77 | XCTFail("not reached") 78 | } catch let error as InvalidStringError { 79 | XCTAssertEqual(error.lineNumber, 1, "lineNumbeer") 80 | XCTAssertEqual(error.columnNumber, 14, "columnNumbeer") 81 | } catch { 82 | XCTFail("Unexpected error: \(error)") 83 | } 84 | } 85 | 86 | func testMissingEscapedChar() { 87 | do { 88 | let _ = try Json.deserialize("[ \"foo \\") 89 | XCTFail("not reached") 90 | } catch let error as InvalidStringError { 91 | XCTAssertEqual(error.lineNumber, 1, "lineNumbeer") 92 | XCTAssertEqual(error.columnNumber, 8, "columnNumbeer") 93 | } catch { 94 | XCTFail("Unexpected error: \(error)") 95 | } 96 | } 97 | 98 | func testMissingColon() { 99 | do { 100 | let _ = try Json.deserialize("{ \"foo\" ") 101 | XCTFail("not reached") 102 | } catch let error as UnexpectedTokenError { 103 | XCTAssertEqual(error.lineNumber, 1, "lineNumbeer") 104 | XCTAssertEqual(error.columnNumber, 8, "columnNumbeer") 105 | } catch { 106 | XCTFail("Unexpected error: \(error)") 107 | } 108 | } 109 | 110 | func testMissingObjecValue() { 111 | do { 112 | let _ = try Json.deserialize("{ \"foo\": ") 113 | XCTFail("not reached") 114 | } catch let error as InsufficientTokenError { 115 | XCTAssertEqual(error.lineNumber, 1, "lineNumbeer") 116 | XCTAssertEqual(error.columnNumber, 9, "columnNumbeer") 117 | } catch { 118 | XCTFail("Unexpected error: \(error)") 119 | } 120 | } 121 | 122 | 123 | func testInvalidEscapeSequence() { 124 | do { 125 | let _ = try Json.deserialize("\"\\uFFF\"") 126 | XCTFail("not reached") 127 | } catch let error as InvalidStringError { 128 | XCTAssertEqual(error.lineNumber, 1, "lineNumbeer") 129 | XCTAssertEqual(error.columnNumber, 6, "columnNumbeer") 130 | } catch { 131 | XCTFail("Unexpected error: \(error)") 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /JsonSerializerTests/StringUtilsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringUtilsTests.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/15. 6 | // Copyright (c) 2014 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class StringUtilsTests: XCTestCase { 12 | func testEscapeNewline() { 13 | XCTAssertEqual(escapeAsJsonString("\n \n"), "\"\\n \\n\"", "for \\n") 14 | } 15 | 16 | func testEscapeTab() { 17 | XCTAssertEqual(escapeAsJsonString("\t \t"), "\"\\t \\t\"", "for \\t") 18 | } 19 | 20 | func testEscapeReturn() { 21 | XCTAssertEqual(escapeAsJsonString("\r \r"), "\"\\r \\r\"", "for \\r") 22 | } 23 | 24 | func testEscapeBackslash() { 25 | XCTAssertEqual(escapeAsJsonString("\\ \\"), "\"\\\\ \\\\\"", "for \\") 26 | } 27 | 28 | func testEscapeDoublequote() { 29 | XCTAssertEqual(escapeAsJsonString("\" \""), "\"\\\" \\\"\"", "for \"") 30 | } 31 | 32 | func testEscapeLineSeparator() { 33 | XCTAssertEqual(escapeAsJsonString("\" \""), "\"\\\" \\\"\"", "for \"") 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /JsonSerializerTests/TemplateIcon2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gfx/Swift-PureJsonSerializer/4193ca54afd8c9e1af801edbb1465b8f24dd6180/JsonSerializerTests/TemplateIcon2x.png -------------------------------------------------------------------------------- /JsonSerializerTests/stackoverflow-items.json: -------------------------------------------------------------------------------- 1 | { 2 | "quota_remaining" : 276, 3 | "quota_max" : 300, 4 | "has_more" : true, 5 | "items" : [ 6 | { 7 | "owner" : { 8 | "accept_rate" : 74, 9 | "display_name" : "David Mulder", 10 | "reputation" : 6290, 11 | "user_type" : "registered", 12 | "profile_image" : "https://www.gravatar.com/avatar/5070419219d8d19bfdd2cbc0db761c87?s=128&d=identicon&r=PG", 13 | "link" : "http://stackoverflow.com/users/1266242/david-mulder", 14 | "user_id" : 1266242 15 | }, 16 | "accepted_answer_id" : 24005242, 17 | "score" : 108, 18 | "is_answered" : true, 19 | "tags" : [ 20 | "objective-c", 21 | "swift" 22 | ], 23 | "question_id" : 24002369, 24 | "link" : "http://stackoverflow.com/questions/24002369/how-to-call-objective-c-code-from-swift", 25 | "last_activity_date" : 1407887767, 26 | "answer_count" : 5, 27 | "creation_date" : 1401739542, 28 | "title" : "How to call Objective C code from Swift", 29 | "view_count" : 18711, 30 | "last_edit_date" : 1405693956 31 | }, 32 | { 33 | "creation_date" : 1401737157, 34 | "answer_count" : 13, 35 | "link" : "http://stackoverflow.com/questions/24001778/do-swift-based-applications-work-on-os-x-10-9-ios-7-and-lower", 36 | "last_activity_date" : 1407029241, 37 | "question_id" : 24001778, 38 | "is_answered" : true, 39 | "tags" : [ 40 | "ios", 41 | "osx", 42 | "swift" 43 | ], 44 | "score" : 286, 45 | "accepted_answer_id" : 24002461, 46 | "owner" : { 47 | "display_name" : "MeIr", 48 | "accept_rate" : 87, 49 | "reputation" : 1937, 50 | "user_type" : "registered", 51 | "profile_image" : "http://i.stack.imgur.com/xDQHx.jpg?s=128&g=1", 52 | "link" : "http://stackoverflow.com/users/635610/meir", 53 | "user_id" : 635610 54 | }, 55 | "last_edit_date" : 1405194444, 56 | "view_count" : 43818, 57 | "protected_date" : 1410130480, 58 | "title" : "Do Swift-based applications work on OS X 10.9/iOS 7 and lower?" 59 | }, 60 | { 61 | "title" : "What is an optional value in Swift?", 62 | "last_edit_date" : 1401940641, 63 | "view_count" : 7330, 64 | "score" : 24, 65 | "accepted_answer_id" : 24026093, 66 | "owner" : { 67 | "user_id" : 1223781, 68 | "link" : "http://stackoverflow.com/users/1223781/timvermeulen", 69 | "profile_image" : "https://www.gravatar.com/avatar/886f7902c986a59d5cc184425d513c36?s=128&d=identicon&r=PG", 70 | "user_type" : "registered", 71 | "display_name" : "timvermeulen", 72 | "accept_rate" : 94, 73 | "reputation" : 1030 74 | }, 75 | "creation_date" : 1401744749, 76 | "answer_count" : 6, 77 | "link" : "http://stackoverflow.com/questions/24003642/what-is-an-optional-value-in-swift", 78 | "last_activity_date" : 1405157336, 79 | "question_id" : 24003642, 80 | "is_answered" : true, 81 | "tags" : [ 82 | "cocoa", 83 | "swift" 84 | ] 85 | }, 86 | { 87 | "score" : 80, 88 | "accepted_answer_id" : 24007718, 89 | "owner" : { 90 | "display_name" : "Arbitur", 91 | "accept_rate" : 74, 92 | "reputation" : 2088, 93 | "profile_image" : "http://i.stack.imgur.com/66k9L.png?s=128&g=1", 94 | "user_type" : "registered", 95 | "link" : "http://stackoverflow.com/users/1296280/arbitur", 96 | "user_id" : 1296280 97 | }, 98 | "creation_date" : 1401772903, 99 | "answer_count" : 11, 100 | "link" : "http://stackoverflow.com/questions/24007650/selector-in-swift", 101 | "question_id" : 24007650, 102 | "last_activity_date" : 1407425345, 103 | "is_answered" : true, 104 | "tags" : [ 105 | "swift" 106 | ], 107 | "title" : "@selector() in Swift?", 108 | "last_edit_date" : 1405677146, 109 | "view_count" : 16371 110 | }, 111 | { 112 | "last_activity_date" : 1406238601, 113 | "link" : "http://stackoverflow.com/questions/24018327/what-does-an-exclamation-mark-mean-in-the-swift-language", 114 | "question_id" : 24018327, 115 | "answer_count" : 8, 116 | "creation_date" : 1401807146, 117 | "is_answered" : true, 118 | "tags" : [ 119 | "swift" 120 | ], 121 | "score" : 67, 122 | "owner" : { 123 | "link" : "http://stackoverflow.com/users/1354350/troy", 124 | "user_id" : 1354350, 125 | "user_type" : "registered", 126 | "profile_image" : "https://www.gravatar.com/avatar/b8ae7541deaa2882e97ade27eb9934a4?s=128&d=identicon&r=PG", 127 | "reputation" : 1332, 128 | "display_name" : "Troy", 129 | "accept_rate" : 68 130 | }, 131 | "accepted_answer_id" : 24020951, 132 | "last_edit_date" : 1401899980, 133 | "view_count" : 7319, 134 | "title" : "What does an exclamation mark mean in the Swift language?" 135 | }, 136 | { 137 | "score" : 31, 138 | "accepted_answer_id" : 24055762, 139 | "owner" : { 140 | "reputation" : 163, 141 | "display_name" : "user3524868", 142 | "user_id" : 3524868, 143 | "link" : "http://stackoverflow.com/users/3524868/user3524868", 144 | "user_type" : "registered", 145 | "profile_image" : "http://graph.facebook.com/513116096/picture?type=large" 146 | }, 147 | "creation_date" : 1401940038, 148 | "answer_count" : 9, 149 | "link" : "http://stackoverflow.com/questions/24051314/precision-string-format-specifier-in-swift", 150 | "question_id" : 24051314, 151 | "last_activity_date" : 1409539916, 152 | "is_answered" : true, 153 | "tags" : [ 154 | "swift" 155 | ], 156 | "title" : "Precision String Format Specifier In Swift", 157 | "last_edit_date" : 1403206972, 158 | "view_count" : 8279 159 | }, 160 | { 161 | "owner" : { 162 | "display_name" : "David", 163 | "reputation" : 6859, 164 | "user_id" : 3203487, 165 | "link" : "http://stackoverflow.com/users/3203487/david", 166 | "profile_image" : "https://www.gravatar.com/avatar/642430374fe647174b3e8bc33cdfc049?s=128&d=identicon&r=PG&f=1", 167 | "user_type" : "registered" 168 | }, 169 | "accepted_answer_id" : 24073016, 170 | "score" : 135, 171 | "tags" : [ 172 | "swift" 173 | ], 174 | "is_answered" : true, 175 | "link" : "http://stackoverflow.com/questions/24024549/dispatch-once-singleton-model-in-swift", 176 | "question_id" : 24024549, 177 | "last_activity_date" : 1410054642, 178 | "creation_date" : 1401828072, 179 | "answer_count" : 13, 180 | "protected_date" : 1405980112, 181 | "title" : "dispatch_once singleton model in swift", 182 | "view_count" : 18328, 183 | "last_edit_date" : 1410054642 184 | }, 185 | { 186 | "owner" : { 187 | "reputation" : 1445, 188 | "display_name" : "Tony", 189 | "accept_rate" : 38, 190 | "link" : "http://stackoverflow.com/users/953628/tony", 191 | "user_id" : 953628, 192 | "user_type" : "registered", 193 | "profile_image" : "https://www.gravatar.com/avatar/d9436250503d5a9eb5b2a118d274aa70?s=128&d=identicon&r=PG" 194 | }, 195 | "score" : 83, 196 | "is_answered" : true, 197 | "tags" : [ 198 | "ios", 199 | "xcode", 200 | "swift" 201 | ], 202 | "answer_count" : 22, 203 | "creation_date" : 1401762574, 204 | "link" : "http://stackoverflow.com/questions/24006206/sourcekitservice-terminated", 205 | "question_id" : 24006206, 206 | "last_activity_date" : 1410744471, 207 | "protected_date" : 1402190168, 208 | "title" : "SourceKitService Terminated", 209 | "view_count" : 10792, 210 | "last_edit_date" : 1402705580 211 | }, 212 | { 213 | "last_edit_date" : 1403207137, 214 | "view_count" : 15197, 215 | "title" : "Does Swift have access modifiers?", 216 | "creation_date" : 1401745967, 217 | "answer_count" : 13, 218 | "question_id" : 24003918, 219 | "link" : "http://stackoverflow.com/questions/24003918/does-swift-have-access-modifiers", 220 | "last_activity_date" : 1409741563, 221 | "tags" : [ 222 | "access-modifiers", 223 | "swift" 224 | ], 225 | "is_answered" : true, 226 | "score" : 123, 227 | "accepted_answer_id" : 24012515, 228 | "owner" : { 229 | "accept_rate" : 100, 230 | "display_name" : "Gergo Erdosi", 231 | "reputation" : 8794, 232 | "user_type" : "registered", 233 | "profile_image" : "https://www.gravatar.com/avatar/f9246128ad0f92d9f09c5ecd114acdb8?s=128&d=identicon&r=PG", 234 | "link" : "http://stackoverflow.com/users/775443/gergo-erdosi", 235 | "user_id" : 775443 236 | } 237 | }, 238 | { 239 | "view_count" : 8809, 240 | "last_edit_date" : 1401835434, 241 | "title" : "How do I print the type or class of a variable in Swift?", 242 | "is_answered" : true, 243 | "tags" : [ 244 | "ios", 245 | "swift" 246 | ], 247 | "creation_date" : 1401762282, 248 | "answer_count" : 11, 249 | "link" : "http://stackoverflow.com/questions/24006165/how-do-i-print-the-type-or-class-of-a-variable-in-swift", 250 | "question_id" : 24006165, 251 | "last_activity_date" : 1410831085, 252 | "accepted_answer_id" : 24079729, 253 | "owner" : { 254 | "display_name" : "Matt Bridges", 255 | "reputation" : 14771, 256 | "accept_rate" : 91, 257 | "link" : "http://stackoverflow.com/users/125871/matt-bridges", 258 | "user_id" : 125871, 259 | "user_type" : "registered", 260 | "profile_image" : "https://www.gravatar.com/avatar/a7e5d665b12e355e717e8de7f2627ec5?s=128&d=identicon&r=PG" 261 | }, 262 | "score" : 26 263 | }, 264 | { 265 | "title" : "Swift: Class does not implement its superclass's required members", 266 | "view_count" : 5180, 267 | "last_edit_date" : 1407181428, 268 | "accepted_answer_id" : 25128815, 269 | "owner" : { 270 | "display_name" : "gfrs", 271 | "reputation" : 1504, 272 | "accept_rate" : 100, 273 | "user_type" : "registered", 274 | "profile_image" : "https://www.gravatar.com/avatar/1d560c523dea22722fe39a89c903c33d?s=128&d=identicon&r=PG", 275 | "link" : "http://stackoverflow.com/users/2158465/gfrs", 276 | "user_id" : 2158465 277 | }, 278 | "score" : 63, 279 | "is_answered" : true, 280 | "tags" : [ 281 | "ios", 282 | "swift", 283 | "sprite-kit" 284 | ], 285 | "creation_date" : 1407181302, 286 | "answer_count" : 3, 287 | "question_id" : 25126295, 288 | "link" : "http://stackoverflow.com/questions/25126295/swift-class-does-not-implement-its-superclasss-required-members", 289 | "last_activity_date" : 1407506258 290 | }, 291 | { 292 | "owner" : { 293 | "display_name" : "hemantchittora", 294 | "reputation" : 756, 295 | "user_id" : 2469421, 296 | "link" : "http://stackoverflow.com/users/2469421/hemantchittora", 297 | "user_type" : "registered", 298 | "profile_image" : "https://www.gravatar.com/avatar/fa6512c51f72864da88f030f2d3e4710?s=128&d=identicon&r=PG&f=1" 299 | }, 300 | "accepted_answer_id" : 24042126, 301 | "score" : 40, 302 | "tags" : [ 303 | "ios", 304 | "xcode", 305 | "swift", 306 | "ios8" 307 | ], 308 | "is_answered" : true, 309 | "link" : "http://stackoverflow.com/questions/24033417/unable-to-run-app-in-simulator-xcode-beta-6-ios-8", 310 | "question_id" : 24033417, 311 | "last_activity_date" : 1409087830, 312 | "creation_date" : 1401873204, 313 | "answer_count" : 11, 314 | "title" : "Unable to run app in Simulator : Xcode beta 6 iOS 8", 315 | "view_count" : 16728, 316 | "last_edit_date" : 1401881123 317 | }, 318 | { 319 | "last_edit_date" : 1402214120, 320 | "view_count" : 3161, 321 | "title" : "How can I extend typed Arrays in Swift?", 322 | "answer_count" : 1, 323 | "creation_date" : 1401841574, 324 | "question_id" : 24027116, 325 | "link" : "http://stackoverflow.com/questions/24027116/how-can-i-extend-typed-arrays-in-swift", 326 | "last_activity_date" : 1405361353, 327 | "is_answered" : true, 328 | "tags" : [ 329 | "swift" 330 | ], 331 | "score" : 32, 332 | "accepted_answer_id" : 24028208, 333 | "owner" : { 334 | "link" : "http://stackoverflow.com/users/85785/mythz", 335 | "user_id" : 85785, 336 | "profile_image" : "https://www.gravatar.com/avatar/1257196ff88132651f94ac85f662c038?s=128&d=identicon&r=PG", 337 | "user_type" : "registered", 338 | "reputation" : 57680, 339 | "display_name" : "mythz", 340 | "accept_rate" : 83 341 | } 342 | }, 343 | { 344 | "view_count" : 1554, 345 | "last_edit_date" : 1402092007, 346 | "title" : "Passing an array to a function with variable number of args in Swift", 347 | "is_answered" : true, 348 | "tags" : [ 349 | "swift", 350 | "varargs" 351 | ], 352 | "last_activity_date" : 1402802880, 353 | "link" : "http://stackoverflow.com/questions/24024376/passing-an-array-to-a-function-with-variable-number-of-args-in-swift", 354 | "question_id" : 24024376, 355 | "creation_date" : 1401827357, 356 | "answer_count" : 4, 357 | "owner" : { 358 | "reputation" : 87599, 359 | "display_name" : "Ole Begemann", 360 | "accept_rate" : 89, 361 | "user_type" : "registered", 362 | "profile_image" : "https://www.gravatar.com/avatar/d0efc09d023fa0569a2479c9dcfd4620?s=128&d=identicon&r=PG", 363 | "link" : "http://stackoverflow.com/users/116862/ole-begemann", 364 | "user_id" : 116862 365 | }, 366 | "accepted_answer_id" : 24024724, 367 | "score" : 19 368 | }, 369 | { 370 | "view_count" : 3763, 371 | "last_edit_date" : 1402570911, 372 | "title" : "Does Swift support reflection?", 373 | "is_answered" : true, 374 | "tags" : [ 375 | "ios", 376 | "reflection", 377 | "swift" 378 | ], 379 | "answer_count" : 4, 380 | "creation_date" : 1401972496, 381 | "link" : "http://stackoverflow.com/questions/24060667/does-swift-support-reflection", 382 | "question_id" : 24060667, 383 | "last_activity_date" : 1409915965, 384 | "accepted_answer_id" : 24069875, 385 | "owner" : { 386 | "user_id" : 1572953, 387 | "link" : "http://stackoverflow.com/users/1572953/khanh-nguyen", 388 | "profile_image" : "https://www.gravatar.com/avatar/b708557dd71da57025ad0e68fe385e71?s=128&d=identicon&r=PG", 389 | "user_type" : "registered", 390 | "reputation" : 2027, 391 | "display_name" : "Khanh Nguyen", 392 | "accept_rate" : 83 393 | }, 394 | "score" : 33 395 | }, 396 | { 397 | "title" : "Why create "Implicitly Unwrapped Optionals"?", 398 | "view_count" : 3512, 399 | "owner" : { 400 | "user_id" : 974407, 401 | "link" : "http://stackoverflow.com/users/974407/johnston", 402 | "profile_image" : "https://www.gravatar.com/avatar/19c57d25aa8932f3c41257ab756b69eb?s=128&d=identicon&r=PG", 403 | "user_type" : "registered", 404 | "display_name" : "Johnston", 405 | "reputation" : 1306, 406 | "accept_rate" : 84 407 | }, 408 | "accepted_answer_id" : 24007032, 409 | "score" : 56, 410 | "tags" : [ 411 | "swift" 412 | ], 413 | "is_answered" : true, 414 | "link" : "http://stackoverflow.com/questions/24006975/why-create-implicitly-unwrapped-optionals", 415 | "question_id" : 24006975, 416 | "last_activity_date" : 1409943604, 417 | "creation_date" : 1401768573, 418 | "answer_count" : 7 419 | }, 420 | { 421 | "score" : 14, 422 | "accepted_answer_id" : 24013852, 423 | "owner" : { 424 | "display_name" : "kmithi", 425 | "accept_rate" : 100, 426 | "reputation" : 283, 427 | "profile_image" : "https://www.gravatar.com/avatar/26bc8b9f0ddb16b527184034697c8bbe?s=128&d=identicon&r=PG", 428 | "user_type" : "registered", 429 | "user_id" : 1974462, 430 | "link" : "http://stackoverflow.com/users/1974462/kmithi" 431 | }, 432 | "answer_count" : 5, 433 | "creation_date" : 1401793094, 434 | "question_id" : 24013410, 435 | "link" : "http://stackoverflow.com/questions/24013410/how-to-parse-a-json-file-in-swift", 436 | "last_activity_date" : 1409287892, 437 | "is_answered" : true, 438 | "tags" : [ 439 | "ios", 440 | "swift" 441 | ], 442 | "title" : "How to parse a JSON file in swift?", 443 | "last_edit_date" : 1401813062, 444 | "view_count" : 11025 445 | }, 446 | { 447 | "view_count" : 2767, 448 | "last_edit_date" : 1404232795, 449 | "title" : "Input from the keyboard in command line application", 450 | "is_answered" : true, 451 | "tags" : [ 452 | "osx", 453 | "cocoa", 454 | "swift" 455 | ], 456 | "creation_date" : 1401750952, 457 | "answer_count" : 5, 458 | "question_id" : 24004776, 459 | "link" : "http://stackoverflow.com/questions/24004776/input-from-the-keyboard-in-command-line-application", 460 | "last_activity_date" : 1410241782, 461 | "accepted_answer_id" : 24021467, 462 | "owner" : { 463 | "profile_image" : "https://www.gravatar.com/avatar/0333c8c993f63d263c9bc59ad2c35a9b?s=128&d=identicon&r=PG", 464 | "user_type" : "registered", 465 | "user_id" : 155417, 466 | "link" : "http://stackoverflow.com/users/155417/chalkers", 467 | "display_name" : "Chalkers", 468 | "reputation" : 531 469 | }, 470 | "score" : 17 471 | }, 472 | { 473 | "creation_date" : 1408393893, 474 | "answer_count" : 1, 475 | "question_id" : 25371556, 476 | "link" : "http://stackoverflow.com/questions/25371556/swift-beta-6-confusing-linker-error-message", 477 | "last_activity_date" : 1409247367, 478 | "tags" : [ 479 | "swift", 480 | "xcode6" 481 | ], 482 | "is_answered" : true, 483 | "score" : 46, 484 | "accepted_answer_id" : 25376271, 485 | "owner" : { 486 | "display_name" : "DrScott", 487 | "reputation" : 238, 488 | "link" : "http://stackoverflow.com/users/3730849/drscott", 489 | "user_id" : 3730849, 490 | "profile_image" : "https://www.gravatar.com/avatar/1bed9f5df224d16142a3aeccd89933c0?s=128&d=identicon&r=PG&f=1", 491 | "user_type" : "registered" 492 | }, 493 | "view_count" : 2497, 494 | "title" : "Swift beta 6 - Confusing linker error message" 495 | }, 496 | { 497 | "view_count" : 2729, 498 | "last_edit_date" : 1403661278, 499 | "title" : "Swift UIColor initializer - compiler Error only when targeting iPhone5s", 500 | "is_answered" : true, 501 | "tags" : [ 502 | "ios", 503 | "swift" 504 | ], 505 | "answer_count" : 2, 506 | "creation_date" : 1402624017, 507 | "last_activity_date" : 1403661278, 508 | "link" : "http://stackoverflow.com/questions/24196528/swift-uicolor-initializer-compiler-error-only-when-targeting-iphone5s", 509 | "question_id" : 24196528, 510 | "accepted_answer_id" : 24196575, 511 | "owner" : { 512 | "display_name" : "Joe", 513 | "reputation" : 1230, 514 | "user_type" : "registered", 515 | "profile_image" : "https://www.gravatar.com/avatar/829b1016785383a7c796653df77e8c2f?s=128&d=identicon&r=PG", 516 | "link" : "http://stackoverflow.com/users/2397883/joe", 517 | "user_id" : 2397883 518 | }, 519 | "score" : 10 520 | }, 521 | { 522 | "view_count" : 965, 523 | "last_edit_date" : 1403205914, 524 | "title" : "Crash when casting the result of arc4random() to Int", 525 | "is_answered" : true, 526 | "tags" : [ 527 | "random", 528 | "swift" 529 | ], 530 | "last_activity_date" : 1408791281, 531 | "link" : "http://stackoverflow.com/questions/24087518/crash-when-casting-the-result-of-arc4random-to-int", 532 | "question_id" : 24087518, 533 | "answer_count" : 3, 534 | "creation_date" : 1402075705, 535 | "owner" : { 536 | "profile_image" : "https://www.gravatar.com/avatar/cbf2646af29b9514dc8c1d1db658595c?s=128&d=identicon&r=PG", 537 | "user_type" : "registered", 538 | "user_id" : 145965, 539 | "link" : "http://stackoverflow.com/users/145965/shaun-inman", 540 | "display_name" : "Shaun Inman", 541 | "reputation" : 743, 542 | "accept_rate" : 93 543 | }, 544 | "accepted_answer_id" : 24088457, 545 | "score" : 4 546 | }, 547 | { 548 | "owner" : { 549 | "display_name" : "SiLo", 550 | "reputation" : 3205, 551 | "accept_rate" : 70, 552 | "link" : "http://stackoverflow.com/users/1970765/silo", 553 | "user_id" : 1970765, 554 | "profile_image" : "https://www.gravatar.com/avatar/fa2c11cb91eee337ba91d354f8c38592?s=128&d=identicon&r=PG", 555 | "user_type" : "registered" 556 | }, 557 | "accepted_answer_id" : 24050187, 558 | "score" : 18, 559 | "tags" : [ 560 | "introspection", 561 | "swift" 562 | ], 563 | "is_answered" : true, 564 | "link" : "http://stackoverflow.com/questions/24049673/swift-class-introspection-generics", 565 | "last_activity_date" : 1402626623, 566 | "question_id" : 24049673, 567 | "answer_count" : 4, 568 | "creation_date" : 1401927324, 569 | "title" : "Swift class introspection & generics", 570 | "view_count" : 4384 571 | }, 572 | { 573 | "score" : 15, 574 | "accepted_answer_id" : 24102130, 575 | "owner" : { 576 | "accept_rate" : 97, 577 | "display_name" : "Jiaaro", 578 | "reputation" : 15510, 579 | "user_id" : 2908, 580 | "link" : "http://stackoverflow.com/users/2908/jiaaro", 581 | "user_type" : "registered", 582 | "profile_image" : "https://www.gravatar.com/avatar/30e3f436d5fb8e941126a0c2c2868f84?s=128&d=identicon&r=PG" 583 | }, 584 | "answer_count" : 2, 585 | "creation_date" : 1402173983, 586 | "question_id" : 24101450, 587 | "link" : "http://stackoverflow.com/questions/24101450/how-do-you-find-out-the-type-of-an-object-in-swift", 588 | "last_activity_date" : 1408354745, 589 | "tags" : [ 590 | "swift" 591 | ], 592 | "is_answered" : true, 593 | "title" : "How do you find out the type of an object (in Swift)?", 594 | "last_edit_date" : 1405959536, 595 | "view_count" : 5659 596 | }, 597 | { 598 | "is_answered" : true, 599 | "tags" : [ 600 | "swift" 601 | ], 602 | "creation_date" : 1402324605, 603 | "answer_count" : 1, 604 | "last_activity_date" : 1403614959, 605 | "link" : "http://stackoverflow.com/questions/24122601/swift-variable-decorations-with-and", 606 | "question_id" : 24122601, 607 | "accepted_answer_id" : 24122706, 608 | "owner" : { 609 | "user_id" : 1414028, 610 | "link" : "http://stackoverflow.com/users/1414028/jay-morgan", 611 | "user_type" : "registered", 612 | "profile_image" : "https://www.gravatar.com/avatar/6c8ef36f633ab7e8d0043cb280bcc215?s=128&d=identicon&r=PG", 613 | "display_name" : "Jay Morgan", 614 | "reputation" : 750, 615 | "accept_rate" : 93 616 | }, 617 | "score" : 13, 618 | "view_count" : 497, 619 | "title" : "Swift variable decorations with "?" and "!"" 620 | }, 621 | { 622 | "question_id" : 24002092, 623 | "link" : "http://stackoverflow.com/questions/24002092/what-is-the-difference-between-let-and-var-in-swift", 624 | "last_activity_date" : 1407732542, 625 | "answer_count" : 13, 626 | "creation_date" : 1401738371, 627 | "tags" : [ 628 | "swift" 629 | ], 630 | "is_answered" : true, 631 | "score" : 11, 632 | "owner" : { 633 | "reputation" : 582, 634 | "display_name" : "Edward", 635 | "user_id" : 2440343, 636 | "link" : "http://stackoverflow.com/users/2440343/edward", 637 | "profile_image" : "https://www.gravatar.com/avatar/a9b6c953f88ac18c06b90073fbdb3329?s=128&d=identicon&r=PG", 638 | "user_type" : "registered" 639 | }, 640 | "accepted_answer_id" : 24002209, 641 | "last_edit_date" : 1401888913, 642 | "view_count" : 5739, 643 | "title" : "What is the difference between `let` and `var` in swift?" 644 | }, 645 | { 646 | "last_edit_date" : 1401819138, 647 | "view_count" : 4494, 648 | "title" : "Can't unwrap 'Optional.None'", 649 | "answer_count" : 3, 650 | "creation_date" : 1401763186, 651 | "question_id" : 24006287, 652 | "link" : "http://stackoverflow.com/questions/24006287/cant-unwrap-optional-none", 653 | "last_activity_date" : 1407915572, 654 | "tags" : [ 655 | "ios", 656 | "swift" 657 | ], 658 | "is_answered" : true, 659 | "score" : 5, 660 | "owner" : { 661 | "display_name" : "Tony", 662 | "accept_rate" : 38, 663 | "reputation" : 1445, 664 | "user_id" : 953628, 665 | "link" : "http://stackoverflow.com/users/953628/tony", 666 | "profile_image" : "https://www.gravatar.com/avatar/d9436250503d5a9eb5b2a118d274aa70?s=128&d=identicon&r=PG", 667 | "user_type" : "registered" 668 | } 669 | }, 670 | { 671 | "question_id" : 24057525, 672 | "link" : "http://stackoverflow.com/questions/24057525/swift-native-base-class-or-nsobject", 673 | "last_activity_date" : 1409315199, 674 | "answer_count" : 5, 675 | "creation_date" : 1401963547, 676 | "is_answered" : true, 677 | "tags" : [ 678 | "objective-c", 679 | "swift", 680 | "objective-c-runtime" 681 | ], 682 | "score" : 11, 683 | "owner" : { 684 | "user_id" : 404201, 685 | "link" : "http://stackoverflow.com/users/404201/jasper-blues", 686 | "user_type" : "registered", 687 | "profile_image" : "https://www.gravatar.com/avatar/c0cded6278df1ec9d9e15586340e3036?s=128&d=identicon&r=PG", 688 | "reputation" : 7742, 689 | "display_name" : "Jasper Blues", 690 | "accept_rate" : 92 691 | }, 692 | "accepted_answer_id" : 24067969, 693 | "last_edit_date" : 1402363962, 694 | "view_count" : 2827, 695 | "title" : "Swift native base class or NSObject" 696 | }, 697 | { 698 | "tags" : [ 699 | "ios", 700 | "osx", 701 | "swift" 702 | ], 703 | "is_answered" : true, 704 | "last_activity_date" : 1403374387, 705 | "link" : "http://stackoverflow.com/questions/24003291/ifdef-replacement-in-swift-language", 706 | "question_id" : 24003291, 707 | "answer_count" : 4, 708 | "creation_date" : 1401743197, 709 | "owner" : { 710 | "display_name" : "mxg", 711 | "reputation" : 4683, 712 | "accept_rate" : 90, 713 | "link" : "http://stackoverflow.com/users/193718/mxg", 714 | "user_id" : 193718, 715 | "profile_image" : "https://www.gravatar.com/avatar/a335f855072e3eb74aaa3bf9de0f8560?s=128&d=identicon&r=PG", 716 | "user_type" : "registered" 717 | }, 718 | "score" : 20, 719 | "view_count" : 3519, 720 | "last_edit_date" : 1401940672, 721 | "title" : "#ifdef replacement in swift language" 722 | }, 723 | { 724 | "answer_count" : 2, 725 | "creation_date" : 1401822725, 726 | "last_activity_date" : 1402343111, 727 | "link" : "http://stackoverflow.com/questions/24023112/try-catch-exceptions-in-swift", 728 | "question_id" : 24023112, 729 | "is_answered" : true, 730 | "tags" : [ 731 | "exception", 732 | "try-catch", 733 | "swift" 734 | ], 735 | "score" : 4, 736 | "accepted_answer_id" : 24023248, 737 | "owner" : { 738 | "accept_rate" : 94, 739 | "display_name" : "modocache", 740 | "reputation" : 2132, 741 | "link" : "http://stackoverflow.com/users/679254/modocache", 742 | "user_id" : 679254, 743 | "user_type" : "registered", 744 | "profile_image" : "https://www.gravatar.com/avatar/f921fa5d507b31ef6984fd3d77ae710c?s=128&d=identicon&r=PG" 745 | }, 746 | "closed_reason" : "duplicate", 747 | "last_edit_date" : 1402343111, 748 | "view_count" : 4748, 749 | "title" : "try-catch exceptions in Swift", 750 | "closed_date" : 1401823964 751 | }, 752 | { 753 | "score" : 5, 754 | "closed_reason" : "duplicate", 755 | "owner" : { 756 | "display_name" : "zbrox", 757 | "accept_rate" : 67, 758 | "reputation" : 428, 759 | "link" : "http://stackoverflow.com/users/240448/zbrox", 760 | "user_id" : 240448, 761 | "user_type" : "registered", 762 | "profile_image" : "https://www.gravatar.com/avatar/7f5eb8b92235249863df32ba699aaec5?s=128&d=identicon&r=PG" 763 | }, 764 | "accepted_answer_id" : 24047452, 765 | "link" : "http://stackoverflow.com/questions/24047374/string-formatting-of-a-double", 766 | "last_activity_date" : 1401915557, 767 | "question_id" : 24047374, 768 | "creation_date" : 1401915210, 769 | "answer_count" : 2, 770 | "is_answered" : true, 771 | "tags" : [ 772 | "string-formatting", 773 | "swift" 774 | ], 775 | "closed_date" : 1405133639, 776 | "title" : "String formatting of a Double", 777 | "view_count" : 3010 778 | } 779 | ] 780 | } 781 | -------------------------------------------------------------------------------- /JsonSerializerTests/tweets.json: -------------------------------------------------------------------------------- 1 | { 2 | "statuses": [ 3 | { 4 | "coordinates": null, 5 | "favorited": false, 6 | "truncated": false, 7 | "created_at": "Mon Sep 24 03:35:21 +0000 2012", 8 | "id_str": "250075927172759552", 9 | "entities": { 10 | "urls": [ 11 | 12 | ], 13 | "hashtags": [ 14 | { 15 | "text": "freebandnames", 16 | "indices": [ 17 | 20, 18 | 34 19 | ] 20 | } 21 | ], 22 | "user_mentions": [ 23 | 24 | ] 25 | }, 26 | "in_reply_to_user_id_str": null, 27 | "contributors": null, 28 | "text": "Aggressive Ponytail #freebandnames", 29 | "metadata": { 30 | "iso_language_code": "en", 31 | "result_type": "recent" 32 | }, 33 | "retweet_count": 0, 34 | "in_reply_to_status_id_str": null, 35 | "id": 250075927172759552, 36 | "geo": null, 37 | "retweeted": false, 38 | "in_reply_to_user_id": null, 39 | "place": null, 40 | "user": { 41 | "profile_sidebar_fill_color": "DDEEF6", 42 | "profile_sidebar_border_color": "C0DEED", 43 | "profile_background_tile": false, 44 | "name": "Sean Cummings", 45 | "profile_image_url": "http://a0.twimg.com/profile_images/2359746665/1v6zfgqo8g0d3mk7ii5s_normal.jpeg", 46 | "created_at": "Mon Apr 26 06:01:55 +0000 2010", 47 | "location": "LA, CA", 48 | "follow_request_sent": null, 49 | "profile_link_color": "0084B4", 50 | "is_translator": false, 51 | "id_str": "137238150", 52 | "entities": { 53 | "url": { 54 | "urls": [ 55 | { 56 | "expanded_url": null, 57 | "url": "", 58 | "indices": [ 59 | 0, 60 | 0 61 | ] 62 | } 63 | ] 64 | }, 65 | "description": { 66 | "urls": [ 67 | 68 | ] 69 | } 70 | }, 71 | "default_profile": true, 72 | "contributors_enabled": false, 73 | "favourites_count": 0, 74 | "url": null, 75 | "profile_image_url_https": "https://si0.twimg.com/profile_images/2359746665/1v6zfgqo8g0d3mk7ii5s_normal.jpeg", 76 | "utc_offset": -28800, 77 | "id": 137238150, 78 | "profile_use_background_image": true, 79 | "listed_count": 2, 80 | "profile_text_color": "333333", 81 | "lang": "en", 82 | "followers_count": 70, 83 | "protected": false, 84 | "notifications": null, 85 | "profile_background_image_url_https": "https://si0.twimg.com/images/themes/theme1/bg.png", 86 | "profile_background_color": "C0DEED", 87 | "verified": false, 88 | "geo_enabled": true, 89 | "time_zone": "Pacific Time (US & Canada)", 90 | "description": "Born 330 Live 310", 91 | "default_profile_image": false, 92 | "profile_background_image_url": "http://a0.twimg.com/images/themes/theme1/bg.png", 93 | "statuses_count": 579, 94 | "friends_count": 110, 95 | "following": null, 96 | "show_all_inline_media": false, 97 | "screen_name": "sean_cummings" 98 | }, 99 | "in_reply_to_screen_name": null, 100 | "source": "Twitter for Mac", 101 | "in_reply_to_status_id": null 102 | }, 103 | { 104 | "coordinates": null, 105 | "favorited": false, 106 | "truncated": false, 107 | "created_at": "Fri Sep 21 23:40:54 +0000 2012", 108 | "id_str": "249292149810667520", 109 | "entities": { 110 | "urls": [ 111 | 112 | ], 113 | "hashtags": [ 114 | { 115 | "text": "FreeBandNames", 116 | "indices": [ 117 | 20, 118 | 34 119 | ] 120 | } 121 | ], 122 | "user_mentions": [ 123 | 124 | ] 125 | }, 126 | "in_reply_to_user_id_str": null, 127 | "contributors": null, 128 | "text": "Thee Namaste Nerdz. #FreeBandNames", 129 | "metadata": { 130 | "iso_language_code": "pl", 131 | "result_type": "recent" 132 | }, 133 | "retweet_count": 0, 134 | "in_reply_to_status_id_str": null, 135 | "id": 249292149810667520, 136 | "geo": null, 137 | "retweeted": false, 138 | "in_reply_to_user_id": null, 139 | "place": null, 140 | "user": { 141 | "profile_sidebar_fill_color": "DDFFCC", 142 | "profile_sidebar_border_color": "BDDCAD", 143 | "profile_background_tile": true, 144 | "name": "Chaz Martenstein", 145 | "profile_image_url": "http://a0.twimg.com/profile_images/447958234/Lichtenstein_normal.jpg", 146 | "created_at": "Tue Apr 07 19:05:07 +0000 2009", 147 | "location": "Durham, NC", 148 | "follow_request_sent": null, 149 | "profile_link_color": "0084B4", 150 | "is_translator": false, 151 | "id_str": "29516238", 152 | "entities": { 153 | "url": { 154 | "urls": [ 155 | { 156 | "expanded_url": null, 157 | "url": "http://bullcityrecords.com/wnng/", 158 | "indices": [ 159 | 0, 160 | 32 161 | ] 162 | } 163 | ] 164 | }, 165 | "description": { 166 | "urls": [ 167 | 168 | ] 169 | } 170 | }, 171 | "default_profile": false, 172 | "contributors_enabled": false, 173 | "favourites_count": 8, 174 | "url": "http://bullcityrecords.com/wnng/", 175 | "profile_image_url_https": "https://si0.twimg.com/profile_images/447958234/Lichtenstein_normal.jpg", 176 | "utc_offset": -18000, 177 | "id": 29516238, 178 | "profile_use_background_image": true, 179 | "listed_count": 118, 180 | "profile_text_color": "333333", 181 | "lang": "en", 182 | "followers_count": 2052, 183 | "protected": false, 184 | "notifications": null, 185 | "profile_background_image_url_https": "https://si0.twimg.com/profile_background_images/9423277/background_tile.bmp", 186 | "profile_background_color": "9AE4E8", 187 | "verified": false, 188 | "geo_enabled": false, 189 | "time_zone": "Eastern Time (US & Canada)", 190 | "description": "You will come to Durham, North Carolina. I will sell you some records then, here in Durham, North Carolina. Fun will happen.", 191 | "default_profile_image": false, 192 | "profile_background_image_url": "http://a0.twimg.com/profile_background_images/9423277/background_tile.bmp", 193 | "statuses_count": 7579, 194 | "friends_count": 348, 195 | "following": null, 196 | "show_all_inline_media": true, 197 | "screen_name": "bullcityrecords" 198 | }, 199 | "in_reply_to_screen_name": null, 200 | "source": "web", 201 | "in_reply_to_status_id": null 202 | }, 203 | { 204 | "coordinates": null, 205 | "favorited": false, 206 | "truncated": false, 207 | "created_at": "Fri Sep 21 23:30:20 +0000 2012", 208 | "id_str": "249289491129438208", 209 | "entities": { 210 | "urls": [ 211 | 212 | ], 213 | "hashtags": [ 214 | { 215 | "text": "freebandnames", 216 | "indices": [ 217 | 29, 218 | 43 219 | ] 220 | } 221 | ], 222 | "user_mentions": [ 223 | 224 | ] 225 | }, 226 | "in_reply_to_user_id_str": null, 227 | "contributors": null, 228 | "text": "Mexican Heaven, Mexican Hell #freebandnames", 229 | "metadata": { 230 | "iso_language_code": "en", 231 | "result_type": "recent" 232 | }, 233 | "retweet_count": 0, 234 | "in_reply_to_status_id_str": null, 235 | "id": 249289491129438208, 236 | "geo": null, 237 | "retweeted": false, 238 | "in_reply_to_user_id": null, 239 | "place": null, 240 | "user": { 241 | "profile_sidebar_fill_color": "99CC33", 242 | "profile_sidebar_border_color": "829D5E", 243 | "profile_background_tile": false, 244 | "name": "Thomas John Wakeman", 245 | "profile_image_url": "http://a0.twimg.com/profile_images/2219333930/Froggystyle_normal.png", 246 | "created_at": "Tue Sep 01 21:21:35 +0000 2009", 247 | "location": "Kingston New York", 248 | "follow_request_sent": null, 249 | "profile_link_color": "D02B55", 250 | "is_translator": false, 251 | "id_str": "70789458", 252 | "entities": { 253 | "url": { 254 | "urls": [ 255 | { 256 | "expanded_url": null, 257 | "url": "", 258 | "indices": [ 259 | 0, 260 | 0 261 | ] 262 | } 263 | ] 264 | }, 265 | "description": { 266 | "urls": [ 267 | 268 | ] 269 | } 270 | }, 271 | "default_profile": false, 272 | "contributors_enabled": false, 273 | "favourites_count": 19, 274 | "url": null, 275 | "profile_image_url_https": "https://si0.twimg.com/profile_images/2219333930/Froggystyle_normal.png", 276 | "utc_offset": -18000, 277 | "id": 70789458, 278 | "profile_use_background_image": true, 279 | "listed_count": 1, 280 | "profile_text_color": "3E4415", 281 | "lang": "en", 282 | "followers_count": 63, 283 | "protected": false, 284 | "notifications": null, 285 | "profile_background_image_url_https": "https://si0.twimg.com/images/themes/theme5/bg.gif", 286 | "profile_background_color": "352726", 287 | "verified": false, 288 | "geo_enabled": false, 289 | "time_zone": "Eastern Time (US & Canada)", 290 | "description": "Science Fiction Writer, sort of. Likes Superheroes, Mole People, Alt. Timelines.", 291 | "default_profile_image": false, 292 | "profile_background_image_url": "http://a0.twimg.com/images/themes/theme5/bg.gif", 293 | "statuses_count": 1048, 294 | "friends_count": 63, 295 | "following": null, 296 | "show_all_inline_media": false, 297 | "screen_name": "MonkiesFist" 298 | }, 299 | "in_reply_to_screen_name": null, 300 | "source": "web", 301 | "in_reply_to_status_id": null 302 | }, 303 | { 304 | "coordinates": null, 305 | "favorited": false, 306 | "truncated": false, 307 | "created_at": "Fri Sep 21 22:51:18 +0000 2012", 308 | "id_str": "249279667666817024", 309 | "entities": { 310 | "urls": [ 311 | 312 | ], 313 | "hashtags": [ 314 | { 315 | "text": "freebandnames", 316 | "indices": [ 317 | 20, 318 | 34 319 | ] 320 | } 321 | ], 322 | "user_mentions": [ 323 | 324 | ] 325 | }, 326 | "in_reply_to_user_id_str": null, 327 | "contributors": null, 328 | "text": "The Foolish Mortals #freebandnames", 329 | "metadata": { 330 | "iso_language_code": "en", 331 | "result_type": "recent" 332 | }, 333 | "retweet_count": 0, 334 | "in_reply_to_status_id_str": null, 335 | "id": 249279667666817024, 336 | "geo": null, 337 | "retweeted": false, 338 | "in_reply_to_user_id": null, 339 | "place": null, 340 | "user": { 341 | "profile_sidebar_fill_color": "BFAC83", 342 | "profile_sidebar_border_color": "615A44", 343 | "profile_background_tile": true, 344 | "name": "Marty Elmer", 345 | "profile_image_url": "http://a0.twimg.com/profile_images/1629790393/shrinker_2000_trans_normal.png", 346 | "created_at": "Mon May 04 00:05:00 +0000 2009", 347 | "location": "Wisconsin, USA", 348 | "follow_request_sent": null, 349 | "profile_link_color": "3B2A26", 350 | "is_translator": false, 351 | "id_str": "37539828", 352 | "entities": { 353 | "url": { 354 | "urls": [ 355 | { 356 | "expanded_url": null, 357 | "url": "http://www.omnitarian.me", 358 | "indices": [ 359 | 0, 360 | 24 361 | ] 362 | } 363 | ] 364 | }, 365 | "description": { 366 | "urls": [ 367 | 368 | ] 369 | } 370 | }, 371 | "default_profile": false, 372 | "contributors_enabled": false, 373 | "favourites_count": 647, 374 | "url": "http://www.omnitarian.me", 375 | "profile_image_url_https": "https://si0.twimg.com/profile_images/1629790393/shrinker_2000_trans_normal.png", 376 | "utc_offset": -21600, 377 | "id": 37539828, 378 | "profile_use_background_image": true, 379 | "listed_count": 52, 380 | "profile_text_color": "000000", 381 | "lang": "en", 382 | "followers_count": 608, 383 | "protected": false, 384 | "notifications": null, 385 | "profile_background_image_url_https": "https://si0.twimg.com/profile_background_images/106455659/rect6056-9.png", 386 | "profile_background_color": "EEE3C4", 387 | "verified": false, 388 | "geo_enabled": false, 389 | "time_zone": "Central Time (US & Canada)", 390 | "description": "Cartoonist, Illustrator, and T-Shirt connoisseur", 391 | "default_profile_image": false, 392 | "profile_background_image_url": "http://a0.twimg.com/profile_background_images/106455659/rect6056-9.png", 393 | "statuses_count": 3575, 394 | "friends_count": 249, 395 | "following": null, 396 | "show_all_inline_media": true, 397 | "screen_name": "Omnitarian" 398 | }, 399 | "in_reply_to_screen_name": null, 400 | "source": "Twitter for iPhone", 401 | "in_reply_to_status_id": null 402 | } 403 | ], 404 | "search_metadata": { 405 | "max_id": 250126199840518145, 406 | "since_id": 24012619984051000, 407 | "refresh_url": "?since_id=250126199840518145&q=%23freebandnames&result_type=mixed&include_entities=1", 408 | "next_results": "?max_id=249279667666817023&q=%23freebandnames&count=4&include_entities=1&result_type=mixed", 409 | "count": 4, 410 | "completed_in": 0.035, 411 | "since_id_str": "24012619984051000", 412 | "query": "%23freebandnames", 413 | "max_id_str": "250126199840518145" 414 | } 415 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2014 Fuji, Goro (gfx) 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | XCODEBUILD:=xctool 3 | 4 | default: buildcheck test 5 | 6 | buildcheck: 7 | $(XCODEBUILD) -sdk iphoneos -scheme SwiftFeed build 8 | 9 | test: 10 | $(XCODEBUILD) -sdk iphonesimulator -arch i386 -scheme JsonSerializerTests test 11 | $(XCODEBUILD) -sdk iphonesimulator -arch i386 -scheme SwiftFeed test 12 | 13 | clean: 14 | $(XCODEBUILD) -sdk iphonesimulator -scheme JsonSerializer clean 15 | $(XCODEBUILD) -sdk iphonesimulator -scheme SwiftFeed clean 16 | $(XCODEBUILD) -sdk iphoneos -scheme JsonSerializer clean 17 | $(XCODEBUILD) -sdk iphoneos -scheme SwiftFeed clean 18 | 19 | .PHONY: build test clean default 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "PureJsonSerializer" 5 | ) 6 | -------------------------------------------------------------------------------- /PureJsonSerializer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'PureJsonSerializer' 3 | spec.version = '1.1.3' 4 | spec.license = { :type => 'Apache 2.0'} 5 | spec.homepage = 'https://github.com/gfx/Swift-JsonSerializer' 6 | spec.authors = { 'Goro Fuji' => 'gfuji@cpan.org' } 7 | spec.summary = 'A pure-Swift JSON serializer and deserializer' 8 | spec.source = { :git => 'https://github.com/gfx/Swift-JsonSerializer.git', :tag => "#{spec.version}" } 9 | spec.source_files = 'JsonSerializer/*.{swift}', 'Source/**/*.{swift}' 10 | 11 | spec.ios.deployment_target = "8.0" 12 | spec.osx.deployment_target = "10.9" 13 | spec.watchos.deployment_target = "2.0" 14 | spec.tvos.deployment_target = "9.0" 15 | spec.requires_arc = true 16 | spec.social_media_url = 'https://twitter.com/__gfx__' 17 | end 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PureJsonSerializer [![Build Status](https://travis-ci.org/gfx/Swift-PureJsonSerializer.svg)](https://travis-ci.org/gfx/Swift-PureJsonSerializer) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 2 | 3 | A pure-Swift JSON serializer and deserializer. 4 | 5 | # COCOAPODS 6 | 7 | Lot's of CocoaPods means lots of failed namespaces. The actual pod for this library is called `PureJsonSerializer`. 8 | 9 | ```Ruby 10 | pod 'PureJsonSerializer' 11 | ``` 12 | 13 | # DESERIALIZE 14 | 15 | ```Swift 16 | import PureJsonSerializer 17 | 18 | // parse a JSON data 19 | let data: NSData = ... 20 | 21 | do { 22 | let json = try Json.deserialize(jsonSource) 23 | let value = json["Foo"]?["bar"]?.stringValue ?? "" 24 | print(value) 25 | } catch { 26 | print("Json serialization failed with error: \(error)") 27 | } 28 | ``` 29 | 30 | # BUILD 31 | 32 | ```Swift 33 | // build a JSON structure 34 | let profile: Json = [ 35 | "name": "Swift", 36 | "started": 2014, 37 | "keywords": ["OOP", "functional programming", "static types", "iOS"], 38 | ] 39 | println(profile.description) // packed JSON string 40 | println(profile.debugDescription) // pretty JSON string 41 | ``` 42 | 43 | # SERIALIZE 44 | 45 | ```Swift 46 | let serializedJson = json.serialize(.PrettyPrint) 47 | ``` 48 | 49 | # DESCRIPTION 50 | 51 | Swift-JsonSerializer is a JSON serializer and deserializer which are implemented in **Pure Swift** and adds nothing 52 | to built-in / standard classes in Swift. 53 | 54 | # GENOME 55 | 56 | This library is featured in a complete Json mapping library you can find here. 57 | 58 | # KNOWN ISSUES 59 | 60 | * This library doesn't work with optimization flags (`swiftc -O`) as of Xcode 6.1.1 / Swift version 1.1 (swift-600.0.56.1). 61 | 62 | # SEE ALSO 63 | 64 | * [RFC 7159 The JavaScript Object Notation (JSON) Data Interchange Format](http://tools.ietf.org/html/rfc7159) 65 | 66 | # AUTHOR 67 | 68 | Fuji, Goro (gfx) gfuji@cpan.org 69 | 70 | # LICENSE 71 | 72 | The Apache 2.0 License 73 | -------------------------------------------------------------------------------- /Source/Json.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Json.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/15. 6 | // Copyright (c) 2014 Fuji Goro. All rights reserved. 7 | // 8 | 9 | public enum Json: CustomStringConvertible, CustomDebugStringConvertible, Equatable { 10 | 11 | case nullValue 12 | case booleanValue(Bool) 13 | case numberValue(Double) 14 | case StringValue(String) 15 | case ArrayValue([Json]) 16 | case ObjectValue([String:Json]) 17 | 18 | // MARK: Initialization 19 | 20 | public init(_ value: Bool) { 21 | self = .booleanValue(value) 22 | } 23 | 24 | public init(_ value: Double) { 25 | self = .numberValue(value) 26 | } 27 | 28 | public init(_ value: String) { 29 | self = .StringValue(value) 30 | } 31 | 32 | public init(_ value: [Json]) { 33 | self = .ArrayValue(value) 34 | } 35 | 36 | public init(_ value: [String : Json]) { 37 | self = .ObjectValue(value) 38 | } 39 | 40 | // MARK: From 41 | 42 | public static func from(_ value: Bool) -> Json { 43 | return .booleanValue(value) 44 | } 45 | 46 | public static func from(_ value: Double) -> Json { 47 | return .numberValue(value) 48 | } 49 | 50 | public static func from(_ value: String) -> Json { 51 | return .StringValue(value) 52 | } 53 | 54 | public static func from(_ value: [Json]) -> Json { 55 | return .ArrayValue(value) 56 | } 57 | 58 | public static func from(_ value: [String : Json]) -> Json { 59 | return .ObjectValue(value) 60 | } 61 | } 62 | 63 | // MARK: Serialization 64 | 65 | extension Json { 66 | public static func deserialize(_ source: String) throws -> Json { 67 | return try JsonDeserializer(source.utf8).deserialize() 68 | } 69 | 70 | public static func deserialize(_ source: [UInt8]) throws -> Json { 71 | return try JsonDeserializer(source).deserialize() 72 | } 73 | 74 | public static func deserialize(_ sequence: ByteSequence) throws -> Json where ByteSequence.Iterator.Element == UInt8 { 75 | return try JsonDeserializer(sequence).deserialize() 76 | } 77 | } 78 | 79 | extension Json { 80 | public enum SerializationStyle { 81 | case `default` 82 | case prettyPrint 83 | 84 | fileprivate var serializer: JsonSerializer.Type { 85 | switch self { 86 | case .default: 87 | return DefaultJsonSerializer.self 88 | case .prettyPrint: 89 | return PrettyJsonSerializer.self 90 | } 91 | } 92 | } 93 | 94 | public func serialize(_ style: SerializationStyle = .default) -> String { 95 | return style.serializer.init().serialize(self) 96 | } 97 | } 98 | 99 | // MARK: Convenience 100 | 101 | extension Json { 102 | public var isNull: Bool { 103 | guard case .nullValue = self else { return false } 104 | return true 105 | } 106 | 107 | public var boolValue: Bool? { 108 | if case let .booleanValue(bool) = self { 109 | return bool 110 | } else if let integer = intValue , integer == 1 || integer == 0 { 111 | // When converting from foundation type `[String : AnyObject]`, something that I see as important, 112 | // it's not possible to distinguish between 'bool', 'double', and 'int'. 113 | // Because of this, if we have an integer that is 0 or 1, and a user is requesting a boolean val, 114 | // it's fairly likely this is their desired result. 115 | return integer == 1 116 | } else { 117 | return nil 118 | } 119 | } 120 | 121 | public var floatValue: Float? { 122 | guard let double = doubleValue else { return nil } 123 | return Float(double) 124 | } 125 | 126 | public var doubleValue: Double? { 127 | guard case let .numberValue(double) = self else { 128 | return nil 129 | } 130 | 131 | return double 132 | } 133 | 134 | public var intValue: Int? { 135 | guard case let .numberValue(double) = self , double.truncatingRemainder(dividingBy: 1) == 0 else { 136 | return nil 137 | } 138 | 139 | return Int(double) 140 | } 141 | 142 | public var uintValue: UInt? { 143 | guard let intValue = intValue else { return nil } 144 | return UInt(intValue) 145 | } 146 | 147 | public var stringValue: String? { 148 | guard case let .StringValue(string) = self else { 149 | return nil 150 | } 151 | 152 | return string 153 | } 154 | 155 | public var arrayValue: [Json]? { 156 | guard case let .ArrayValue(array) = self else { return nil } 157 | return array 158 | } 159 | 160 | public var objectValue: [String : Json]? { 161 | guard case let .ObjectValue(object) = self else { return nil } 162 | return object 163 | } 164 | } 165 | 166 | extension Json { 167 | public subscript(index: Int) -> Json? { 168 | assert(index >= 0) 169 | guard let array = arrayValue , index < array.count else { return nil } 170 | return array[index] 171 | } 172 | 173 | public subscript(key: String) -> Json? { 174 | get { 175 | guard let dict = objectValue else { return nil } 176 | return dict[key] 177 | } 178 | set { 179 | guard let object = objectValue else { fatalError("Unable to set string subscript on non-object type!") } 180 | var mutableObject = object 181 | mutableObject[key] = newValue 182 | self = .from(mutableObject) 183 | } 184 | } 185 | } 186 | 187 | extension Json { 188 | public var description: String { 189 | return serialize(DefaultJsonSerializer()) 190 | } 191 | 192 | public var debugDescription: String { 193 | return serialize(PrettyJsonSerializer()) 194 | } 195 | } 196 | 197 | extension Json { 198 | public func serialize(_ serializer: JsonSerializer) -> String { 199 | return serializer.serialize(self) 200 | } 201 | } 202 | 203 | 204 | public func ==(lhs: Json, rhs: Json) -> Bool { 205 | switch lhs { 206 | case .nullValue: 207 | return rhs.isNull 208 | case .booleanValue(let lhsValue): 209 | guard let rhsValue = rhs.boolValue else { return false } 210 | return lhsValue == rhsValue 211 | case .StringValue(let lhsValue): 212 | guard let rhsValue = rhs.stringValue else { return false } 213 | return lhsValue == rhsValue 214 | case .numberValue(let lhsValue): 215 | guard let rhsValue = rhs.doubleValue else { return false } 216 | return lhsValue == rhsValue 217 | case .ArrayValue(let lhsValue): 218 | guard let rhsValue = rhs.arrayValue else { return false } 219 | return lhsValue == rhsValue 220 | case .ObjectValue(let lhsValue): 221 | guard let rhsValue = rhs.objectValue else { return false } 222 | return lhsValue == rhsValue 223 | } 224 | } 225 | 226 | // MARK: Literal Convertibles 227 | 228 | extension Json: ExpressibleByNilLiteral { 229 | public init(nilLiteral value: Void) { 230 | self = .nullValue 231 | } 232 | } 233 | 234 | extension Json: ExpressibleByBooleanLiteral { 235 | public init(booleanLiteral value: BooleanLiteralType) { 236 | self = .booleanValue(value) 237 | } 238 | } 239 | 240 | extension Json: ExpressibleByIntegerLiteral { 241 | public init(integerLiteral value: IntegerLiteralType) { 242 | self = .numberValue(Double(value)) 243 | } 244 | } 245 | 246 | extension Json: ExpressibleByFloatLiteral { 247 | public init(floatLiteral value: FloatLiteralType) { 248 | self = .numberValue(Double(value)) 249 | } 250 | } 251 | 252 | extension Json: ExpressibleByStringLiteral { 253 | public typealias UnicodeScalarLiteralType = String 254 | public typealias ExtendedGraphemeClusterLiteralType = String 255 | 256 | public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) { 257 | self = .StringValue(value) 258 | } 259 | 260 | public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterType) { 261 | self = .StringValue(value) 262 | } 263 | 264 | public init(stringLiteral value: StringLiteralType) { 265 | self = .StringValue(value) 266 | } 267 | } 268 | 269 | extension Json: ExpressibleByArrayLiteral { 270 | public init(arrayLiteral elements: Json...) { 271 | self = .ArrayValue(elements) 272 | } 273 | } 274 | 275 | extension Json: ExpressibleByDictionaryLiteral { 276 | public init(dictionaryLiteral elements: (String, Json)...) { 277 | var object = [String : Json](minimumCapacity: elements.count) 278 | elements.forEach { key, value in 279 | object[key] = value 280 | } 281 | self = .ObjectValue(object) 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /Source/JsonParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JsonSerializer.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/11. 6 | // Copyright (c) 2014 Fuji Goro. All rights reserved. 7 | // License: The MIT License 8 | // 9 | 10 | #if os(Linux) 11 | import Glibc 12 | #else 13 | import Darwin 14 | #endif 15 | 16 | internal final class JsonDeserializer: Parser { 17 | internal typealias ByteSequence = [UInt8] 18 | internal typealias Char = UInt8 19 | 20 | // MARK: Public Readable 21 | 22 | internal fileprivate(set) var lineNumber = 1 23 | internal fileprivate(set) var columnNumber = 1 24 | 25 | // MARK: Source 26 | 27 | fileprivate let source: [UInt8] 28 | 29 | // MARK: State 30 | 31 | fileprivate var cur: Int 32 | fileprivate let end: Int 33 | 34 | // MARK: Accessors 35 | 36 | fileprivate var currentChar: Char { 37 | return source[cur] 38 | } 39 | 40 | fileprivate var nextChar: Char { 41 | return source[(cur + 1)] 42 | } 43 | 44 | fileprivate var currentSymbol: Character { 45 | return Character(UnicodeScalar(currentChar)) 46 | } 47 | 48 | // MARK: Initializer 49 | 50 | internal required convenience init(_ sequence: ByteSequence) where ByteSequence.Iterator.Element == UInt8 { 51 | self.init(Array(sequence)) 52 | } 53 | 54 | internal required init(_ source: ByteSequence) { 55 | self.source = source 56 | self.cur = source.startIndex 57 | self.end = source.endIndex 58 | } 59 | 60 | // MARK: Serialize 61 | 62 | internal func deserialize() throws -> Json { 63 | let json = try deserializeNextValue() 64 | skipWhitespaces() 65 | 66 | guard cur == end else { 67 | throw ExtraTokenError("extra tokens found", self) as! Error 68 | } 69 | 70 | return json 71 | } 72 | 73 | fileprivate func deserializeNextValue() throws -> Json { 74 | skipWhitespaces() 75 | guard cur != end else { 76 | throw InsufficientTokenError("unexpected end of tokens", self) as! Error 77 | } 78 | 79 | switch currentChar { 80 | case Char(ascii: "n"): 81 | return try parseSymbol("null", Json.nullValue) 82 | case Char(ascii: "t"): 83 | return try parseSymbol("true", Json.booleanValue(true)) 84 | case Char(ascii: "f"): 85 | return try parseSymbol("false", Json.booleanValue(false)) 86 | case Char(ascii: "-"), Char(ascii: "0") ... Char(ascii: "9"): 87 | return try parseNumber() 88 | case Char(ascii: "\""): 89 | return try parseString() 90 | case Char(ascii: "{"): 91 | return try parseObject() 92 | case Char(ascii: "["): 93 | return try parseArray() 94 | case let c: 95 | throw UnexpectedTokenError("unexpected token: \(c)", self) as! Error 96 | } 97 | } 98 | 99 | fileprivate func parseSymbol(_ target: StaticString, _ iftrue: @autoclosure () -> Json) throws -> Json { 100 | guard expect(target) else { 101 | throw UnexpectedTokenError("expected \"\(target)\" but \(currentSymbol)", self) as! Error 102 | } 103 | 104 | return iftrue() 105 | } 106 | 107 | fileprivate func parseString() throws -> Json { 108 | assert(currentChar == Char(ascii: "\""), "points a double quote") 109 | advance() 110 | 111 | var buffer = [CChar]() 112 | 113 | while cur != end && currentChar != Char(ascii: "\"") { 114 | switch currentChar { 115 | case Char(ascii: "\\"): 116 | advance() 117 | 118 | guard cur != end else { 119 | throw InvalidStringError("unexpected end of a string literal", self) as! Error 120 | } 121 | 122 | guard let escapedChar = parseEscapedChar() else { 123 | throw InvalidStringError("invalid escape sequence", self) as! Error 124 | } 125 | 126 | String(escapedChar).utf8.forEach { 127 | buffer.append(CChar(bitPattern: $0)) 128 | } 129 | default: 130 | buffer.append(CChar(bitPattern: currentChar)) 131 | } 132 | 133 | advance() 134 | } 135 | 136 | guard expect("\"") else { 137 | throw InvalidStringError("missing double quote", self) as! Error 138 | } 139 | 140 | buffer.append(0) // trailing nul 141 | 142 | return .StringValue(String(cString: buffer)) 143 | } 144 | 145 | fileprivate func parseEscapedChar() -> UnicodeScalar? { 146 | let character = UnicodeScalar(currentChar) 147 | 148 | // 'u' indicates unicode 149 | guard character == "u" else { 150 | return unescapeMapping[character] ?? character 151 | } 152 | 153 | guard let surrogateValue = parseEscapedUnicodeSurrogate() else { return nil } 154 | 155 | // two consecutive \u#### sequences represent 32 bit unicode characters 156 | if nextChar == Char(ascii: "\\") && source[cur.advanced(by: 2)] == Char(ascii: "u") { 157 | advance(); advance() 158 | guard let surrogatePairValue = parseEscapedUnicodeSurrogate() else { return nil } 159 | 160 | return UnicodeScalar(surrogateValue << 16 | surrogatePairValue) 161 | } 162 | 163 | return UnicodeScalar(surrogateValue) 164 | } 165 | fileprivate func parseEscapedUnicodeSurrogate() -> UInt32? { 166 | let requiredLength = 4 167 | 168 | var length = 0 169 | var value: UInt32 = 0 170 | while let d = hexToDigit(nextChar) , length < requiredLength { 171 | advance() 172 | length += 1 173 | 174 | value <<= 4 175 | value |= d 176 | } 177 | 178 | guard length == requiredLength else { return nil } 179 | return value 180 | } 181 | 182 | // number = [ minus ] int [ frac ] [ exp ] 183 | fileprivate func parseNumber() throws -> Json { 184 | let sign = expect("-") ? -1.0 : 1.0 185 | 186 | var integer: Int64 = 0 187 | switch currentChar { 188 | case Char(ascii: "0"): 189 | advance() 190 | case Char(ascii: "1") ... Char(ascii: "9"): 191 | while let value = digitToInt(currentChar) , cur != end { 192 | integer = (integer * 10) + Int64(value) 193 | advance() 194 | } 195 | default: 196 | throw InvalidNumberError("invalid token in number", self) as! Error 197 | } 198 | 199 | var fraction: Double = 0.0 200 | if expect(".") { 201 | var factor = 0.1 202 | var fractionLength = 0 203 | 204 | while let value = digitToInt(currentChar) , cur != end { 205 | fraction += (Double(value) * factor) 206 | factor /= 10 207 | fractionLength += 1 208 | 209 | advance() 210 | } 211 | 212 | guard fractionLength != 0 else { 213 | throw InvalidNumberError("insufficient fraction part in number", self) as! Error 214 | } 215 | } 216 | 217 | var exponent: Int64 = 0 218 | if expect("e") || expect("E") { 219 | var expSign: Int64 = 1 220 | if expect("-") { 221 | expSign = -1 222 | } else if expect("+") { 223 | // do nothing 224 | } 225 | 226 | exponent = 0 227 | 228 | var exponentLength = 0 229 | while let value = digitToInt(currentChar) , cur != end { 230 | exponent = (exponent * 10) + Int64(value) 231 | exponentLength += 1 232 | advance() 233 | } 234 | 235 | guard exponentLength != 0 else { 236 | throw InvalidNumberError("insufficient exponent part in number", self) as! Error 237 | } 238 | 239 | exponent *= expSign 240 | } 241 | 242 | return .numberValue(sign * (Double(integer) + fraction) * pow(10, Double(exponent))) 243 | } 244 | 245 | fileprivate func parseObject() throws -> Json { 246 | return try getObject() 247 | } 248 | 249 | /** 250 | There is a bug in the compiler which makes this function necessary to be called from parseObject 251 | */ 252 | fileprivate func getObject() throws -> Json { 253 | assert(currentChar == Char(ascii: "{"), "points \"{\"") 254 | advance() 255 | skipWhitespaces() 256 | 257 | var object = [String:Json]() 258 | 259 | while cur != end && !expect("}") { 260 | guard case let .StringValue(key) = try deserializeNextValue() else { 261 | throw NonStringKeyError("unexpected value for object key", self) as! Error 262 | } 263 | 264 | skipWhitespaces() 265 | guard expect(":") else { 266 | throw UnexpectedTokenError("missing colon (:)", self) as! Error 267 | } 268 | skipWhitespaces() 269 | 270 | let value = try deserializeNextValue() 271 | object[key] = value 272 | 273 | skipWhitespaces() 274 | 275 | guard !expect("}") else { 276 | break 277 | } 278 | 279 | guard expect(",") else { 280 | throw UnexpectedTokenError("missing comma (,)", self) as! Error 281 | } 282 | } 283 | 284 | return .ObjectValue(object) 285 | } 286 | 287 | fileprivate func parseArray() throws -> Json { 288 | assert(currentChar == Char(ascii: "["), "points \"[\"") 289 | advance() 290 | skipWhitespaces() 291 | 292 | var a = Array() 293 | 294 | LOOP: while cur != end && !expect("]") { 295 | let json = try deserializeNextValue() 296 | skipWhitespaces() 297 | 298 | a.append(json) 299 | 300 | if expect(",") { 301 | continue 302 | } else if expect("]") { 303 | break LOOP 304 | } else { 305 | throw UnexpectedTokenError("missing comma (,) (token: \(currentSymbol))", self) as! Error 306 | } 307 | 308 | } 309 | 310 | return .ArrayValue(a) 311 | } 312 | 313 | fileprivate func expect(_ target: StaticString) -> Bool { 314 | guard cur != end else { return false } 315 | 316 | if !isIdentifier(target.utf8Start.pointee) { 317 | // when single character 318 | if target.utf8Start.pointee == currentChar { 319 | advance() 320 | return true 321 | } else { 322 | return false 323 | } 324 | } 325 | 326 | let start = cur 327 | let l = lineNumber 328 | let c = columnNumber 329 | 330 | var p = target.utf8Start 331 | let endp = p.advanced(by: Int(target.utf8CodeUnitCount)) 332 | while p != endp { 333 | if p.pointee != currentChar { 334 | cur = start // unread 335 | lineNumber = l 336 | columnNumber = c 337 | return false 338 | } 339 | 340 | p += 1 341 | advance() 342 | } 343 | 344 | return true 345 | } 346 | 347 | // only "true", "false", "null" are identifiers 348 | fileprivate func isIdentifier(_ c: Char) -> Bool { 349 | switch c { 350 | case Char(ascii: "a") ... Char(ascii: "z"): 351 | return true 352 | default: 353 | return false 354 | } 355 | } 356 | 357 | fileprivate func advance() { 358 | assert(cur != end, "out of range") 359 | cur += 1 360 | guard cur != end else { return } 361 | 362 | switch currentChar { 363 | case Char(ascii: "\n"): 364 | lineNumber += 1 365 | columnNumber = 1 366 | default: 367 | columnNumber += 1 368 | } 369 | } 370 | 371 | fileprivate func skipWhitespaces() { 372 | while cur != end && currentChar.isWhitespace { 373 | advance() 374 | } 375 | } 376 | } 377 | 378 | extension JsonDeserializer.Char { 379 | var isWhitespace: Bool { 380 | let type = type(of: self) 381 | switch self { 382 | case type.init(ascii: " "), type.init(ascii: "\t"), type.init(ascii: "\r"), type.init(ascii: "\n"): 383 | return true 384 | default: 385 | return false 386 | } 387 | } 388 | } 389 | 390 | extension Collection { 391 | func prefixUntil(_ stopCondition: (Generator.Element) -> Bool) -> Array { 392 | var prefix: [Generator.Element] = [] 393 | for element in self { 394 | guard !stopCondition(element) else { return prefix } 395 | prefix.append(element) 396 | } 397 | return prefix 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /Source/JsonSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JsonSerializer.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/18. 6 | // Copyright (c) 2014年 Fuji Goro. All rights reserved. 7 | // 8 | 9 | public protocol JsonSerializer { 10 | init() 11 | func serialize(_: Json) -> String 12 | } 13 | 14 | internal class DefaultJsonSerializer: JsonSerializer { 15 | 16 | required init() {} 17 | 18 | internal func serialize(_ json: Json) -> String { 19 | switch json { 20 | case .nullValue: 21 | return "null" 22 | case .booleanValue(let b): 23 | return b ? "true" : "false" 24 | case .numberValue(let n): 25 | return serializeNumber(n) 26 | case .StringValue(let s): 27 | return escapeAsJsonString(s) 28 | case .ArrayValue(let a): 29 | return serializeArray(a) 30 | case .ObjectValue(let o): 31 | return serializeObject(o) 32 | } 33 | } 34 | 35 | func serializeNumber(_ n: Double) -> String { 36 | if n == Double(Int64(n)) { 37 | return Int64(n).description 38 | } else { 39 | return n.description 40 | } 41 | } 42 | 43 | func serializeArray(_ array: [Json]) -> String { 44 | var string = "[" 45 | string += array 46 | .map { $0.serialize(self) } 47 | .joined(separator: ",") 48 | return string + "]" 49 | } 50 | 51 | func serializeObject(_ object: [String : Json]) -> String { 52 | var string = "{" 53 | string += object 54 | .map { key, val in 55 | let escapedKey = escapeAsJsonString(key) 56 | let serializedVal = val.serialize(self) 57 | return "\(escapedKey):\(serializedVal)" 58 | } 59 | .joined(separator: ",") 60 | return string + "}" 61 | } 62 | 63 | } 64 | 65 | internal class PrettyJsonSerializer: DefaultJsonSerializer { 66 | fileprivate var indentLevel = 0 67 | 68 | required init() { 69 | super.init() 70 | } 71 | 72 | override internal func serializeArray(_ array: [Json]) -> String { 73 | indentLevel += 1 74 | defer { 75 | indentLevel -= 1 76 | } 77 | 78 | let indentString = indent() 79 | 80 | var string = "[\n" 81 | string += array 82 | .map { val in 83 | let serialized = val.serialize(self) 84 | return indentString + serialized 85 | } 86 | .joined(separator: ",\n") 87 | return string + " ]" 88 | } 89 | 90 | override internal func serializeObject(_ object: [String : Json]) -> String { 91 | indentLevel += 1 92 | defer { 93 | indentLevel -= 1 94 | } 95 | 96 | let indentString = indent() 97 | 98 | var string = "{\n" 99 | string += object 100 | .map { key, val in 101 | let escapedKey = escapeAsJsonString(key) 102 | let serializedValue = val.serialize(self) 103 | let serializedLine = "\(escapedKey): \(serializedValue)" 104 | return indentString + serializedLine 105 | } 106 | .joined(separator: ",\n") 107 | string += " }" 108 | 109 | return string 110 | } 111 | 112 | func indent() -> String { 113 | return Array(1...indentLevel) 114 | .map { _ in " " } 115 | .joined(separator: "") 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Source/ParseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseError.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/15. 6 | // Copyright (c) 2014 Fuji Goro. All rights reserved. 7 | // 8 | 9 | protocol Parser { 10 | var lineNumber: Int { get } 11 | var columnNumber: Int { get } 12 | } 13 | 14 | open class ParseError: Error, CustomStringConvertible { 15 | open let reason: String 16 | let parser: Parser 17 | 18 | open var lineNumber: Int { 19 | return parser.lineNumber 20 | } 21 | open var columnNumber: Int { 22 | return parser.columnNumber 23 | } 24 | 25 | open var description: String { 26 | return "\(Mirror(reflecting: self))[\(lineNumber):\(columnNumber)]: \(reason)" 27 | } 28 | 29 | init(_ reason: String, _ parser: Parser) { 30 | self.reason = reason 31 | self.parser = parser 32 | } 33 | } 34 | 35 | open class UnexpectedTokenError: ParseError { } 36 | 37 | open class InsufficientTokenError: ParseError { } 38 | 39 | open class ExtraTokenError: ParseError { } 40 | 41 | open class NonStringKeyError: ParseError { } 42 | 43 | open class InvalidStringError: ParseError { } 44 | 45 | open class InvalidNumberError: ParseError { } 46 | -------------------------------------------------------------------------------- /Source/StringUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringUtils.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/15. 6 | // Copyright (c) 2014 Fuji Goro. All rights reserved. 7 | // 8 | 9 | let unescapeMapping: [UnicodeScalar: UnicodeScalar] = [ 10 | "t": "\t", 11 | "r": "\r", 12 | "n": "\n", 13 | ] 14 | 15 | let escapeMapping: [Character : String] = [ 16 | "\r": "\\r", 17 | "\n": "\\n", 18 | "\t": "\\t", 19 | "\\": "\\\\", 20 | "\"": "\\\"", 21 | 22 | "\u{2028}": "\\u2028", // LINE SEPARATOR 23 | "\u{2029}": "\\u2029", // PARAGRAPH SEPARATOR 24 | 25 | // XXX: countElements("\r\n") is 1 in Swift 1.0 26 | "\r\n": "\\r\\n", 27 | ] 28 | 29 | let hexMapping: [UnicodeScalar : UInt32] = [ 30 | "0": 0x0, 31 | "1": 0x1, 32 | "2": 0x2, 33 | "3": 0x3, 34 | "4": 0x4, 35 | "5": 0x5, 36 | "6": 0x6, 37 | "7": 0x7, 38 | "8": 0x8, 39 | "9": 0x9, 40 | "a": 0xA, "A": 0xA, 41 | "b": 0xB, "B": 0xB, 42 | "c": 0xC, "C": 0xC, 43 | "d": 0xD, "D": 0xD, 44 | "e": 0xE, "E": 0xE, 45 | "f": 0xF, "F": 0xF, 46 | ] 47 | 48 | let digitMapping: [UnicodeScalar:Int] = [ 49 | "0": 0, 50 | "1": 1, 51 | "2": 2, 52 | "3": 3, 53 | "4": 4, 54 | "5": 5, 55 | "6": 6, 56 | "7": 7, 57 | "8": 8, 58 | "9": 9, 59 | ] 60 | 61 | extension String { 62 | public var escapedJsonString: String { 63 | let mapped = characters 64 | .map { escapeMapping[$0] ?? String($0) } 65 | .joined(separator: "") 66 | return "\"" + mapped + "\"" 67 | } 68 | } 69 | 70 | public func escapeAsJsonString(_ source : String) -> String { 71 | return source.escapedJsonString 72 | } 73 | 74 | func digitToInt(_ b: UInt8) -> Int? { 75 | return digitMapping[UnicodeScalar(b)] 76 | } 77 | 78 | func hexToDigit(_ b: UInt8) -> UInt32? { 79 | return hexMapping[UnicodeScalar(b)] 80 | } 81 | 82 | -------------------------------------------------------------------------------- /SwiftFeed/ApiClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApiClient.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/19. 6 | // Copyright (c) 2014年 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import JsonSerializer 11 | 12 | class ApiClient { 13 | 14 | enum Result { 15 | case Success(Json) 16 | case Error(ErrorType) 17 | } 18 | 19 | func get(url: NSURL, completion: (Result) -> Void) { 20 | let request = NSMutableURLRequest(URL: url); 21 | request.HTTPMethod = "GET" 22 | 23 | let session = NSURLSession.sharedSession() 24 | let task = session.dataTaskWithRequest(request) { (data, request, error) -> Void in 25 | if let err = error { 26 | completion(.Error(err)) 27 | return 28 | } 29 | 30 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { 31 | guard let data = data else { return } 32 | do { 33 | let result = try Json.deserialize(data); 34 | dispatch_async(dispatch_get_main_queue()) { 35 | completion(.Success(result)) 36 | } 37 | } catch { 38 | completion(.Error(error)) 39 | } 40 | } 41 | } 42 | task.resume() 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /SwiftFeed/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftFeed 4 | // 5 | // Created by Fuji Goro on 2014/09/17. 6 | // Copyright (c) 2014年 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SwiftFeed/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /SwiftFeed/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 45 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /SwiftFeed/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // SwiftFeed 4 | // 5 | // Created by Fuji Goro on 2014/09/17. 6 | // Copyright (c) 2014年 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JsonSerializer 11 | 12 | class DetailViewController: UIViewController, UIWebViewDelegate { 13 | 14 | @IBOutlet weak var webView: UIWebView! 15 | 16 | var detailItem: Json! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | configureView() 22 | } 23 | 24 | func configureView() { 25 | title = detailItem["title"]?.stringValue 26 | 27 | guard 28 | let urlString = detailItem["link"]?.stringValue, 29 | let url = NSURL(string: urlString) 30 | else { return } 31 | 32 | let request = NSURLRequest(URL: url) 33 | webView.loadRequest(request) 34 | webView.delegate = self 35 | } 36 | 37 | var indicator: OverlayIndicator? 38 | func webViewDidStartLoad(webView: UIWebView) { 39 | indicator = OverlayIndicator() 40 | } 41 | func webViewDidFinishLoad(webView: UIWebView) { 42 | indicator = nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SwiftFeed/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /SwiftFeed/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | NSAppTransportSecurity 50 | 51 | 52 | NSAllowsArbitraryLoads 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /SwiftFeed/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // SwiftFeed 4 | // 5 | // Created by Fuji Goro on 2014/09/17. 6 | // Copyright (c) 2014年 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JsonSerializer 11 | 12 | class MasterViewController: UITableViewController { 13 | 14 | let url = "http://api.stackexchange.com/2.2/tags/swift/faq?site=stackoverflow.com" 15 | 16 | var entries: [Json] = [] 17 | 18 | let dateFormatter: NSDateFormatter = { 19 | let dateFormatter = NSDateFormatter() 20 | dateFormatter.timeZone = NSTimeZone.localTimeZone() 21 | dateFormatter.dateFormat = "yyyy/M/d HH:mm:dd" 22 | return dateFormatter 23 | }() 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | 29 | let indicator = OverlayIndicator() 30 | 31 | ApiClient().get(NSURL(string: url)!) { result in 32 | indicator 33 | 34 | switch result { 35 | case .Success(let json): 36 | NSLog("quota max: %@", json["quota_max"]?.stringValue ?? "") 37 | self.entries = json["items"]?.arrayValue ?? [] 38 | self.tableView.reloadData() 39 | case .Error(let error): 40 | print("Error: \(error)") 41 | } 42 | } 43 | } 44 | 45 | // MARK: - Segues 46 | 47 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 48 | guard 49 | segue.identifier == "showDetail", 50 | let indexPath = self.tableView.indexPathForSelectedRow, 51 | let detailViewController = segue.destinationViewController as? DetailViewController 52 | where indexPath.row < entries.count 53 | else { return } 54 | 55 | let object = entries[indexPath.row] 56 | detailViewController.detailItem = object 57 | } 58 | 59 | // MARK: - Table View 60 | 61 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 62 | return 1 63 | } 64 | 65 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 66 | return entries.count 67 | } 68 | 69 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 70 | let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) 71 | 72 | let object = entries[indexPath.row] 73 | cell.textLabel?.text = object["title"]?.stringValue 74 | 75 | if let timeInterval = object["last_activity_date"]?.doubleValue { 76 | let date = NSDate(timeIntervalSince1970: timeInterval) 77 | cell.detailTextLabel?.text = dateFormatter.stringFromDate(date) 78 | } 79 | return cell 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /SwiftFeed/OverlayIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlapIndicator.swift 3 | // JsonSerializer 4 | // 5 | // Created by Fuji Goro on 2014/09/21. 6 | // Copyright (c) 2014年 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class OverlayIndicator { 12 | let overlay: UIActivityIndicatorView 13 | 14 | public init() { 15 | let window = UIApplication.sharedApplication().keyWindow! 16 | 17 | overlay = UIActivityIndicatorView(activityIndicatorStyle: .Gray) 18 | overlay.frame = window.frame 19 | overlay.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.1) 20 | 21 | window.addSubview(overlay) 22 | overlay.startAnimating() 23 | } 24 | 25 | deinit { 26 | let v = self.overlay 27 | UIView.animateWithDuration(0.1, 28 | animations: { 29 | v.alpha = 0 30 | }, 31 | completion: { (_) -> Void in 32 | v.removeFromSuperview() 33 | }) 34 | } 35 | } -------------------------------------------------------------------------------- /SwiftFeedTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftFeedTests/SwiftFeedTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftFeedTests.swift 3 | // SwiftFeedTests 4 | // 5 | // Created by Fuji Goro on 2014/09/17. 6 | // Copyright (c) 2014年 Fuji Goro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class SwiftFeedTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /rfc7159.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Internet Engineering Task Force (IETF) T. Bray, Ed. 8 | Request for Comments: 7159 Google, Inc. 9 | Obsoletes: 4627, 7158 March 2014 10 | Category: Standards Track 11 | ISSN: 2070-1721 12 | 13 | 14 | The JavaScript Object Notation (JSON) Data Interchange Format 15 | 16 | Abstract 17 | 18 | JavaScript Object Notation (JSON) is a lightweight, text-based, 19 | language-independent data interchange format. It was derived from 20 | the ECMAScript Programming Language Standard. JSON defines a small 21 | set of formatting rules for the portable representation of structured 22 | data. 23 | 24 | This document removes inconsistencies with other specifications of 25 | JSON, repairs specification errors, and offers experience-based 26 | interoperability guidance. 27 | 28 | Status of This Memo 29 | 30 | This is an Internet Standards Track document. 31 | 32 | This document is a product of the Internet Engineering Task Force 33 | (IETF). It represents the consensus of the IETF community. It has 34 | received public review and has been approved for publication by the 35 | Internet Engineering Steering Group (IESG). Further information on 36 | Internet Standards is available in Section 2 of RFC 5741. 37 | 38 | Information about the current status of this document, any errata, 39 | and how to provide feedback on it may be obtained at 40 | http://www.rfc-editor.org/info/rfc7159. 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | Bray Standards Track [Page 1] 59 | 60 | RFC 7159 JSON March 2014 61 | 62 | 63 | Copyright Notice 64 | 65 | Copyright (c) 2014 IETF Trust and the persons identified as the 66 | document authors. All rights reserved. 67 | 68 | This document is subject to BCP 78 and the IETF Trust's Legal 69 | Provisions Relating to IETF Documents 70 | (http://trustee.ietf.org/license-info) in effect on the date of 71 | publication of this document. Please review these documents 72 | carefully, as they describe your rights and restrictions with respect 73 | to this document. Code Components extracted from this document must 74 | include Simplified BSD License text as described in Section 4.e of 75 | the Trust Legal Provisions and are provided without warranty as 76 | described in the Simplified BSD License. 77 | 78 | This document may contain material from IETF Documents or IETF 79 | Contributions published or made publicly available before November 80 | 10, 2008. The person(s) controlling the copyright in some of this 81 | material may not have granted the IETF Trust the right to allow 82 | modifications of such material outside the IETF Standards Process. 83 | Without obtaining an adequate license from the person(s) controlling 84 | the copyright in such materials, this document may not be modified 85 | outside the IETF Standards Process, and derivative works of it may 86 | not be created outside the IETF Standards Process, except to format 87 | it for publication as an RFC or to translate it into languages other 88 | than English. 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Bray Standards Track [Page 2] 115 | 116 | RFC 7159 JSON March 2014 117 | 118 | 119 | Table of Contents 120 | 121 | 1. Introduction ....................................................3 122 | 1.1. Conventions Used in This Document ..........................4 123 | 1.2. Specifications of JSON .....................................4 124 | 1.3. Introduction to This Revision ..............................4 125 | 2. JSON Grammar ....................................................4 126 | 3. Values ..........................................................5 127 | 4. Objects .........................................................6 128 | 5. Arrays ..........................................................6 129 | 6. Numbers .........................................................6 130 | 7. Strings .........................................................8 131 | 8. String and Character Issues .....................................9 132 | 8.1. Character Encoding .........................................9 133 | 8.2. Unicode Characters .........................................9 134 | 8.3. String Comparison ..........................................9 135 | 9. Parsers ........................................................10 136 | 10. Generators ....................................................10 137 | 11. IANA Considerations ...........................................10 138 | 12. Security Considerations .......................................11 139 | 13. Examples ......................................................12 140 | 14. Contributors ..................................................13 141 | 15. References ....................................................13 142 | 15.1. Normative References .....................................13 143 | 15.2. Informative References ...................................13 144 | Appendix A. Changes from RFC 4627 .................................15 145 | 146 | 1. Introduction 147 | 148 | JavaScript Object Notation (JSON) is a text format for the 149 | serialization of structured data. It is derived from the object 150 | literals of JavaScript, as defined in the ECMAScript Programming 151 | Language Standard, Third Edition [ECMA-262]. 152 | 153 | JSON can represent four primitive types (strings, numbers, booleans, 154 | and null) and two structured types (objects and arrays). 155 | 156 | A string is a sequence of zero or more Unicode characters [UNICODE]. 157 | Note that this citation references the latest version of Unicode 158 | rather than a specific release. It is not expected that future 159 | changes in the UNICODE specification will impact the syntax of JSON. 160 | 161 | An object is an unordered collection of zero or more name/value 162 | pairs, where a name is a string and a value is a string, number, 163 | boolean, null, object, or array. 164 | 165 | An array is an ordered sequence of zero or more values. 166 | 167 | 168 | 169 | 170 | Bray Standards Track [Page 3] 171 | 172 | RFC 7159 JSON March 2014 173 | 174 | 175 | The terms "object" and "array" come from the conventions of 176 | JavaScript. 177 | 178 | JSON's design goals were for it to be minimal, portable, textual, and 179 | a subset of JavaScript. 180 | 181 | 1.1. Conventions Used in This Document 182 | 183 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 184 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 185 | document are to be interpreted as described in [RFC2119]. 186 | 187 | The grammatical rules in this document are to be interpreted as 188 | described in [RFC5234]. 189 | 190 | 1.2. Specifications of JSON 191 | 192 | This document updates [RFC4627], which describes JSON and registers 193 | the media type "application/json". 194 | 195 | A description of JSON in ECMAScript terms appears in Version 5.1 of 196 | the ECMAScript specification [ECMA-262], Section 15.12. JSON is also 197 | described in [ECMA-404]. 198 | 199 | All of the specifications of JSON syntax agree on the syntactic 200 | elements of the language. 201 | 202 | 1.3. Introduction to This Revision 203 | 204 | In the years since the publication of RFC 4627, JSON has found very 205 | wide use. This experience has revealed certain patterns, which, 206 | while allowed by its specifications, have caused interoperability 207 | problems. 208 | 209 | Also, a small number of errata have been reported (see RFC Errata IDs 210 | 607 [Err607] and 3607 [Err3607]). 211 | 212 | This document's goal is to apply the errata, remove inconsistencies 213 | with other specifications of JSON, and highlight practices that can 214 | lead to interoperability problems. 215 | 216 | 2. JSON Grammar 217 | 218 | A JSON text is a sequence of tokens. The set of tokens includes six 219 | structural characters, strings, numbers, and three literal names. 220 | 221 | A JSON text is a serialized value. Note that certain previous 222 | specifications of JSON constrained a JSON text to be an object or an 223 | 224 | 225 | 226 | Bray Standards Track [Page 4] 227 | 228 | RFC 7159 JSON March 2014 229 | 230 | 231 | array. Implementations that generate only objects or arrays where a 232 | JSON text is called for will be interoperable in the sense that all 233 | implementations will accept these as conforming JSON texts. 234 | 235 | JSON-text = ws value ws 236 | 237 | These are the six structural characters: 238 | 239 | begin-array = ws %x5B ws ; [ left square bracket 240 | 241 | begin-object = ws %x7B ws ; { left curly bracket 242 | 243 | end-array = ws %x5D ws ; ] right square bracket 244 | 245 | end-object = ws %x7D ws ; } right curly bracket 246 | 247 | name-separator = ws %x3A ws ; : colon 248 | 249 | value-separator = ws %x2C ws ; , comma 250 | 251 | Insignificant whitespace is allowed before or after any of the six 252 | structural characters. 253 | 254 | ws = *( 255 | %x20 / ; Space 256 | %x09 / ; Horizontal tab 257 | %x0A / ; Line feed or New line 258 | %x0D ) ; Carriage return 259 | 260 | 3. Values 261 | 262 | A JSON value MUST be an object, array, number, or string, or one of 263 | the following three literal names: 264 | 265 | false null true 266 | 267 | The literal names MUST be lowercase. No other literal names are 268 | allowed. 269 | 270 | value = false / null / true / object / array / number / string 271 | 272 | false = %x66.61.6c.73.65 ; false 273 | 274 | null = %x6e.75.6c.6c ; null 275 | 276 | true = %x74.72.75.65 ; true 277 | 278 | 279 | 280 | 281 | 282 | Bray Standards Track [Page 5] 283 | 284 | RFC 7159 JSON March 2014 285 | 286 | 287 | 4. Objects 288 | 289 | An object structure is represented as a pair of curly brackets 290 | surrounding zero or more name/value pairs (or members). A name is a 291 | string. A single colon comes after each name, separating the name 292 | from the value. A single comma separates a value from a following 293 | name. The names within an object SHOULD be unique. 294 | 295 | object = begin-object [ member *( value-separator member ) ] 296 | end-object 297 | 298 | member = string name-separator value 299 | 300 | An object whose names are all unique is interoperable in the sense 301 | that all software implementations receiving that object will agree on 302 | the name-value mappings. When the names within an object are not 303 | unique, the behavior of software that receives such an object is 304 | unpredictable. Many implementations report the last name/value pair 305 | only. Other implementations report an error or fail to parse the 306 | object, and some implementations report all of the name/value pairs, 307 | including duplicates. 308 | 309 | JSON parsing libraries have been observed to differ as to whether or 310 | not they make the ordering of object members visible to calling 311 | software. Implementations whose behavior does not depend on member 312 | ordering will be interoperable in the sense that they will not be 313 | affected by these differences. 314 | 315 | 5. Arrays 316 | 317 | An array structure is represented as square brackets surrounding zero 318 | or more values (or elements). Elements are separated by commas. 319 | 320 | array = begin-array [ value *( value-separator value ) ] end-array 321 | 322 | There is no requirement that the values in an array be of the same 323 | type. 324 | 325 | 6. Numbers 326 | 327 | The representation of numbers is similar to that used in most 328 | programming languages. A number is represented in base 10 using 329 | decimal digits. It contains an integer component that may be 330 | prefixed with an optional minus sign, which may be followed by a 331 | fraction part and/or an exponent part. Leading zeros are not 332 | allowed. 333 | 334 | A fraction part is a decimal point followed by one or more digits. 335 | 336 | 337 | 338 | Bray Standards Track [Page 6] 339 | 340 | RFC 7159 JSON March 2014 341 | 342 | 343 | An exponent part begins with the letter E in upper or lower case, 344 | which may be followed by a plus or minus sign. The E and optional 345 | sign are followed by one or more digits. 346 | 347 | Numeric values that cannot be represented in the grammar below (such 348 | as Infinity and NaN) are not permitted. 349 | 350 | number = [ minus ] int [ frac ] [ exp ] 351 | 352 | decimal-point = %x2E ; . 353 | 354 | digit1-9 = %x31-39 ; 1-9 355 | 356 | e = %x65 / %x45 ; e E 357 | 358 | exp = e [ minus / plus ] 1*DIGIT 359 | 360 | frac = decimal-point 1*DIGIT 361 | 362 | int = zero / ( digit1-9 *DIGIT ) 363 | 364 | minus = %x2D ; - 365 | 366 | plus = %x2B ; + 367 | 368 | zero = %x30 ; 0 369 | 370 | This specification allows implementations to set limits on the range 371 | and precision of numbers accepted. Since software that implements 372 | IEEE 754-2008 binary64 (double precision) numbers [IEEE754] is 373 | generally available and widely used, good interoperability can be 374 | achieved by implementations that expect no more precision or range 375 | than these provide, in the sense that implementations will 376 | approximate JSON numbers within the expected precision. A JSON 377 | number such as 1E400 or 3.141592653589793238462643383279 may indicate 378 | potential interoperability problems, since it suggests that the 379 | software that created it expects receiving software to have greater 380 | capabilities for numeric magnitude and precision than is widely 381 | available. 382 | 383 | Note that when such software is used, numbers that are integers and 384 | are in the range [-(2**53)+1, (2**53)-1] are interoperable in the 385 | sense that implementations will agree exactly on their numeric 386 | values. 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | Bray Standards Track [Page 7] 395 | 396 | RFC 7159 JSON March 2014 397 | 398 | 399 | 7. Strings 400 | 401 | The representation of strings is similar to conventions used in the C 402 | family of programming languages. A string begins and ends with 403 | quotation marks. All Unicode characters may be placed within the 404 | quotation marks, except for the characters that must be escaped: 405 | quotation mark, reverse solidus, and the control characters (U+0000 406 | through U+001F). 407 | 408 | Any character may be escaped. If the character is in the Basic 409 | Multilingual Plane (U+0000 through U+FFFF), then it may be 410 | represented as a six-character sequence: a reverse solidus, followed 411 | by the lowercase letter u, followed by four hexadecimal digits that 412 | encode the character's code point. The hexadecimal letters A though 413 | F can be upper or lower case. So, for example, a string containing 414 | only a single reverse solidus character may be represented as 415 | "\u005C". 416 | 417 | Alternatively, there are two-character sequence escape 418 | representations of some popular characters. So, for example, a 419 | string containing only a single reverse solidus character may be 420 | represented more compactly as "\\". 421 | 422 | To escape an extended character that is not in the Basic Multilingual 423 | Plane, the character is represented as a 12-character sequence, 424 | encoding the UTF-16 surrogate pair. So, for example, a string 425 | containing only the G clef character (U+1D11E) may be represented as 426 | "\uD834\uDD1E". 427 | 428 | string = quotation-mark *char quotation-mark 429 | 430 | char = unescaped / 431 | escape ( 432 | %x22 / ; " quotation mark U+0022 433 | %x5C / ; \ reverse solidus U+005C 434 | %x2F / ; / solidus U+002F 435 | %x62 / ; b backspace U+0008 436 | %x66 / ; f form feed U+000C 437 | %x6E / ; n line feed U+000A 438 | %x72 / ; r carriage return U+000D 439 | %x74 / ; t tab U+0009 440 | %x75 4HEXDIG ) ; uXXXX U+XXXX 441 | 442 | escape = %x5C ; \ 443 | 444 | quotation-mark = %x22 ; " 445 | 446 | unescaped = %x20-21 / %x23-5B / %x5D-10FFFF 447 | 448 | 449 | 450 | Bray Standards Track [Page 8] 451 | 452 | RFC 7159 JSON March 2014 453 | 454 | 455 | 8. String and Character Issues 456 | 457 | 8.1. Character Encoding 458 | 459 | JSON text SHALL be encoded in UTF-8, UTF-16, or UTF-32. The default 460 | encoding is UTF-8, and JSON texts that are encoded in UTF-8 are 461 | interoperable in the sense that they will be read successfully by the 462 | maximum number of implementations; there are many implementations 463 | that cannot successfully read texts in other encodings (such as 464 | UTF-16 and UTF-32). 465 | 466 | Implementations MUST NOT add a byte order mark to the beginning of a 467 | JSON text. In the interests of interoperability, implementations 468 | that parse JSON texts MAY ignore the presence of a byte order mark 469 | rather than treating it as an error. 470 | 471 | 8.2. Unicode Characters 472 | 473 | When all the strings represented in a JSON text are composed entirely 474 | of Unicode characters [UNICODE] (however escaped), then that JSON 475 | text is interoperable in the sense that all software implementations 476 | that parse it will agree on the contents of names and of string 477 | values in objects and arrays. 478 | 479 | However, the ABNF in this specification allows member names and 480 | string values to contain bit sequences that cannot encode Unicode 481 | characters; for example, "\uDEAD" (a single unpaired UTF-16 482 | surrogate). Instances of this have been observed, for example, when 483 | a library truncates a UTF-16 string without checking whether the 484 | truncation split a surrogate pair. The behavior of software that 485 | receives JSON texts containing such values is unpredictable; for 486 | example, implementations might return different values for the length 487 | of a string value or even suffer fatal runtime exceptions. 488 | 489 | 8.3. String Comparison 490 | 491 | Software implementations are typically required to test names of 492 | object members for equality. Implementations that transform the 493 | textual representation into sequences of Unicode code units and then 494 | perform the comparison numerically, code unit by code unit, are 495 | interoperable in the sense that implementations will agree in all 496 | cases on equality or inequality of two strings. For example, 497 | implementations that compare strings with escaped characters 498 | unconverted may incorrectly find that "a\\b" and "a\u005Cb" are not 499 | equal. 500 | 501 | 502 | 503 | 504 | 505 | 506 | Bray Standards Track [Page 9] 507 | 508 | RFC 7159 JSON March 2014 509 | 510 | 511 | 9. Parsers 512 | 513 | A JSON parser transforms a JSON text into another representation. A 514 | JSON parser MUST accept all texts that conform to the JSON grammar. 515 | A JSON parser MAY accept non-JSON forms or extensions. 516 | 517 | An implementation may set limits on the size of texts that it 518 | accepts. An implementation may set limits on the maximum depth of 519 | nesting. An implementation may set limits on the range and precision 520 | of numbers. An implementation may set limits on the length and 521 | character contents of strings. 522 | 523 | 10. Generators 524 | 525 | A JSON generator produces JSON text. The resulting text MUST 526 | strictly conform to the JSON grammar. 527 | 528 | 11. IANA Considerations 529 | 530 | The MIME media type for JSON text is application/json. 531 | 532 | Type name: application 533 | 534 | Subtype name: json 535 | 536 | Required parameters: n/a 537 | 538 | Optional parameters: n/a 539 | 540 | Encoding considerations: binary 541 | 542 | Security considerations: See [RFC7159], Section 12. 543 | 544 | Interoperability considerations: Described in [RFC7159] 545 | 546 | Published specification: [RFC7159] 547 | 548 | Applications that use this media type: 549 | JSON has been used to exchange data between applications written 550 | in all of these programming languages: ActionScript, C, C#, 551 | Clojure, ColdFusion, Common Lisp, E, Erlang, Go, Java, JavaScript, 552 | Lua, Objective CAML, Perl, PHP, Python, Rebol, Ruby, Scala, and 553 | Scheme. 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | Bray Standards Track [Page 10] 563 | 564 | RFC 7159 JSON March 2014 565 | 566 | 567 | Additional information: 568 | Magic number(s): n/a 569 | File extension(s): .json 570 | Macintosh file type code(s): TEXT 571 | 572 | Person & email address to contact for further information: 573 | IESG 574 | 575 | 576 | Intended usage: COMMON 577 | 578 | Restrictions on usage: none 579 | 580 | Author: 581 | Douglas Crockford 582 | 583 | 584 | Change controller: 585 | IESG 586 | 587 | 588 | Note: No "charset" parameter is defined for this registration. 589 | Adding one really has no effect on compliant recipients. 590 | 591 | 12. Security Considerations 592 | 593 | Generally, there are security issues with scripting languages. JSON 594 | is a subset of JavaScript but excludes assignment and invocation. 595 | 596 | Since JSON's syntax is borrowed from JavaScript, it is possible to 597 | use that language's "eval()" function to parse JSON texts. This 598 | generally constitutes an unacceptable security risk, since the text 599 | could contain executable code along with data declarations. The same 600 | consideration applies to the use of eval()-like functions in any 601 | other programming language in which JSON texts conform to that 602 | language's syntax. 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | Bray Standards Track [Page 11] 619 | 620 | RFC 7159 JSON March 2014 621 | 622 | 623 | 13. Examples 624 | 625 | This is a JSON object: 626 | 627 | { 628 | "Image": { 629 | "Width": 800, 630 | "Height": 600, 631 | "Title": "View from 15th Floor", 632 | "Thumbnail": { 633 | "Url": "http://www.example.com/image/481989943", 634 | "Height": 125, 635 | "Width": 100 636 | }, 637 | "Animated" : false, 638 | "IDs": [116, 943, 234, 38793] 639 | } 640 | } 641 | 642 | Its Image member is an object whose Thumbnail member is an object and 643 | whose IDs member is an array of numbers. 644 | 645 | This is a JSON array containing two objects: 646 | 647 | [ 648 | { 649 | "precision": "zip", 650 | "Latitude": 37.7668, 651 | "Longitude": -122.3959, 652 | "Address": "", 653 | "City": "SAN FRANCISCO", 654 | "State": "CA", 655 | "Zip": "94107", 656 | "Country": "US" 657 | }, 658 | { 659 | "precision": "zip", 660 | "Latitude": 37.371991, 661 | "Longitude": -122.026020, 662 | "Address": "", 663 | "City": "SUNNYVALE", 664 | "State": "CA", 665 | "Zip": "94085", 666 | "Country": "US" 667 | } 668 | ] 669 | 670 | 671 | 672 | 673 | 674 | Bray Standards Track [Page 12] 675 | 676 | RFC 7159 JSON March 2014 677 | 678 | 679 | Here are three small JSON texts containing only values: 680 | 681 | "Hello world!" 682 | 683 | 42 684 | 685 | true 686 | 687 | 14. Contributors 688 | 689 | RFC 4627 was written by Douglas Crockford. This document was 690 | constructed by making a relatively small number of changes to that 691 | document; thus, the vast majority of the text here is his. 692 | 693 | 15. References 694 | 695 | 15.1. Normative References 696 | 697 | [IEEE754] IEEE, "IEEE Standard for Floating-Point Arithmetic", IEEE 698 | Standard 754, August 2008, 699 | . 700 | 701 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate 702 | Requirement Levels", BCP 14, RFC 2119, March 1997. 703 | 704 | [RFC5234] Crocker, D. and P. Overell, "Augmented BNF for Syntax 705 | Specifications: ABNF", STD 68, RFC 5234, January 2008. 706 | 707 | [UNICODE] The Unicode Consortium, "The Unicode Standard", 708 | . 709 | 710 | 15.2. Informative References 711 | 712 | [ECMA-262] Ecma International, "ECMAScript Language Specification 713 | Edition 5.1", Standard ECMA-262, June 2011, 714 | . 716 | 717 | [ECMA-404] Ecma International, "The JSON Data Interchange Format", 718 | Standard ECMA-404, October 2013, 719 | . 721 | 722 | [Err3607] RFC Errata, Errata ID 3607, RFC 3607, 723 | . 724 | 725 | 726 | 727 | 728 | 729 | 730 | Bray Standards Track [Page 13] 731 | 732 | RFC 7159 JSON March 2014 733 | 734 | 735 | [Err607] RFC Errata, Errata ID 607, RFC 607, 736 | . 737 | 738 | [RFC4627] Crockford, D., "The application/json Media Type for 739 | JavaScript Object Notation (JSON)", RFC 4627, July 2006. 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | Bray Standards Track [Page 14] 787 | 788 | RFC 7159 JSON March 2014 789 | 790 | 791 | Appendix A. Changes from RFC 4627 792 | 793 | This section lists changes between this document and the text in RFC 794 | 4627. 795 | 796 | o Changed the title and abstract of the document. 797 | 798 | o Changed the reference to [UNICODE] to be not version specific. 799 | 800 | o Added a "Specifications of JSON" section. 801 | 802 | o Added an "Introduction to This Revision" section. 803 | 804 | o Changed the definition of "JSON text" so that it can be any JSON 805 | value, removing the constraint that it be an object or array. 806 | 807 | o Added language about duplicate object member names, member 808 | ordering, and interoperability. 809 | 810 | o Clarified the absence of a requirement that values in an array be 811 | of the same JSON type. 812 | 813 | o Applied erratum #607 from RFC 4627 to correctly align the artwork 814 | for the definition of "object". 815 | 816 | o Changed "as sequences of digits" to "in the grammar below" in the 817 | "Numbers" section, and made base-10-ness explicit. 818 | 819 | o Added language about number interoperability as a function of 820 | IEEE754, and added an IEEE754 reference. 821 | 822 | o Added language about interoperability and Unicode characters and 823 | about string comparisons. To do this, turned the old "Encoding" 824 | section into a "String and Character Issues" section, with three 825 | subsections: "Character Encoding", "Unicode Characters", and 826 | "String Comparison". 827 | 828 | o Changed guidance in the "Parsers" section to point out that 829 | implementations may set limits on the range "and precision" of 830 | numbers. 831 | 832 | o Updated and tidied the "IANA Considerations" section. 833 | 834 | o Made a real "Security Considerations" section and lifted the text 835 | out of the previous "IANA Considerations" section. 836 | 837 | 838 | 839 | 840 | 841 | 842 | Bray Standards Track [Page 15] 843 | 844 | RFC 7159 JSON March 2014 845 | 846 | 847 | o Applied erratum #3607 from RFC 4627 by removing the security 848 | consideration that begins "A JSON text can be safely passed" and 849 | the JavaScript code that went with that consideration. 850 | 851 | o Added a note to the "Security Considerations" section pointing out 852 | the risks of using the "eval()" function in JavaScript or any 853 | other language in which JSON texts conform to that language's 854 | syntax. 855 | 856 | o Added a note to the "IANA Considerations" clarifying the absence 857 | of a "charset" parameter for the application/json media type. 858 | 859 | o Changed "100" to 100 and added a boolean field, both in the first 860 | example. 861 | 862 | o Added examples of JSON texts with simple values, neither objects 863 | nor arrays. 864 | 865 | o Added a "Contributors" section crediting Douglas Crockford. 866 | 867 | o Added a reference to RFC 4627. 868 | 869 | o Moved the ECMAScript reference from Normative to Informative and 870 | updated it to reference ECMAScript 5.1, and added a reference to 871 | ECMA 404. 872 | 873 | Author's Address 874 | 875 | Tim Bray (editor) 876 | Google, Inc. 877 | 878 | EMail: tbray@textuality.com 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | Bray Standards Track [Page 16] 899 | 900 | --------------------------------------------------------------------------------