├── .gitignore ├── LICENSE ├── README.md ├── STJSON.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── STJSON ├── STJSONParser.swift └── main.swift └── STJSONTests ├── Info.plist └── STJSONTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Nicolas Seriot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STJSON 2 | A JSON Parser in Swift 3 compliant with RFC 7159 3 | 4 | STJSON was written along with the article [Parsing JSON is a Minefield](http://seriot.ch/parsing_json.php). 5 | 6 | Basic usage: 7 | 8 | ```Swift 9 | var p = STJSONParser(data: data) 10 | 11 | do { 12 | let o = p.parse() 13 | } catch let e { 14 | print(e) 15 | } 16 | ``` 17 | 18 | Instantiation with options: 19 | 20 | ```Swift 21 | var p = STJSON(data:data, 22 | maxParserDepth:1024, 23 | options:[.useUnicodeReplacementCharacter]) 24 | ``` 25 | -------------------------------------------------------------------------------- /STJSON.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 035DA2F51DB503C400C8895E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035DA2F41DB503C400C8895E /* main.swift */; }; 11 | 035DA2FC1DB503D100C8895E /* STJSONParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035DA2FB1DB503D100C8895E /* STJSONParser.swift */; }; 12 | 035DA3041DB503EC00C8895E /* STJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035DA3031DB503EC00C8895E /* STJSONTests.swift */; }; 13 | 035DA3091DB5044600C8895E /* STJSONParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035DA2FB1DB503D100C8895E /* STJSONParser.swift */; }; 14 | 03B473A61DB7D1DD00345B26 /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 03B473A51DB7D1DD00345B26 /* README.md */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXCopyFilesBuildPhase section */ 18 | 035DA2EF1DB503C400C8895E /* CopyFiles */ = { 19 | isa = PBXCopyFilesBuildPhase; 20 | buildActionMask = 2147483647; 21 | dstPath = /usr/share/man/man1/; 22 | dstSubfolderSpec = 0; 23 | files = ( 24 | ); 25 | runOnlyForDeploymentPostprocessing = 1; 26 | }; 27 | /* End PBXCopyFilesBuildPhase section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 035DA2F11DB503C400C8895E /* STJSON */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = STJSON; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 035DA2F41DB503C400C8895E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 32 | 035DA2FB1DB503D100C8895E /* STJSONParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STJSONParser.swift; sourceTree = ""; }; 33 | 035DA3011DB503EC00C8895E /* STJSONTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = STJSONTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 035DA3031DB503EC00C8895E /* STJSONTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STJSONTests.swift; sourceTree = ""; }; 35 | 035DA3051DB503EC00C8895E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 03B473A51DB7D1DD00345B26 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 035DA2EE1DB503C400C8895E /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | 035DA2FE1DB503EC00C8895E /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 035DA2E81DB503C400C8895E = { 58 | isa = PBXGroup; 59 | children = ( 60 | 03B473A51DB7D1DD00345B26 /* README.md */, 61 | 035DA2F31DB503C400C8895E /* STJSON */, 62 | 035DA3021DB503EC00C8895E /* STJSONTests */, 63 | 035DA2F21DB503C400C8895E /* Products */, 64 | ); 65 | sourceTree = ""; 66 | }; 67 | 035DA2F21DB503C400C8895E /* Products */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 035DA2F11DB503C400C8895E /* STJSON */, 71 | 035DA3011DB503EC00C8895E /* STJSONTests.xctest */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | 035DA2F31DB503C400C8895E /* STJSON */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 035DA2FB1DB503D100C8895E /* STJSONParser.swift */, 80 | 035DA2F41DB503C400C8895E /* main.swift */, 81 | ); 82 | path = STJSON; 83 | sourceTree = ""; 84 | }; 85 | 035DA3021DB503EC00C8895E /* STJSONTests */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 035DA3031DB503EC00C8895E /* STJSONTests.swift */, 89 | 035DA3051DB503EC00C8895E /* Info.plist */, 90 | ); 91 | path = STJSONTests; 92 | sourceTree = ""; 93 | }; 94 | /* End PBXGroup section */ 95 | 96 | /* Begin PBXNativeTarget section */ 97 | 035DA2F01DB503C400C8895E /* STJSON */ = { 98 | isa = PBXNativeTarget; 99 | buildConfigurationList = 035DA2F81DB503C400C8895E /* Build configuration list for PBXNativeTarget "STJSON" */; 100 | buildPhases = ( 101 | 035DA2ED1DB503C400C8895E /* Sources */, 102 | 035DA2EE1DB503C400C8895E /* Frameworks */, 103 | 035DA2EF1DB503C400C8895E /* CopyFiles */, 104 | ); 105 | buildRules = ( 106 | ); 107 | dependencies = ( 108 | ); 109 | name = STJSON; 110 | productName = STJSON; 111 | productReference = 035DA2F11DB503C400C8895E /* STJSON */; 112 | productType = "com.apple.product-type.tool"; 113 | }; 114 | 035DA3001DB503EC00C8895E /* STJSONTests */ = { 115 | isa = PBXNativeTarget; 116 | buildConfigurationList = 035DA3061DB503EC00C8895E /* Build configuration list for PBXNativeTarget "STJSONTests" */; 117 | buildPhases = ( 118 | 035DA2FD1DB503EC00C8895E /* Sources */, 119 | 035DA2FE1DB503EC00C8895E /* Frameworks */, 120 | 035DA2FF1DB503EC00C8895E /* Resources */, 121 | ); 122 | buildRules = ( 123 | ); 124 | dependencies = ( 125 | ); 126 | name = STJSONTests; 127 | productName = STJSONTests; 128 | productReference = 035DA3011DB503EC00C8895E /* STJSONTests.xctest */; 129 | productType = "com.apple.product-type.bundle.unit-test"; 130 | }; 131 | /* End PBXNativeTarget section */ 132 | 133 | /* Begin PBXProject section */ 134 | 035DA2E91DB503C400C8895E /* Project object */ = { 135 | isa = PBXProject; 136 | attributes = { 137 | LastSwiftUpdateCheck = 0810; 138 | LastUpgradeCheck = 0810; 139 | ORGANIZATIONNAME = ch.seriot; 140 | TargetAttributes = { 141 | 035DA2F01DB503C400C8895E = { 142 | CreatedOnToolsVersion = 8.1; 143 | ProvisioningStyle = Automatic; 144 | }; 145 | 035DA3001DB503EC00C8895E = { 146 | CreatedOnToolsVersion = 8.1; 147 | ProvisioningStyle = Automatic; 148 | }; 149 | }; 150 | }; 151 | buildConfigurationList = 035DA2EC1DB503C400C8895E /* Build configuration list for PBXProject "STJSON" */; 152 | compatibilityVersion = "Xcode 3.2"; 153 | developmentRegion = English; 154 | hasScannedForEncodings = 0; 155 | knownRegions = ( 156 | en, 157 | ); 158 | mainGroup = 035DA2E81DB503C400C8895E; 159 | productRefGroup = 035DA2F21DB503C400C8895E /* Products */; 160 | projectDirPath = ""; 161 | projectRoot = ""; 162 | targets = ( 163 | 035DA2F01DB503C400C8895E /* STJSON */, 164 | 035DA3001DB503EC00C8895E /* STJSONTests */, 165 | ); 166 | }; 167 | /* End PBXProject section */ 168 | 169 | /* Begin PBXResourcesBuildPhase section */ 170 | 035DA2FF1DB503EC00C8895E /* Resources */ = { 171 | isa = PBXResourcesBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXResourcesBuildPhase section */ 178 | 179 | /* Begin PBXSourcesBuildPhase section */ 180 | 035DA2ED1DB503C400C8895E /* Sources */ = { 181 | isa = PBXSourcesBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | 03B473A61DB7D1DD00345B26 /* README.md in Sources */, 185 | 035DA2F51DB503C400C8895E /* main.swift in Sources */, 186 | 035DA2FC1DB503D100C8895E /* STJSONParser.swift in Sources */, 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | 035DA2FD1DB503EC00C8895E /* Sources */ = { 191 | isa = PBXSourcesBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | 035DA3041DB503EC00C8895E /* STJSONTests.swift in Sources */, 195 | 035DA3091DB5044600C8895E /* STJSONParser.swift in Sources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXSourcesBuildPhase section */ 200 | 201 | /* Begin XCBuildConfiguration section */ 202 | 035DA2F61DB503C400C8895E /* Debug */ = { 203 | isa = XCBuildConfiguration; 204 | buildSettings = { 205 | ALWAYS_SEARCH_USER_PATHS = NO; 206 | CLANG_ANALYZER_NONNULL = YES; 207 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 208 | CLANG_CXX_LIBRARY = "libc++"; 209 | CLANG_ENABLE_MODULES = YES; 210 | CLANG_ENABLE_OBJC_ARC = YES; 211 | CLANG_WARN_BOOL_CONVERSION = YES; 212 | CLANG_WARN_CONSTANT_CONVERSION = YES; 213 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 214 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 215 | CLANG_WARN_EMPTY_BODY = YES; 216 | CLANG_WARN_ENUM_CONVERSION = YES; 217 | CLANG_WARN_INFINITE_RECURSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 220 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 221 | CLANG_WARN_UNREACHABLE_CODE = YES; 222 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 223 | CODE_SIGN_IDENTITY = "-"; 224 | COPY_PHASE_STRIP = NO; 225 | DEBUG_INFORMATION_FORMAT = dwarf; 226 | ENABLE_STRICT_OBJC_MSGSEND = YES; 227 | ENABLE_TESTABILITY = YES; 228 | GCC_C_LANGUAGE_STANDARD = gnu99; 229 | GCC_DYNAMIC_NO_PIC = NO; 230 | GCC_NO_COMMON_BLOCKS = YES; 231 | GCC_OPTIMIZATION_LEVEL = 0; 232 | GCC_PREPROCESSOR_DEFINITIONS = ( 233 | "DEBUG=1", 234 | "$(inherited)", 235 | ); 236 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 237 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 238 | GCC_WARN_UNDECLARED_SELECTOR = YES; 239 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 240 | GCC_WARN_UNUSED_FUNCTION = YES; 241 | GCC_WARN_UNUSED_VARIABLE = YES; 242 | MACOSX_DEPLOYMENT_TARGET = 10.12; 243 | MTL_ENABLE_DEBUG_INFO = YES; 244 | ONLY_ACTIVE_ARCH = YES; 245 | SDKROOT = macosx; 246 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 247 | }; 248 | name = Debug; 249 | }; 250 | 035DA2F71DB503C400C8895E /* Release */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | CLANG_ANALYZER_NONNULL = YES; 255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 256 | CLANG_CXX_LIBRARY = "libc++"; 257 | CLANG_ENABLE_MODULES = YES; 258 | CLANG_ENABLE_OBJC_ARC = YES; 259 | CLANG_WARN_BOOL_CONVERSION = YES; 260 | CLANG_WARN_CONSTANT_CONVERSION = YES; 261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 262 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INFINITE_RECURSION = YES; 266 | CLANG_WARN_INT_CONVERSION = YES; 267 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 268 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | CODE_SIGN_IDENTITY = "-"; 272 | COPY_PHASE_STRIP = NO; 273 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 274 | ENABLE_NS_ASSERTIONS = NO; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu99; 277 | GCC_NO_COMMON_BLOCKS = YES; 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | MACOSX_DEPLOYMENT_TARGET = 10.12; 285 | MTL_ENABLE_DEBUG_INFO = NO; 286 | SDKROOT = macosx; 287 | }; 288 | name = Release; 289 | }; 290 | 035DA2F91DB503C400C8895E /* Debug */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | PRODUCT_NAME = "$(TARGET_NAME)"; 294 | SWIFT_VERSION = 3.0; 295 | }; 296 | name = Debug; 297 | }; 298 | 035DA2FA1DB503C400C8895E /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | PRODUCT_NAME = "$(TARGET_NAME)"; 302 | SWIFT_VERSION = 3.0; 303 | }; 304 | name = Release; 305 | }; 306 | 035DA3071DB503EC00C8895E /* Debug */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | COMBINE_HIDPI_IMAGES = YES; 310 | INFOPLIST_FILE = STJSONTests/Info.plist; 311 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 312 | PRODUCT_BUNDLE_IDENTIFIER = ch.seriot.STJSONTests; 313 | PRODUCT_NAME = "$(TARGET_NAME)"; 314 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 315 | SWIFT_VERSION = 3.0; 316 | }; 317 | name = Debug; 318 | }; 319 | 035DA3081DB503EC00C8895E /* Release */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | COMBINE_HIDPI_IMAGES = YES; 323 | INFOPLIST_FILE = STJSONTests/Info.plist; 324 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 325 | PRODUCT_BUNDLE_IDENTIFIER = ch.seriot.STJSONTests; 326 | PRODUCT_NAME = "$(TARGET_NAME)"; 327 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 328 | SWIFT_VERSION = 3.0; 329 | }; 330 | name = Release; 331 | }; 332 | /* End XCBuildConfiguration section */ 333 | 334 | /* Begin XCConfigurationList section */ 335 | 035DA2EC1DB503C400C8895E /* Build configuration list for PBXProject "STJSON" */ = { 336 | isa = XCConfigurationList; 337 | buildConfigurations = ( 338 | 035DA2F61DB503C400C8895E /* Debug */, 339 | 035DA2F71DB503C400C8895E /* Release */, 340 | ); 341 | defaultConfigurationIsVisible = 0; 342 | defaultConfigurationName = Release; 343 | }; 344 | 035DA2F81DB503C400C8895E /* Build configuration list for PBXNativeTarget "STJSON" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | 035DA2F91DB503C400C8895E /* Debug */, 348 | 035DA2FA1DB503C400C8895E /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | 035DA3061DB503EC00C8895E /* Build configuration list for PBXNativeTarget "STJSONTests" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | 035DA3071DB503EC00C8895E /* Debug */, 357 | 035DA3081DB503EC00C8895E /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | /* End XCConfigurationList section */ 363 | }; 364 | rootObject = 035DA2E91DB503C400C8895E /* Project object */; 365 | } 366 | -------------------------------------------------------------------------------- /STJSON.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /STJSON/STJSONParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONParser.swift 3 | // JSON 4 | // 5 | // Created by Nicolas Seriot on 12/07/16. 6 | // Copyright © 2016 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct STJSONParser { 12 | 13 | public struct Options: OptionSet { 14 | // https://oleb.net/blog/2016/09/swift-option-sets/ 15 | 16 | public let rawValue: Int 17 | 18 | public init(rawValue:Int) { 19 | self.rawValue = rawValue 20 | } 21 | 22 | static let useUnicodeReplacementCharacter = Options(rawValue: 1) 23 | } 24 | 25 | enum ASCIIByte : UInt8 { 26 | case objectOpen = 0x7B // { 27 | case tab = 0x09 // \t 28 | case newline = 0x0A // \n 29 | case space = 0x20 // " " 30 | case carriageReturn = 0x0D // \r 31 | case doubleQuote = 0x22 // " 32 | case semiColon = 0x3A // : 33 | case objectClose = 0x7D // } 34 | case comma = 0x2C // , 35 | case arrayOpen = 0x5B // [ 36 | case arrayClose = 0x5D // ] 37 | case slash = 0x2F // / 38 | case backSlash = 0x5C // \ 39 | case plus = 0x2B // + 40 | case minus = 0x2D // - 41 | case dot = 0x2E // . 42 | case zero = 0x30 // 0 43 | case one = 0x31 // 1 44 | case nine = 0x39 // 9 45 | case utf8BOMByte1 = 0xEF 46 | case utf8BOMByte2 = 0xBB 47 | case utf8BOMByte3 = 0xBF 48 | case A = 0x41 49 | case E = 0x45 50 | case F = 0x46 51 | case Z = 0x5A 52 | case a = 0x61 53 | case z = 0x7A 54 | case e = 0x65 55 | case f = 0x66 56 | case l = 0x6C 57 | case n = 0x6e 58 | case r = 0x72 59 | case s = 0x73 60 | case t = 0x74 61 | case u = 0x75 62 | } 63 | 64 | let data : Data 65 | var i = 0 66 | let dataLength : Int 67 | var parserDepth = 0 68 | var maxParserDepth : Int 69 | let REPLACEMENT_STRING = "\u{FFFD}" 70 | var options : Options 71 | 72 | init(data:Data, maxParserDepth : Int = 512, options:Options = []) { 73 | self.maxParserDepth = maxParserDepth 74 | self.data = data 75 | self.dataLength = data.count 76 | self.options = options 77 | } 78 | 79 | func printRemainingString() { 80 | let range = Range(uncheckedBounds: (i, data.count)) 81 | let remainingData = data.subdata(in:range) 82 | guard let remainingString = String(data:remainingData, encoding: .utf8) else { 83 | print("-- can't print remaining string") 84 | return 85 | } 86 | print("-- REMAINING STRING FROM \(i): \(remainingString)") 87 | } 88 | 89 | func read() -> UInt8? { 90 | guard i < dataLength else { /* print("can't read at index \(i)"); */ return nil } 91 | return data[i] 92 | } 93 | 94 | mutating func readAndMoveByteEither(_ a:[ASCIIByte]) -> UnicodeScalar? { 95 | 96 | guard let byte = read() else { return nil } 97 | 98 | for b in a { 99 | if byte == b.rawValue { 100 | i = i+1 101 | return UnicodeScalar(byte) 102 | } 103 | } 104 | 105 | return nil 106 | } 107 | 108 | mutating func readAndMove(_ b:ASCIIByte) -> Bool { 109 | 110 | guard let byte = read() 111 | , byte == b.rawValue else { return false } 112 | 113 | i = i+1 114 | 115 | return true 116 | } 117 | 118 | mutating func readAndMoveInByteRange(_ from:ASCIIByte, _ to:ASCIIByte) -> UnicodeScalar? { 119 | 120 | guard let byte = read() 121 | , byte >= from.rawValue && byte <= to.rawValue else { return nil } 122 | 123 | i = i+1 124 | 125 | return UnicodeScalar(byte) 126 | } 127 | 128 | mutating func readAndMoveHexadecimalDigit() -> UInt8? { 129 | 130 | guard let byte = read() 131 | , readAndMoveInByteRange(.zero, .nine) != nil 132 | || readAndMoveInByteRange(.A, .F) != nil 133 | || readAndMoveInByteRange(.a, .f) != nil else { 134 | return nil 135 | } 136 | 137 | return byte 138 | } 139 | 140 | mutating func readAndMoveWhitespace() -> Bool { 141 | return readAndMove(.tab) 142 | || readAndMove(.newline) 143 | || readAndMove(.carriageReturn) 144 | || readAndMove(.space) 145 | } 146 | 147 | mutating func readAndMoveAcceptableByte() -> Bool { 148 | 149 | guard let byte = read() 150 | ,byte != ASCIIByte.doubleQuote.rawValue && byte != ASCIIByte.backSlash.rawValue && byte > 0x1F else { 151 | return false 152 | } 153 | 154 | i = i + 1 155 | 156 | return true 157 | } 158 | 159 | mutating func readArray() throws -> [Any] { 160 | 161 | while readAndMoveWhitespace() {} 162 | 163 | guard readAndMove(.arrayOpen) else { 164 | throw JSONError.expectedArrayOpen(i:i) 165 | } 166 | parserDepth += 1 167 | guard parserDepth <= maxParserDepth else { throw JSONError.maxParserDepthReached(depth:maxParserDepth) } 168 | 169 | var a : [Any] = [] 170 | 171 | while readAndMoveWhitespace() {} 172 | 173 | if readAndMove(.arrayClose) { 174 | parserDepth -= 1 175 | return a 176 | } 177 | 178 | if let v = try readValue() { 179 | a.append(v) 180 | 181 | while readAndMoveWhitespace() {} 182 | 183 | while readAndMove(.comma) { 184 | guard let v = try readValue() else { 185 | throw JSONError.expectedValue(i:i) 186 | } 187 | a.append(v) 188 | 189 | while readAndMoveWhitespace() {} 190 | } 191 | } 192 | 193 | guard readAndMove(.arrayClose) else { 194 | throw JSONError.expectedArrayClose(i:i) 195 | } 196 | parserDepth -= 1 197 | 198 | return a 199 | } 200 | 201 | func unescape(_ byte:UInt8) throws -> UInt8 { 202 | if byte == ASCIIByte.backSlash.rawValue 203 | || byte == ASCIIByte.doubleQuote.rawValue 204 | || byte == ASCIIByte.slash.rawValue { 205 | return byte 206 | } 207 | 208 | if byte == UInt8(ascii: "b") { return 0x08 } // \b backspace 209 | if byte == UInt8(ascii: "f") { return 0x0C } // \f form feed 210 | if byte == UInt8(ascii: "n") { return 0x0A } // \n line feed 211 | if byte == UInt8(ascii: "r") { return 0x0D } // \r carriage return 212 | if byte == UInt8(ascii: "t") { return 0x09 } // \t tabulation 213 | 214 | throw JSONError.expectedCharacterToBeUnescaped(i:i) 215 | } 216 | 217 | /* 218 | func isValidCodepoint(codepoint cp:Int) -> Bool { 219 | // http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf 220 | // Table 3-7. Well-Formed UTF-8 Byte Sequences 221 | return (cp >= 0 && cp <= 0xD7FF) || (cp >= 0xE000 && cp <= 0x10FFFF) 222 | } 223 | */ 224 | 225 | func isHighSurrogate(codepoint cp:Int) -> Bool { 226 | return cp >= 0xD800 && cp <= 0xDBFF 227 | } 228 | 229 | func isLowSurrogate(codepoint cp:Int) -> Bool { 230 | return cp >= 0xDC00 && cp <= 0xDFFF 231 | } 232 | 233 | mutating func readAndMoveUEscapedCodepoint() -> (String, Int)? { 234 | guard readAndMove(.u) else { return nil } 235 | 236 | guard let b1 = readAndMoveHexadecimalDigit() else { /*print("** expected hexadecimal digit");*/ return nil } 237 | guard let b2 = readAndMoveHexadecimalDigit() else { /*print("** expected hexadecimal digit");*/ return nil } 238 | guard let b3 = readAndMoveHexadecimalDigit() else { /*print("** expected hexadecimal digit");*/ return nil } 239 | guard let b4 = readAndMoveHexadecimalDigit() else { /*print("** expected hexadecimal digit");*/ return nil } 240 | 241 | guard let s = String(bytes: [b1, b2, b3, b4], encoding: .utf8) else { 242 | fatalError("** cannot convert \([b1, b2, b3, b4]) into string") 243 | } 244 | 245 | return (s, strtol(s, nil, 16)) 246 | } 247 | 248 | mutating func readAndMoveEscapedCodepointOrSurrogates() throws -> String? { 249 | 250 | let useReplacementString = self.options.contains(.useUnicodeReplacementCharacter) 251 | 252 | /* 253 | http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf 254 | -> 6.1.4 The String Type 255 | // A code unit in the range 0 to 0xD7FF or in the range 0xE000 to 0xFFFF is interpreted as a code point with the same value. 256 | * A sequence of two code units, where the first code unit c1 is in the range 0xD800 to 0xDBFF and the second code unit c2 257 | is in the range 0xDC00 to 0xDFFF, is a surrogate pair and is interpreted as a code point with the value (c1 ‑ 0xD800) × 258 | 0x400 + (c2 ‑ 0xDC00) + 0x10000. (See 10.1.2) 259 | * A code unit that is in the range 0xD800 to 0xDFFF, but is not part of a surrogate pair, is interpreted as a code point with 260 | the same value. 261 | */ 262 | 263 | guard let (s1, c1) = readAndMoveUEscapedCodepoint() else { 264 | return nil 265 | } 266 | 267 | // valid codepoint -> return 268 | if let u = UnicodeScalar(c1) { 269 | return "\(u)" 270 | } 271 | 272 | // invalid codepoint must be high surrogate, or error 273 | guard isHighSurrogate(codepoint: c1) else { 274 | return useReplacementString ? REPLACEMENT_STRING : "\\u\(s1)" 275 | } 276 | 277 | // look for second surrogate escape character, or error 278 | guard readAndMove(.backSlash) else { 279 | return useReplacementString ? REPLACEMENT_STRING : "\\u\(s1)" 280 | } 281 | 282 | // read second codepoint 283 | guard let (s2, c2) = readAndMoveUEscapedCodepoint() else { 284 | // or escaped sequence 285 | let x = try readAndMoveEscapedSequence() 286 | return useReplacementString ? REPLACEMENT_STRING + "\(x)" : "\\u\(s1)\(x)" 287 | } 288 | 289 | // second codepoint must be low surrogate, or error 290 | guard isLowSurrogate(codepoint: c2) else { 291 | return useReplacementString ? REPLACEMENT_STRING + REPLACEMENT_STRING : "\\u\(s1)\\u\(s2)" 292 | } 293 | 294 | let finalCodepoint = 0x400 + 0x2400 + (c1 - 0xD800) + (c2 - 0xDC00) + 0x10000 295 | guard let u = UnicodeScalar(finalCodepoint) else { 296 | return useReplacementString ? REPLACEMENT_STRING + REPLACEMENT_STRING : "\\u\(s1)\\u\(s2)" 297 | } 298 | 299 | return "\(u)" 300 | } 301 | 302 | mutating func readAndMoveEscapedSequence() throws -> String { 303 | 304 | if let s = try readAndMoveEscapedCodepointOrSurrogates() { 305 | return s 306 | } 307 | 308 | guard let b = read() else { 309 | throw JSONError.expectedCharacter(i:i) 310 | } 311 | 312 | i = i + 1 313 | 314 | let unescaped = try unescape(b) 315 | 316 | let data = Data(bytes: [unescaped]) 317 | 318 | guard let s = String(data:data, encoding:.utf8) else { 319 | fatalError() 320 | } 321 | 322 | return s 323 | } 324 | 325 | mutating func readString() throws -> String { 326 | 327 | if readAndMove(.doubleQuote) == false { 328 | throw JSONError.expectedDoubleQuote(i:i) 329 | } 330 | 331 | var s : String = "" 332 | 333 | while readAndMove(.doubleQuote) == false { 334 | 335 | guard read() != nil else { 336 | throw JSONError.expectedCharacter(i:i) 337 | } 338 | 339 | if readAndMove(.backSlash) { 340 | 341 | let x = try readAndMoveEscapedSequence() 342 | s.append(x) 343 | 344 | } else if readAndMoveAcceptableByte() { 345 | 346 | let start = i-1 347 | 348 | while readAndMoveAcceptableByte() { 349 | () 350 | } 351 | 352 | let range = Range(uncheckedBounds: (start, i)) 353 | 354 | let subdata = data.subdata(in: range) 355 | 356 | guard let substring = String(data:subdata, encoding:.utf8) else { 357 | throw JSONError.cannotBuildStringFromData(i:i) 358 | } 359 | 360 | s.append(substring) 361 | } else { 362 | throw JSONError.expectedAcceptableCodepointOrEscapedSequence(i:i) 363 | } 364 | } 365 | 366 | return s 367 | } 368 | 369 | mutating func readObject() throws -> [String:Any] { 370 | 371 | guard readAndMove(.objectOpen) else { 372 | throw JSONError.expectedObjectOpen(i:i) 373 | } 374 | parserDepth += 1 375 | guard parserDepth <= maxParserDepth else { throw JSONError.maxParserDepthReached(depth:maxParserDepth) } 376 | 377 | var d : [String:Any] = [:] 378 | 379 | while readAndMoveWhitespace() {} 380 | 381 | if readAndMove(.objectClose) { 382 | parserDepth -= 1 383 | return d 384 | } 385 | 386 | repeat { 387 | 388 | while readAndMoveWhitespace() {} 389 | 390 | let s = try readString() 391 | 392 | while readAndMoveWhitespace() {} 393 | 394 | guard readAndMove(.semiColon) else { 395 | throw JSONError.expectedSemicolon(i:i) 396 | } 397 | 398 | guard let v = try readValue() else { 399 | throw JSONError.expectedValue(i:i) 400 | } 401 | 402 | d[s] = v 403 | 404 | while readAndMoveWhitespace() {} 405 | 406 | } while readAndMove(.comma) 407 | 408 | guard readAndMove(.objectClose) else { 409 | throw JSONError.expectedObjectClose(i:i) 410 | } 411 | parserDepth -= 1 412 | 413 | return d 414 | } 415 | 416 | func throwIfStartsWithUTF16BOM() throws { 417 | let BOM_LENGTH = 2 418 | 419 | guard dataLength >= BOM_LENGTH else { return } 420 | 421 | let UTF_16_BE = Data(bytes: [0xFE, 0xFF]) 422 | let UTF_16_LE = Data(bytes: [0xFF, 0xFE]) 423 | 424 | try data.withUnsafeBytes { (p:UnsafePointer) -> Void in 425 | let BOM = Data(buffer: UnsafeBufferPointer(start: p, count: BOM_LENGTH)) 426 | 427 | if BOM == UTF_16_BE { throw JSONError.foundBOMForUnsupportdEncodingUTF16BE } 428 | if BOM == UTF_16_LE { throw JSONError.foundBOMForUnsupportdEncodingUTF16LE } 429 | } 430 | } 431 | 432 | func throwIfStartsWithUTF32BOM() throws { 433 | let BOM_LENGTH = 4 434 | 435 | guard dataLength >= BOM_LENGTH else { return } 436 | 437 | let UTF_32_BE = Data(bytes: [0x00, 0x00, 0xFE, 0xFF]) 438 | let UTF_32_LE = Data(bytes: [0xFF, 0xFE, 0x00, 0x00]) 439 | 440 | try data.withUnsafeBytes { (p:UnsafePointer) -> Void in 441 | let BOM = Data(buffer: UnsafeBufferPointer(start: p, count: BOM_LENGTH)) 442 | 443 | if BOM == UTF_32_BE { throw JSONError.foundBOMForUnsupportdEncodingUTF32BE } 444 | if BOM == UTF_32_LE { throw JSONError.foundBOMForUnsupportdEncodingUTF32LE } 445 | } 446 | } 447 | 448 | mutating func parse() throws -> Any? { 449 | 450 | // throw if a UTF-16 or UTF-32 BOM is found 451 | // this is the only place where STJSON does not follow RFC 7159 452 | // which supports UTF-16 anf UTF-32, optionnaly preceeded by a BOM 453 | // STJSON only supports UTF-8 454 | try throwIfStartsWithUTF16BOM() 455 | try throwIfStartsWithUTF32BOM() 456 | 457 | // skip UTF-8 BOM if present (EF BB BF) 458 | if readAndMove(.utf8BOMByte1) { 459 | guard readAndMove(.utf8BOMByte2) else { return nil} 460 | guard readAndMove(.utf8BOMByte3) else { return nil} 461 | } 462 | 463 | let o = try readValue() 464 | 465 | while readAndMoveWhitespace() {} 466 | guard read() == nil else { 467 | throw JSONError.extraData(i:i) 468 | } 469 | return o 470 | } 471 | 472 | func myDouble(_ s:String) -> Double? { 473 | 474 | return s.withCString() { startPointer -> Double in 475 | 476 | var doubleEndPointer : UnsafeMutablePointer? = nil 477 | 478 | return strtod(startPointer, &doubleEndPointer) 479 | } 480 | } 481 | 482 | // func myInt(_ s:String) -> Int? { 483 | // 484 | // return s.withCString() { startPointer -> Int in 485 | // 486 | // var intEndPointer : UnsafeMutablePointer? = nil 487 | // 488 | // return strtol(startPointer, &intEndPointer, 10) 489 | // } 490 | // } 491 | 492 | mutating func readValue() throws -> Any? { 493 | 494 | while readAndMoveWhitespace() {} 495 | 496 | guard let byte = read() else { 497 | throw JSONError.cannotReadByte(i:i) 498 | } 499 | 500 | switch(byte) { 501 | case ASCIIByte.arrayOpen.rawValue: 502 | return try readArray() 503 | case ASCIIByte.doubleQuote.rawValue: 504 | return try readString() 505 | case ASCIIByte.objectOpen.rawValue: 506 | return try readObject() 507 | case ASCIIByte.t.rawValue: 508 | let start_pos = i 509 | if readAndMove(.t) 510 | && readAndMove(.r) 511 | && readAndMove(.u) 512 | && readAndMove(.e) { 513 | return true 514 | } 515 | throw JSONError.foundGarbage(i: start_pos) 516 | case ASCIIByte.f.rawValue: 517 | let start_pos = i 518 | if readAndMove(.f) 519 | && readAndMove(.a) 520 | && readAndMove(.l) 521 | && readAndMove(.s) 522 | && readAndMove(.e) { 523 | return false 524 | } 525 | throw JSONError.foundGarbage(i: start_pos) 526 | case ASCIIByte.n.rawValue: 527 | let start_pos = i 528 | if readAndMove(.n) 529 | && readAndMove(.u) 530 | && readAndMove(.l) 531 | && readAndMove(.l) { 532 | return NSNull() 533 | } 534 | throw JSONError.foundGarbage(i: start_pos) 535 | default: 536 | guard let (isADouble, n) = try readNumber() else { 537 | throw JSONError.expectedNumber(i: i) 538 | } 539 | 540 | guard let d = myDouble(n) else { 541 | print("n is not a double") 542 | return nil 543 | } 544 | 545 | if d.isInfinite { 546 | print("d is infinite") 547 | return nil 548 | } 549 | 550 | if d.isNaN { 551 | print("d in NaN") 552 | return nil 553 | } 554 | 555 | if isADouble { 556 | return d 557 | } 558 | 559 | if d > Double(Int.max) { 560 | return n // Int cannot represent n, so we return the string 561 | } 562 | 563 | if d < Double(Int.min) { 564 | return n // Int cannot represent n, so we return the string 565 | } 566 | 567 | return Int(d) 568 | } 569 | } 570 | 571 | enum JSONError: Error { 572 | case cannotReadByte(i:Int) 573 | case expectedDigit(i:Int) 574 | case expectedCharacterToBeUnescaped(i:Int) 575 | case expectedValue(i:Int) 576 | case expectedString(i:Int) 577 | case expectedNumber(i:Int) 578 | case expectedSemicolon(i:Int) 579 | case expectedObjectOpen(i:Int) 580 | case expectedObjectContent(i:Int) 581 | case expectedObjectClose(i:Int) 582 | case expectedArrayOpen(i:Int) 583 | case expectedArrayClose(i:Int) 584 | case expectedDoubleQuote(i:Int) 585 | case expectedCharacter(i:Int) 586 | case cannotBuildStringFromData(i:Int) 587 | case expectedAcceptableCodepointOrEscapedSequence(i:Int) 588 | case cannotReadInt(i:Int) 589 | case extraData(i:Int) 590 | case maxParserDepthReached(depth:Int) 591 | case foundGarbage(i:Int) 592 | case expectedHighSurrogate(i:Int) 593 | case expectedLowSurrogate(i:Int) 594 | case foundSurrogatesWithInvalidCodepoint(i:Int) 595 | case foundBOMForUnsupportdEncodingUTF16BE 596 | case foundBOMForUnsupportdEncodingUTF16LE 597 | case foundBOMForUnsupportdEncodingUTF32BE 598 | case foundBOMForUnsupportdEncodingUTF32LE 599 | } 600 | 601 | mutating func readNumber() throws -> (Bool, String)? { 602 | 603 | var isADouble = false 604 | 605 | var s = "" 606 | 607 | if readAndMove(.minus) { 608 | s.append("-") 609 | } 610 | 611 | guard let b = read() 612 | , b >= ASCIIByte.zero.rawValue && b <= ASCIIByte.nine.rawValue else { return nil } 613 | 614 | if readAndMove(.zero) { 615 | s.append("0") 616 | } else if let d = readAndMoveInByteRange(.one, .nine) { 617 | s.append(String(d)) 618 | while let d = readAndMoveInByteRange(.zero, .nine) { 619 | s.append(String(d)) 620 | } 621 | } 622 | 623 | if readAndMove(.dot) { 624 | isADouble = true 625 | 626 | s.append(".") 627 | 628 | var digitFound = false 629 | 630 | while let d = readAndMoveInByteRange(.zero, .nine) { 631 | digitFound = true 632 | s.append(String(d)) 633 | } 634 | 635 | guard digitFound else { 636 | throw JSONError.expectedDigit(i: i) 637 | } 638 | } 639 | 640 | if let x = readAndMoveByteEither([.e, .E]) { 641 | isADouble = true 642 | 643 | s.append(String(x)) 644 | 645 | if let x = readAndMoveByteEither([.plus, .minus]) { 646 | s.append(String(x)) 647 | } 648 | 649 | var digitFound = false 650 | 651 | while let d = readAndMoveInByteRange(.zero, .nine) { 652 | digitFound = true 653 | s.append(String(d)) 654 | } 655 | 656 | guard digitFound else { 657 | throw JSONError.expectedDigit(i: i) 658 | } 659 | } 660 | 661 | return (isADouble, s) 662 | } 663 | } 664 | -------------------------------------------------------------------------------- /STJSON/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // STJSON 4 | // 5 | // Created by Nicolas Seriot on 17.10.16. 6 | // Copyright © 2016 ch.seriot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func main() { 12 | 13 | guard ProcessInfo.processInfo.arguments.count == 2 else { 14 | let url = URL(fileURLWithPath: ProcessInfo.processInfo.arguments[0]) 15 | let programName = url.lastPathComponent 16 | print("Usage: ./\(programName) file.json") 17 | exit(1) 18 | } 19 | 20 | let path = ProcessInfo.processInfo.arguments[1] 21 | let url = NSURL.fileURL(withPath:path) 22 | 23 | do { 24 | let data = try Data(contentsOf:url) 25 | 26 | //var p = JSONParser(data: data, maxParserDepth:10, options:[.useUnicodeReplacementCharacter]) 27 | var p = STJSONParser(data: data) 28 | do { 29 | let o = try p.parse() 30 | 31 | guard o != nil else { 32 | exit(1) 33 | } 34 | 35 | exit(0) 36 | } catch let e { 37 | print(e) 38 | exit(1) 39 | } 40 | } catch let e { 41 | print("*** CANNOT READ DATA AT \(url)") 42 | print(e) 43 | exit(1) 44 | } 45 | } 46 | 47 | main() 48 | -------------------------------------------------------------------------------- /STJSONTests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /STJSONTests/STJSONTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // STJSONTests.swift 3 | // STJSONTests 4 | // 5 | // Created by Nicolas Seriot on 17.10.16. 6 | // Copyright © 2016 ch.seriot. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class STJSONTests: XCTestCase { 12 | 13 | func parseData(data:Data) -> Any? { 14 | 15 | var p = STJSONParser(data: data) 16 | do { 17 | //try data.write(to: URL(fileURLWithPath: "/Users/nst/Desktop/data.json")) 18 | return try p.parse() 19 | } catch let e { 20 | print(e) 21 | return nil 22 | } 23 | } 24 | 25 | func parseString(_ s:String) -> Any? { 26 | guard let data = s.data(using: String.Encoding.utf8) else { XCTFail(); return nil } 27 | 28 | return parseData(data:data) 29 | } 30 | 31 | func testCases() { 32 | let dir = "/Users/nst/Projects/dropbox/JSON/test_cases/" 33 | 34 | guard let enumerator = FileManager.default.enumerator(atPath: dir) else { 35 | // please checkout tests from https://github.com/nst/JSONTestSuite 36 | // and setup the path in this method 37 | XCTFail() 38 | return 39 | } 40 | 41 | for filename in enumerator where (filename as! NSString).pathExtension == "json" { 42 | 43 | guard let s = filename as? String else { continue } 44 | 45 | let url = URL(fileURLWithPath: dir+s) 46 | 47 | print("*** testing \(s)") 48 | 49 | do { 50 | let data = try Data(contentsOf: url) 51 | 52 | let o = parseData(data:data) 53 | 54 | if s.hasPrefix("n_") { 55 | if o != nil { 56 | print("STJSON\tSHOULD_HAVE_FAILED\t\(filename)") 57 | } 58 | XCTAssertNil(o) 59 | } else if s.hasPrefix("y_") { 60 | if o == nil { 61 | print("STJSON\tSHOULD_HAVE_PASSED\t\(filename)") 62 | } 63 | XCTAssertNotNil(o) 64 | } else if o == nil { 65 | print("STJSON\tIMPLEMENTATION_FAIL\t\(filename)") 66 | } else { 67 | print("STJSON\tIMPLEMENTATION_PASS\t\(filename)") 68 | } 69 | } catch let e { 70 | print(e) 71 | XCTFail() 72 | } 73 | } 74 | } 75 | 76 | func testReadDouble() { 77 | XCTAssertEqual(2.0, STJSONParser(data:Data()).myDouble("2.0")) 78 | } 79 | 80 | func testReadDoubleWithExponent() { 81 | XCTAssertEqual(100.0, STJSONParser(data:Data()).myDouble("10.0e1")) 82 | } 83 | 84 | func testReadExponent() { 85 | 86 | let i = Int(Double("20e2")!) 87 | print("***", i) 88 | 89 | let o = parseString("[20e2]") as! [Double] 90 | XCTAssertEqual([2000], o) 91 | } 92 | } 93 | --------------------------------------------------------------------------------