├── .gitignore ├── .swift-sample ├── .travis.yml ├── JSON.podspec ├── JSON.xcodeproj ├── Configs │ └── Project.xcconfig ├── JSONTests_Info.plist ├── JSON_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── JSON.xcscheme │ └── xcschememanagement.plist ├── LICENCE.md ├── Package.swift ├── README.md ├── Sources └── JSON │ ├── Constants.swift │ ├── Expressible.swift │ ├── JSON.swift │ ├── JSONAccessors.swift │ ├── JSONConvertible.swift │ ├── JSONError.swift │ ├── JSONInitializable.swift │ ├── JSONIterator.swift │ ├── JSONOptionalExtensions.swift │ ├── JSONParser.swift │ ├── JSONRepresentable.swift │ └── JSONSerializer.swift └── Tests ├── JSONTests ├── AccessorTests.swift ├── FixtureSupport.swift ├── Fixtures │ ├── insane.json │ ├── large.json │ └── large_min.json ├── ModelMapTests.swift ├── ParserPerformance.swift ├── ParserTests.swift ├── PublicAPITests.swift ├── SerializerPerformance.swift ├── SerializerTests.swift └── UserModel.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | ## Test Resources (no need for consumers to download huge test dependency files) 5 | ## Would be nice to have someway to include these anyway 6 | #Tests/JSONTests/Fixtures/* 7 | 8 | # Created by https://www.gitignore.io/api/xcode 9 | 10 | ### Xcode ### 11 | # Xcode 12 | # 13 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 14 | 15 | ## Build generated 16 | .build/ 17 | build/ 18 | DerivedData/ 19 | 20 | ## Various settings 21 | *.pbxuser 22 | !default.pbxuser 23 | *.mode1v3 24 | !default.mode1v3 25 | *.mode2v3 26 | !default.mode2v3 27 | *.perspectivev3 28 | !default.perspectivev3 29 | xcuserdata/ 30 | 31 | ## Other 32 | *.moved-aside 33 | *.xccheckout 34 | *.xcscmblueprint 35 | 36 | ## SPM 37 | /Packages 38 | 39 | ## Carthage 40 | /Carthage 41 | -------------------------------------------------------------------------------- /.swift-sample: -------------------------------------------------------------------------------- 1 | { 2 | "type": "sandbox", 3 | "title": "JSON", 4 | "description": "Experiment with JSON", 5 | "swiftversion": "swift-3.0-RELEASE-ubuntu14.04", 6 | "giturl": "https://github.com/vdka/JSON-Sample.git", 7 | "gittag": "0.2.0" 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8 3 | script: 4 | - set -o pipefail 5 | - xcodebuild -project JSON.xcodeproj -scheme "JSON" test | xcpretty -c 6 | -------------------------------------------------------------------------------- /JSON.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "JSON" 4 | s.version = "0.16.3" 5 | s.summary = "The fastest type-safe native Swift JSON parser available." 6 | 7 | s.description = <<-DESC 8 | FastParse is a ground-up implementation of JSON serialisation and parsing that 9 | avoids casting to and from AnyObject. When transforming directly to models, 10 | FastParse is 5x faster than Foundation.JSONSerialization. It is NOT just Another 11 | Swift JSON Package. 12 | DESC 13 | 14 | s.homepage = "https://github.com/vdka/JSON" 15 | s.license = { :type => "MIT", :file => "LICENSE.md" } 16 | s.author = "Ethan Jackwitz" 17 | s.ios.deployment_target = "8.0" # Because we're using frameworks 18 | s.source = { :git => "https://github.com/vdka/JSON.git", :tag => "#{s.version}" } 19 | s.source_files = "Sources", "Sources/**/*.swift" 20 | 21 | end 22 | -------------------------------------------------------------------------------- /JSON.xcodeproj/Configs/Project.xcconfig: -------------------------------------------------------------------------------- 1 | PRODUCT_NAME = $(TARGET_NAME) 2 | SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator 3 | MACOSX_DEPLOYMENT_TARGET = 10.10 4 | DYLIB_INSTALL_NAME_BASE = @rpath 5 | OTHER_SWIFT_FLAGS = -DXcode 6 | COMBINE_HIDPI_IMAGES = YES 7 | USE_HEADERMAP = NO 8 | -------------------------------------------------------------------------------- /JSON.xcodeproj/JSONTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /JSON.xcodeproj/JSON_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 | 0.16.3 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 0.16.3 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /JSON.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AA2B38D61DC0CCBB008CECCD /* AccessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2B38D51DC0CCBB008CECCD /* AccessorTests.swift */; }; 11 | AA2B38D81DC0D2EB008CECCD /* JSONIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2B38D71DC0D2EB008CECCD /* JSONIterator.swift */; }; 12 | _LinkFileRef_JSON_via_JSONTests /* JSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "_____Product_JSON" /* JSON.framework */; }; 13 | __src_cc_ref_Sources/JSON/Constants.swift /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/Constants.swift /* Constants.swift */; }; 14 | __src_cc_ref_Sources/JSON/Expressible.swift /* Expressible.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/Expressible.swift /* Expressible.swift */; }; 15 | __src_cc_ref_Sources/JSON/JSON.swift /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/JSON.swift /* JSON.swift */; }; 16 | __src_cc_ref_Sources/JSON/JSONAccessors.swift /* JSONAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/JSONAccessors.swift /* JSONAccessors.swift */; }; 17 | __src_cc_ref_Sources/JSON/JSONConvertible.swift /* JSONConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/JSONConvertible.swift /* JSONConvertible.swift */; }; 18 | __src_cc_ref_Sources/JSON/JSONError.swift /* JSONError.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/JSONError.swift /* JSONError.swift */; }; 19 | __src_cc_ref_Sources/JSON/JSONInitializable.swift /* JSONInitializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/JSONInitializable.swift /* JSONInitializable.swift */; }; 20 | __src_cc_ref_Sources/JSON/JSONOptionalExtensions.swift /* JSONOptionalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/JSONOptionalExtensions.swift /* JSONOptionalExtensions.swift */; }; 21 | __src_cc_ref_Sources/JSON/JSONParser.swift /* JSONParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/JSONParser.swift /* JSONParser.swift */; }; 22 | __src_cc_ref_Sources/JSON/JSONRepresentable.swift /* JSONRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/JSONRepresentable.swift /* JSONRepresentable.swift */; }; 23 | __src_cc_ref_Sources/JSON/JSONSerializer.swift /* JSONSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Sources/JSON/JSONSerializer.swift /* JSONSerializer.swift */; }; 24 | __src_cc_ref_Tests/JSONTests/FixtureSupport.swift /* FixtureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Tests/JSONTests/FixtureSupport.swift /* FixtureSupport.swift */; }; 25 | __src_cc_ref_Tests/JSONTests/ModelMapTests.swift /* ModelMapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Tests/JSONTests/ModelMapTests.swift /* ModelMapTests.swift */; }; 26 | __src_cc_ref_Tests/JSONTests/ParserPerformance.swift /* ParserPerformance.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Tests/JSONTests/ParserPerformance.swift /* ParserPerformance.swift */; }; 27 | __src_cc_ref_Tests/JSONTests/ParserTests.swift /* ParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Tests/JSONTests/ParserTests.swift /* ParserTests.swift */; }; 28 | __src_cc_ref_Tests/JSONTests/PublicAPITests.swift /* PublicAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Tests/JSONTests/PublicAPITests.swift /* PublicAPITests.swift */; }; 29 | __src_cc_ref_Tests/JSONTests/SerializerPerformance.swift /* SerializerPerformance.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Tests/JSONTests/SerializerPerformance.swift /* SerializerPerformance.swift */; }; 30 | __src_cc_ref_Tests/JSONTests/SerializerTests.swift /* SerializerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Tests/JSONTests/SerializerTests.swift /* SerializerTests.swift */; }; 31 | __src_cc_ref_Tests/JSONTests/UserModel.swift /* UserModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = __PBXFileRef_Tests/JSONTests/UserModel.swift /* UserModel.swift */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | 5D1889F41DA9D5DF00108B32 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = __RootObject_ /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = "______Target_JSON"; 40 | remoteInfo = JSON; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | AA2B38D51DC0CCBB008CECCD /* AccessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessorTests.swift; sourceTree = ""; }; 46 | AA2B38D71DC0D2EB008CECCD /* JSONIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONIterator.swift; sourceTree = ""; }; 47 | __PBXFileRef_JSON.xcodeproj/Configs/Project.xcconfig /* JSON.xcodeproj/Configs/Project.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = JSON.xcodeproj/Configs/Project.xcconfig; sourceTree = ""; }; 48 | __PBXFileRef_Package.swift /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 49 | __PBXFileRef_Sources/JSON/Constants.swift /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 50 | __PBXFileRef_Sources/JSON/Expressible.swift /* Expressible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expressible.swift; sourceTree = ""; }; 51 | __PBXFileRef_Sources/JSON/JSON.swift /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; 52 | __PBXFileRef_Sources/JSON/JSONAccessors.swift /* JSONAccessors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONAccessors.swift; sourceTree = ""; }; 53 | __PBXFileRef_Sources/JSON/JSONConvertible.swift /* JSONConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONConvertible.swift; sourceTree = ""; }; 54 | __PBXFileRef_Sources/JSON/JSONError.swift /* JSONError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONError.swift; sourceTree = ""; }; 55 | __PBXFileRef_Sources/JSON/JSONInitializable.swift /* JSONInitializable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONInitializable.swift; sourceTree = ""; }; 56 | __PBXFileRef_Sources/JSON/JSONOptionalExtensions.swift /* JSONOptionalExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONOptionalExtensions.swift; sourceTree = ""; }; 57 | __PBXFileRef_Sources/JSON/JSONParser.swift /* JSONParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONParser.swift; sourceTree = ""; }; 58 | __PBXFileRef_Sources/JSON/JSONRepresentable.swift /* JSONRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONRepresentable.swift; sourceTree = ""; }; 59 | __PBXFileRef_Sources/JSON/JSONSerializer.swift /* JSONSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONSerializer.swift; sourceTree = ""; }; 60 | __PBXFileRef_Tests/JSONTests/FixtureSupport.swift /* FixtureSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixtureSupport.swift; sourceTree = ""; }; 61 | __PBXFileRef_Tests/JSONTests/ModelMapTests.swift /* ModelMapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelMapTests.swift; sourceTree = ""; }; 62 | __PBXFileRef_Tests/JSONTests/ParserPerformance.swift /* ParserPerformance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserPerformance.swift; sourceTree = ""; }; 63 | __PBXFileRef_Tests/JSONTests/ParserTests.swift /* ParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserTests.swift; sourceTree = ""; }; 64 | __PBXFileRef_Tests/JSONTests/PublicAPITests.swift /* PublicAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicAPITests.swift; sourceTree = ""; }; 65 | __PBXFileRef_Tests/JSONTests/SerializerPerformance.swift /* SerializerPerformance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializerPerformance.swift; sourceTree = ""; }; 66 | __PBXFileRef_Tests/JSONTests/SerializerTests.swift /* SerializerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializerTests.swift; sourceTree = ""; }; 67 | __PBXFileRef_Tests/JSONTests/UserModel.swift /* UserModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserModel.swift; sourceTree = ""; }; 68 | "_____Product_JSON" /* JSON.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = JSON.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | "_____Product_JSONTests" /* JSONTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = JSONTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | /* End PBXFileReference section */ 71 | 72 | /* Begin PBXFrameworksBuildPhase section */ 73 | "___LinkPhase_JSON" /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 0; 76 | files = ( 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | "___LinkPhase_JSONTests" /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 0; 83 | files = ( 84 | _LinkFileRef_JSON_via_JSONTests /* JSON.framework in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | TestProducts_ /* Tests */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | "_____Product_JSONTests" /* JSONTests.xctest */, 95 | ); 96 | name = Tests; 97 | sourceTree = ""; 98 | }; 99 | "___RootGroup_" = { 100 | isa = PBXGroup; 101 | children = ( 102 | __PBXFileRef_Package.swift /* Package.swift */, 103 | "_____Configs_" /* Configs */, 104 | "_____Sources_" /* Sources */, 105 | "_______Tests_" /* Tests */, 106 | "____Products_" /* Products */, 107 | ); 108 | indentWidth = 2; 109 | sourceTree = ""; 110 | tabWidth = 2; 111 | }; 112 | "____Products_" /* Products */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | TestProducts_ /* Tests */, 116 | "_____Product_JSON" /* JSON.framework */, 117 | ); 118 | name = Products; 119 | sourceTree = ""; 120 | }; 121 | "_____Configs_" /* Configs */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | __PBXFileRef_JSON.xcodeproj/Configs/Project.xcconfig /* JSON.xcodeproj/Configs/Project.xcconfig */, 125 | ); 126 | name = Configs; 127 | sourceTree = ""; 128 | }; 129 | "_____Sources_" /* Sources */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | "_______Group_JSON" /* JSON */, 133 | ); 134 | name = Sources; 135 | sourceTree = ""; 136 | }; 137 | "_______Group_JSON" /* JSON */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | __PBXFileRef_Sources/JSON/Constants.swift /* Constants.swift */, 141 | __PBXFileRef_Sources/JSON/Expressible.swift /* Expressible.swift */, 142 | __PBXFileRef_Sources/JSON/JSON.swift /* JSON.swift */, 143 | __PBXFileRef_Sources/JSON/JSONAccessors.swift /* JSONAccessors.swift */, 144 | AA2B38D71DC0D2EB008CECCD /* JSONIterator.swift */, 145 | __PBXFileRef_Sources/JSON/JSONConvertible.swift /* JSONConvertible.swift */, 146 | __PBXFileRef_Sources/JSON/JSONError.swift /* JSONError.swift */, 147 | __PBXFileRef_Sources/JSON/JSONInitializable.swift /* JSONInitializable.swift */, 148 | __PBXFileRef_Sources/JSON/JSONOptionalExtensions.swift /* JSONOptionalExtensions.swift */, 149 | __PBXFileRef_Sources/JSON/JSONParser.swift /* JSONParser.swift */, 150 | __PBXFileRef_Sources/JSON/JSONRepresentable.swift /* JSONRepresentable.swift */, 151 | __PBXFileRef_Sources/JSON/JSONSerializer.swift /* JSONSerializer.swift */, 152 | ); 153 | name = JSON; 154 | path = Sources/JSON; 155 | sourceTree = ""; 156 | }; 157 | "_______Group_JSONTests" /* JSONTests */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | __PBXFileRef_Tests/JSONTests/FixtureSupport.swift /* FixtureSupport.swift */, 161 | AA2B38D51DC0CCBB008CECCD /* AccessorTests.swift */, 162 | __PBXFileRef_Tests/JSONTests/ModelMapTests.swift /* ModelMapTests.swift */, 163 | __PBXFileRef_Tests/JSONTests/ParserPerformance.swift /* ParserPerformance.swift */, 164 | __PBXFileRef_Tests/JSONTests/ParserTests.swift /* ParserTests.swift */, 165 | __PBXFileRef_Tests/JSONTests/PublicAPITests.swift /* PublicAPITests.swift */, 166 | __PBXFileRef_Tests/JSONTests/SerializerPerformance.swift /* SerializerPerformance.swift */, 167 | __PBXFileRef_Tests/JSONTests/SerializerTests.swift /* SerializerTests.swift */, 168 | __PBXFileRef_Tests/JSONTests/UserModel.swift /* UserModel.swift */, 169 | ); 170 | name = JSONTests; 171 | path = Tests/JSONTests; 172 | sourceTree = ""; 173 | }; 174 | "_______Tests_" /* Tests */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | "_______Group_JSONTests" /* JSONTests */, 178 | ); 179 | name = Tests; 180 | sourceTree = ""; 181 | }; 182 | /* End PBXGroup section */ 183 | 184 | /* Begin PBXNativeTarget section */ 185 | "______Target_JSON" /* JSON */ = { 186 | isa = PBXNativeTarget; 187 | buildConfigurationList = "_______Confs_JSON" /* Build configuration list for PBXNativeTarget "JSON" */; 188 | buildPhases = ( 189 | CompilePhase_JSON /* Sources */, 190 | "___LinkPhase_JSON" /* Frameworks */, 191 | ); 192 | buildRules = ( 193 | ); 194 | dependencies = ( 195 | ); 196 | name = JSON; 197 | productName = JSON; 198 | productReference = "_____Product_JSON" /* JSON.framework */; 199 | productType = "com.apple.product-type.framework"; 200 | }; 201 | "______Target_JSONTests" /* JSONTests */ = { 202 | isa = PBXNativeTarget; 203 | buildConfigurationList = "_______Confs_JSONTests" /* Build configuration list for PBXNativeTarget "JSONTests" */; 204 | buildPhases = ( 205 | CompilePhase_JSONTests /* Sources */, 206 | "___LinkPhase_JSONTests" /* Frameworks */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | __Dependency_JSON /* PBXTargetDependency */, 212 | ); 213 | name = JSONTests; 214 | productName = JSONTests; 215 | productReference = "_____Product_JSONTests" /* JSONTests.xctest */; 216 | productType = "com.apple.product-type.bundle.unit-test"; 217 | }; 218 | /* End PBXNativeTarget section */ 219 | 220 | /* Begin PBXProject section */ 221 | __RootObject_ /* Project object */ = { 222 | isa = PBXProject; 223 | attributes = { 224 | LastUpgradeCheck = 9999; 225 | }; 226 | buildConfigurationList = "___RootConfs_" /* Build configuration list for PBXProject "JSON" */; 227 | compatibilityVersion = "Xcode 3.2"; 228 | developmentRegion = English; 229 | hasScannedForEncodings = 0; 230 | knownRegions = ( 231 | English, 232 | en, 233 | ); 234 | mainGroup = "___RootGroup_"; 235 | productRefGroup = "____Products_" /* Products */; 236 | projectDirPath = ""; 237 | projectRoot = ""; 238 | targets = ( 239 | "______Target_JSON" /* JSON */, 240 | "______Target_JSONTests" /* JSONTests */, 241 | ); 242 | }; 243 | /* End PBXProject section */ 244 | 245 | /* Begin PBXSourcesBuildPhase section */ 246 | CompilePhase_JSON /* Sources */ = { 247 | isa = PBXSourcesBuildPhase; 248 | buildActionMask = 0; 249 | files = ( 250 | __src_cc_ref_Sources/JSON/Constants.swift /* Constants.swift in Sources */, 251 | __src_cc_ref_Sources/JSON/Expressible.swift /* Expressible.swift in Sources */, 252 | __src_cc_ref_Sources/JSON/JSON.swift /* JSON.swift in Sources */, 253 | __src_cc_ref_Sources/JSON/JSONAccessors.swift /* JSONAccessors.swift in Sources */, 254 | __src_cc_ref_Sources/JSON/JSONConvertible.swift /* JSONConvertible.swift in Sources */, 255 | AA2B38D81DC0D2EB008CECCD /* JSONIterator.swift in Sources */, 256 | __src_cc_ref_Sources/JSON/JSONError.swift /* JSONError.swift in Sources */, 257 | __src_cc_ref_Sources/JSON/JSONInitializable.swift /* JSONInitializable.swift in Sources */, 258 | __src_cc_ref_Sources/JSON/JSONOptionalExtensions.swift /* JSONOptionalExtensions.swift in Sources */, 259 | __src_cc_ref_Sources/JSON/JSONParser.swift /* JSONParser.swift in Sources */, 260 | __src_cc_ref_Sources/JSON/JSONRepresentable.swift /* JSONRepresentable.swift in Sources */, 261 | __src_cc_ref_Sources/JSON/JSONSerializer.swift /* JSONSerializer.swift in Sources */, 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | CompilePhase_JSONTests /* Sources */ = { 266 | isa = PBXSourcesBuildPhase; 267 | buildActionMask = 0; 268 | files = ( 269 | __src_cc_ref_Tests/JSONTests/FixtureSupport.swift /* FixtureSupport.swift in Sources */, 270 | __src_cc_ref_Tests/JSONTests/ModelMapTests.swift /* ModelMapTests.swift in Sources */, 271 | AA2B38D61DC0CCBB008CECCD /* AccessorTests.swift in Sources */, 272 | __src_cc_ref_Tests/JSONTests/ParserPerformance.swift /* ParserPerformance.swift in Sources */, 273 | __src_cc_ref_Tests/JSONTests/ParserTests.swift /* ParserTests.swift in Sources */, 274 | __src_cc_ref_Tests/JSONTests/PublicAPITests.swift /* PublicAPITests.swift in Sources */, 275 | __src_cc_ref_Tests/JSONTests/SerializerPerformance.swift /* SerializerPerformance.swift in Sources */, 276 | __src_cc_ref_Tests/JSONTests/SerializerTests.swift /* SerializerTests.swift in Sources */, 277 | __src_cc_ref_Tests/JSONTests/UserModel.swift /* UserModel.swift in Sources */, 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | /* End PBXSourcesBuildPhase section */ 282 | 283 | /* Begin PBXTargetDependency section */ 284 | __Dependency_JSON /* PBXTargetDependency */ = { 285 | isa = PBXTargetDependency; 286 | target = "______Target_JSON" /* JSON */; 287 | targetProxy = 5D1889F41DA9D5DF00108B32 /* PBXContainerItemProxy */; 288 | }; 289 | /* End PBXTargetDependency section */ 290 | 291 | /* Begin XCBuildConfiguration section */ 292 | _ReleaseConf_JSON /* Release */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | APPLICATION_EXTENSION_API_ONLY = YES; 296 | ENABLE_TESTABILITY = YES; 297 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; 298 | INFOPLIST_FILE = JSON.xcodeproj/JSON_Info.plist; 299 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 300 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 301 | OTHER_LDFLAGS = "$(inherited)"; 302 | OTHER_SWIFT_FLAGS = "$(inherited)"; 303 | PRODUCT_BUNDLE_IDENTIFIER = JSON; 304 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 305 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 306 | SWIFT_VERSION = 5.0; 307 | TVOS_DEPLOYMENT_TARGET = 9.0; 308 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 309 | }; 310 | name = Release; 311 | }; 312 | _ReleaseConf_JSONTests /* Release */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 316 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; 317 | INFOPLIST_FILE = JSON.xcodeproj/JSONTests_Info.plist; 318 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; 319 | OTHER_LDFLAGS = "$(inherited)"; 320 | OTHER_SWIFT_FLAGS = "$(inherited)"; 321 | SWIFT_VERSION = 5.0; 322 | }; 323 | name = Release; 324 | }; 325 | "___DebugConf_JSON" /* Debug */ = { 326 | isa = XCBuildConfiguration; 327 | buildSettings = { 328 | APPLICATION_EXTENSION_API_ONLY = YES; 329 | ENABLE_TESTABILITY = YES; 330 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; 331 | INFOPLIST_FILE = JSON.xcodeproj/JSON_Info.plist; 332 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 333 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 334 | OTHER_LDFLAGS = "$(inherited)"; 335 | OTHER_SWIFT_FLAGS = "$(inherited)"; 336 | PRODUCT_BUNDLE_IDENTIFIER = JSON; 337 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 338 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 339 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 340 | SWIFT_VERSION = 5.0; 341 | TVOS_DEPLOYMENT_TARGET = 9.0; 342 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 343 | }; 344 | name = Debug; 345 | }; 346 | "___DebugConf_JSONTests" /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 350 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; 351 | INFOPLIST_FILE = JSON.xcodeproj/JSONTests_Info.plist; 352 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; 353 | OTHER_LDFLAGS = "$(inherited)"; 354 | OTHER_SWIFT_FLAGS = "$(inherited)"; 355 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 356 | SWIFT_VERSION = 5.0; 357 | }; 358 | name = Debug; 359 | }; 360 | "_____Release_" /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | baseConfigurationReference = __PBXFileRef_JSON.xcodeproj/Configs/Project.xcconfig /* JSON.xcodeproj/Configs/Project.xcconfig */; 363 | buildSettings = { 364 | MACOSX_DEPLOYMENT_TARGET = 10.9; 365 | }; 366 | name = Release; 367 | }; 368 | "_______Debug_" /* Debug */ = { 369 | isa = XCBuildConfiguration; 370 | baseConfigurationReference = __PBXFileRef_JSON.xcodeproj/Configs/Project.xcconfig /* JSON.xcodeproj/Configs/Project.xcconfig */; 371 | buildSettings = { 372 | MACOSX_DEPLOYMENT_TARGET = 10.9; 373 | }; 374 | name = Debug; 375 | }; 376 | /* End XCBuildConfiguration section */ 377 | 378 | /* Begin XCConfigurationList section */ 379 | "___RootConfs_" /* Build configuration list for PBXProject "JSON" */ = { 380 | isa = XCConfigurationList; 381 | buildConfigurations = ( 382 | "_______Debug_" /* Debug */, 383 | "_____Release_" /* Release */, 384 | ); 385 | defaultConfigurationIsVisible = 0; 386 | defaultConfigurationName = Debug; 387 | }; 388 | "_______Confs_JSON" /* Build configuration list for PBXNativeTarget "JSON" */ = { 389 | isa = XCConfigurationList; 390 | buildConfigurations = ( 391 | "___DebugConf_JSON" /* Debug */, 392 | _ReleaseConf_JSON /* Release */, 393 | ); 394 | defaultConfigurationIsVisible = 0; 395 | defaultConfigurationName = Debug; 396 | }; 397 | "_______Confs_JSONTests" /* Build configuration list for PBXNativeTarget "JSONTests" */ = { 398 | isa = XCConfigurationList; 399 | buildConfigurations = ( 400 | "___DebugConf_JSONTests" /* Debug */, 401 | _ReleaseConf_JSONTests /* Release */, 402 | ); 403 | defaultConfigurationIsVisible = 0; 404 | defaultConfigurationName = Debug; 405 | }; 406 | /* End XCConfigurationList section */ 407 | }; 408 | rootObject = __RootObject_ /* Project object */; 409 | } 410 | -------------------------------------------------------------------------------- /JSON.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JSON.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JSON.xcodeproj/xcshareddata/xcschemes/JSON.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 44 | 46 | 47 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 69 | 70 | 76 | 77 | 78 | 79 | 80 | 81 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /JSON.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SchemeUserState 5 | 6 | JSON.xcscheme 7 | 8 | 9 | SuppressBuildableAutocreation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ethan Jackwitz 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package(name: "JSON") 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON 2 | 3 | [![Language](https://img.shields.io/badge/Swift-5-brightgreen.svg)](http://swift.org) [![Build Status](https://travis-ci.org/vdka/JSON.svg?branch=master)](https://travis-ci.org/vdka/JSON) 4 | 5 | Improve both the brevity and clarity of your model mapping code. 6 | 7 | JSON provides a simple and performant interface for accessing and creating serialized data. 8 | 9 | This library exposes an API with minimal surface area. 10 | 11 | # API 12 |
13 | API summary 14 | ```swift 15 | // Creating a JSON instance (static) 16 | static func JSON.Parser.parse(_ buffer: UnsafeBufferPointer, options: JSON.Parser.Option = []) throws -> JSON 17 | static func JSON.Parser.parse(_ data: [UTF8.CodeUnit], options: JSON.Parser.Option = []) throws -> JSON 18 | static func JSON.Parser.parse(_ data: Data, options: JSON.Parser.Option = []) throws -> JSON 19 | static func JSON.Parser.parse(_ string: String, options: JSON.Parser.Option = []) throws -> JSON 20 | 21 | // Serializing a JSON instance 22 | static func JSON.Serializer.serialize(_ json: JSON, options: JSON.Serializer.Option = []) throws -> String 23 | static func JSON.Serializer.serialize(_ json: JSON, to stream: inout O, options: JSON.Serializer.Option) throws 24 | func JSON.serialized(options: JSON.Serializer.Option = []) throws -> String 25 | 26 | // Accessing JSON 27 | func JSON.get(_ field: String, `default`: String?) -> T 28 | func JSON.get(_ field: String, `default`: T? = nil) throws -> T? 29 | func JSON.get(_ field: String, `default`: [T]? = nil) throws -> [T] 30 | 31 | var JSON.object: [String: JSON]? 32 | var JSON.array: [JSON]? 33 | var JSON.string: String? 34 | var JSON.int: Int? 35 | var JSON.bool: Bool? 36 | var JSON.double: Double? 37 | 38 | var JSON.isObject: Bool 39 | var JSON.isArray: Bool 40 | var JSON.isString: Bool 41 | var JSON.isInt: Bool 42 | var JSON.isBool: Bool 43 | var JSON.isDouble: Bool 44 | 45 | protocol JSONInitializable { 46 | init(json: JSON) throws 47 | } 48 | 49 | protocol JSONRepresentable { 50 | func encoded() -> JSON 51 | } 52 | ``` 53 |
54 | 55 | For deserialization the `get` method is generic and initializes the result type with `init(json: JSON) throws` or throws an error indicative of what went wrong. Because this generic method is constraint to any type that conforms to `JSONInitializable` it is possible to extract your own complex nested models by just calling `get`. 56 | Furthermore there are overloads to the `get` method that allow the initialization of `Optional` and `RawRepresentable` types when their `Wrapped` and `RawValue`s are conformant to `JSONInitialable`. This means the majority of your simple `RawRepresentable` enum's can be initialized without needing to create an explicit initializer. 57 | 58 | Similarly on the model serialization side the `encoded` method is the single point of call. It is automatically called by the initializers for `ExpressibleByArrayLiteral` & `ExpressibleByDictionaryLiteral`. This makes declaring JSON instances extremely simple. 59 | 60 | # Examples 61 | 62 | - [Samples](https://github.com/vdka/JSON-Sample) Catered examples using real API's 63 | - [Commandline application](https://github.com/vdka/cj) for accessing JSON when scripting 64 | 65 |
66 | Example Usage 67 | ```json 68 | { 69 | "status": "online", 70 | "last_active": 1481873354, 71 | "email": "jane@example.com", 72 | "username": "janesmith", 73 | "name": "Jane Smith", 74 | "dob": 805852800, 75 | "accepted_terms": true 76 | } 77 | ``` 78 | 79 | ```swift 80 | enum State: String { case online, offline } 81 | 82 | struct User { 83 | var status: Status 84 | var lastActive: Date 85 | var name: String 86 | var email: String 87 | var dob: Date 88 | var acceptedTerms: Bool 89 | var friends: [String] 90 | var avatarUrl: URL? 91 | } 92 | 93 | extension User: JSONInitializable { 94 | init(json: JSON) throws { 95 | status = try json.get("status") 96 | lastActive = try json.get("last_active") 97 | name = try json.get("name") 98 | email = try json.get("email") 99 | dob = try json.get("dob") 100 | acceptedTerms = try json.get("accepted_terms") 101 | friends = try json.get("friends") 102 | avatarUrl = try json.get("avatar_url") 103 | } 104 | } 105 | 106 | extension User: JSONRepresentable { 107 | 108 | func encoded() -> JSON { 109 | return 110 | [ 111 | "status": status, 112 | "name": name, 113 | "email": email, 114 | "dob": dob, 115 | "accepted_terms": acceptedTerms, 116 | "friends": friends.encoded(), 117 | "avatar_url": avatarUrl.encoded() 118 | ] 119 | } 120 | } 121 | ``` 122 |
123 | 124 | # Installation 125 | 126 | ## CocoaPods 127 | > Coming soon! 128 | 129 | ## Carthage 130 | ``` 131 | github "vdka/json" 132 | ``` 133 | 134 | ## Swift Package Manager 135 | ``` 136 | .Package(url: "https://github.com/vdka/JSON", majorVersion: 0, minor: 16), 137 | ``` 138 | -------------------------------------------------------------------------------- /Sources/JSON/Constants.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | // json special characters 4 | let arrayOpen: UTF8.CodeUnit = "[".utf8.first! 5 | let objectOpen: UTF8.CodeUnit = "{".utf8.first! 6 | let arrayClose: UTF8.CodeUnit = "]".utf8.first! 7 | let objectClose: UTF8.CodeUnit = "}".utf8.first! 8 | let comma: UTF8.CodeUnit = ",".utf8.first! 9 | let colon: UTF8.CodeUnit = ":".utf8.first! 10 | let quote: UTF8.CodeUnit = "\"".utf8.first! 11 | let slash: UTF8.CodeUnit = "/".utf8.first! 12 | let backslash: UTF8.CodeUnit = "\\".utf8.first! 13 | 14 | let star: UTF8.CodeUnit = "*".utf8.first! 15 | 16 | // whitespace characters 17 | let space: UTF8.CodeUnit = " ".utf8.first! 18 | let tab: UTF8.CodeUnit = "\t".utf8.first! 19 | let cr: UTF8.CodeUnit = "\r".utf8.first! 20 | let newline: UTF8.CodeUnit = "\n".utf8.first! 21 | let backspace: UTF8.CodeUnit = UTF8.CodeUnit(0x08) 22 | let formfeed: UTF8.CodeUnit = UTF8.CodeUnit(0x0C) 23 | 24 | // Literal characters 25 | let n: UTF8.CodeUnit = "n".utf8.first! 26 | let t: UTF8.CodeUnit = "t".utf8.first! 27 | let r: UTF8.CodeUnit = "r".utf8.first! 28 | let u: UTF8.CodeUnit = "u".utf8.first! 29 | let f: UTF8.CodeUnit = "f".utf8.first! 30 | let a: UTF8.CodeUnit = "a".utf8.first! 31 | let l: UTF8.CodeUnit = "l".utf8.first! 32 | let s: UTF8.CodeUnit = "s".utf8.first! 33 | let e: UTF8.CodeUnit = "e".utf8.first! 34 | 35 | let b: UTF8.CodeUnit = "b".utf8.first! 36 | 37 | // Number characters 38 | let E: UTF8.CodeUnit = "E".utf8.first! 39 | let zero: UTF8.CodeUnit = "0".utf8.first! 40 | let plus: UTF8.CodeUnit = "+".utf8.first! 41 | let minus: UTF8.CodeUnit = "-".utf8.first! 42 | let decimal: UTF8.CodeUnit = ".".utf8.first! 43 | let numbers: ClosedRange = "0".utf8.first!..."9".utf8.first! 44 | let alphaNumericLower: ClosedRange = "a".utf8.first!..."f".utf8.first! 45 | let alphaNumericUpper: ClosedRange = "A".utf8.first!..."F".utf8.first! 46 | 47 | let invalidUnicodeBytes: ClosedRange = 0xF5...0xFF 48 | 49 | // Valid integer number Range 50 | let valid64BitInteger: ClosedRange = Int64.min...Int64.max 51 | let validUnsigned64BitInteger: ClosedRange = UInt64.min...UInt64(Int64.max) 52 | 53 | // End of here Literals 54 | let rue: [UTF8.CodeUnit] = ["r".utf8.first!, "u".utf8.first!, "e".utf8.first!] 55 | let alse: [UTF8.CodeUnit] = ["a".utf8.first!, "l".utf8.first!, "s".utf8.first!, "e".utf8.first!] 56 | let ull: [UTF8.CodeUnit] = ["u".utf8.first!, "l".utf8.first!, "l".utf8.first!] 57 | 58 | // Comment stuff 59 | let lineComment: [UTF8.CodeUnit] = ["/".utf8.first!, "/".utf8.first!] 60 | let blockCommentStart: [UTF8.CodeUnit] = ["/".utf8.first!, "*".utf8.first!] 61 | let blockCommentEnd: [UTF8.CodeUnit] = ["*".utf8.first!, "/".utf8.first!] 62 | -------------------------------------------------------------------------------- /Sources/JSON/Expressible.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | extension JSON: ExpressibleByArrayLiteral { 4 | public init(arrayLiteral elements: JSONRepresentable...) { 5 | let array = elements.map({ $0.encoded() }) 6 | self = .array(array) 7 | } 8 | } 9 | 10 | extension JSON: ExpressibleByDictionaryLiteral { 11 | public init(dictionaryLiteral elements: (String, JSONRepresentable)...) { 12 | 13 | var dict: [String: JSON] = [:] 14 | 15 | for (key, value) in elements { 16 | dict[key] = value.encoded() 17 | } 18 | 19 | self = .object(dict) 20 | } 21 | } 22 | 23 | extension JSON: ExpressibleByIntegerLiteral { 24 | public init(integerLiteral value: IntegerLiteralType) { 25 | let val = Int64(value) 26 | self = .integer(val) 27 | } 28 | } 29 | 30 | extension JSON: ExpressibleByFloatLiteral { 31 | public init(floatLiteral value: FloatLiteralType) { 32 | let val = Double(value) 33 | self = .double(val) 34 | } 35 | } 36 | 37 | extension JSON: ExpressibleByStringLiteral { 38 | public init(stringLiteral value: String) { 39 | self = .string(value) 40 | } 41 | 42 | public init(extendedGraphemeClusterLiteral value: String) { 43 | self = .string(value) 44 | } 45 | 46 | public init(unicodeScalarLiteral value: String) { 47 | self = .string(value) 48 | } 49 | } 50 | 51 | extension JSON: ExpressibleByNilLiteral { 52 | public init(nilLiteral: ()) { 53 | self = .null 54 | } 55 | } 56 | 57 | extension JSON: ExpressibleByBooleanLiteral { 58 | public init(booleanLiteral value: Bool) { 59 | self = .bool(value) 60 | } 61 | } 62 | 63 | 64 | // MARK: - JSON: CustomStringConvertible 65 | 66 | extension JSON { 67 | 68 | /** 69 | Turns a nested graph of `JSON`s into a Swift `String`. This produces JSON data that 70 | strictly conforms to [RFT7159](https://tools.ietf.org/html/rfc7159). 71 | It can optionally pretty-print the output for debugging, but this comes with a non-negligible performance cost. 72 | */ 73 | public func serialized(options: JSON.Serializer.Option = []) throws -> String { 74 | return try JSON.Serializer.serialize(self, options: options) 75 | } 76 | } 77 | 78 | extension JSON: CustomStringConvertible { 79 | public var description: String { 80 | do { 81 | return try self.serialized() 82 | } catch { 83 | return String(describing: error) 84 | } 85 | } 86 | } 87 | 88 | extension JSON: CustomDebugStringConvertible { 89 | public var debugDescription: String { 90 | do { 91 | return try self.serialized(options: .prettyPrint) 92 | } catch { 93 | return String(describing: error) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/JSON/JSON.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// Any value that can be expressed in JSON has a representation in `JSON`. 4 | @dynamicMemberLookup 5 | public enum JSON { 6 | case object([String: JSON]) 7 | case array([JSON]) 8 | case null 9 | case bool(Bool) 10 | case string(String) 11 | case integer(Int64) 12 | case double(Double) 13 | } 14 | 15 | extension JSON { 16 | subscript(dynamicMember member: String) -> JSON { 17 | return (try? self.get(member)) ?? .null 18 | } 19 | } 20 | 21 | 22 | // MARK: - JSON Equatable conformance 23 | 24 | extension JSON: Equatable {} 25 | public func ==(lhs: JSON, rhs: JSON) -> Bool { 26 | switch (lhs, rhs) { 27 | case (.object(let l), .object(let r)): return l == r 28 | case (.array(let l), .array(let r)): return l == r 29 | case (.null, .null): return true 30 | case (.bool(let l), .bool(let r)): return l == r 31 | case (.string(let l), .string(let r)): return l == r 32 | case (.double(let l), .double(let r)): return l == r 33 | case (.integer(let l), .integer(let r)): return l == r 34 | default: return false 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/JSON/JSONAccessors.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | // I wish this was generated. 4 | 5 | extension JSON { 6 | 7 | public func get(`default`: T? = nil) throws -> T { 8 | do { 9 | return try T(json: self) 10 | } catch { 11 | guard let `default` = `default` else { throw error } 12 | return `default` 13 | } 14 | } 15 | 16 | public func get(`default`: T? = nil) throws -> T? { 17 | do { 18 | return try T(json: self) 19 | } catch { 20 | if case .null = self { return nil } 21 | guard let `default` = `default` else { throw error } 22 | return `default` 23 | } 24 | } 25 | 26 | public func get(`default`: [T]? = nil) throws -> [T] { 27 | do { 28 | guard case .array(let array) = self else { throw JSON.Error.badValue(self) } 29 | return try array.map(T.init(json:)) 30 | } catch { 31 | guard let `default` = `default` else { throw error } 32 | return `default` 33 | } 34 | } 35 | 36 | public func get(`default`: T? = nil) throws -> T 37 | where T.RawValue: JSONInitializable 38 | { 39 | do { 40 | return try T(json: self) 41 | } catch { 42 | guard let `default` = `default` else { throw error } 43 | return `default` 44 | } 45 | } 46 | 47 | public func get(`default`: T? = nil) throws -> T? 48 | where T.RawValue: JSONInitializable 49 | { 50 | do { 51 | return try T(json: self) 52 | } catch { 53 | if case .null = self { return nil } 54 | guard let `default` = `default` else { throw error } 55 | return `default` 56 | } 57 | } 58 | 59 | public func get(`default`: [T]? = nil) throws -> [T] 60 | where T.RawValue: JSONInitializable 61 | { 62 | do { 63 | guard case .array(let array) = self else { throw JSON.Error.badValue(self) } 64 | return try array.map(T.init(json:)) 65 | } catch { 66 | guard let `default` = `default` else { throw error } 67 | return `default` 68 | } 69 | } 70 | 71 | public func get(`default`: T? = nil) throws -> T { 72 | do { 73 | return try T(json: self) 74 | } catch { 75 | guard let `default` = `default` else { throw error } 76 | return `default` 77 | } 78 | } 79 | 80 | public func get(`default`: T? = nil) throws -> T? 81 | where T.RawValue: JSONInitializable 82 | { 83 | do { 84 | return try T(json: self) 85 | } catch { 86 | if case .null = self { return nil } 87 | guard let `default` = `default` else { throw error } 88 | return `default` 89 | } 90 | } 91 | 92 | /// Returns the content matching the type of its destination 93 | public func get(`default`: [T]? = nil) throws -> [T] { 94 | do { 95 | guard case .array(let array) = self else { throw JSON.Error.badValue(self) } 96 | return try array.map(T.init(json:)) 97 | } catch { 98 | guard let `default` = `default` else { throw error } 99 | return `default` 100 | } 101 | } 102 | } 103 | 104 | 105 | // MARK: With fields 106 | 107 | extension JSON { 108 | 109 | public func get(field: String) throws -> JSON { 110 | guard let json = self[field] else { throw JSON.Error.badField(field) } 111 | return json 112 | } 113 | 114 | /// Returns the content matching the type of its destination 115 | public func get(_ field: String, `default`: T? = nil) throws -> T { 116 | do { 117 | guard let json = self[field] else { throw JSON.Error.badField(field) } 118 | return try T(json: json) 119 | } catch { 120 | guard let `default` = `default` else { throw error } 121 | return `default` 122 | } 123 | } 124 | 125 | /// If the Field exists in the JSON then this will call to the expected types initializer 126 | /// - Note: This call will throw iff the initializer does 127 | public func get(_ field: String, `default`: T? = nil) throws -> T? { 128 | guard let json = self[field] else { return `default` } 129 | if case .null = json { return `default` } 130 | do { 131 | return try T(json: json) 132 | } catch { 133 | guard let `default` = `default` else { throw error } 134 | return `default` 135 | } 136 | } 137 | 138 | public func get(_ field: String, `default`: [T]? = nil) throws -> [T] { 139 | do { 140 | guard let array = self[field].array else { throw JSON.Error.badField(field) } 141 | return try array.map(T.init(json:)) 142 | } catch { 143 | guard let `default` = `default` else { throw error } 144 | return `default` 145 | } 146 | } 147 | 148 | /// Returns the content matching the type of its destination 149 | public func get(_ field: String, `default`: T? = nil) throws -> T 150 | where T.RawValue: JSONInitializable 151 | { 152 | do { 153 | let rawValue: T.RawValue = try self.get(field) 154 | guard let value = T(rawValue: rawValue) else { throw JSON.Error.badField(field) } 155 | return value 156 | } catch { 157 | guard let `default` = `default` else { throw error } 158 | return `default` 159 | } 160 | } 161 | 162 | /// Returns the content matching the type of its destination 163 | public func get(_ field: String, `default`: [T]? = nil) throws -> [T] 164 | where T.RawValue: JSONInitializable 165 | { 166 | do { 167 | guard let array = self[field].array else { throw JSON.Error.badField(field) } 168 | return try array.map(T.init(json:)) 169 | } catch { 170 | guard let `default` = `default` else { throw error } 171 | return `default` 172 | } 173 | } 174 | 175 | public func get(_ field: String, `default`: T? = nil) throws -> T? { 176 | guard let json = self[field] else { return `default` } 177 | if case .null = json { return `default` } 178 | do { 179 | return try T(json: json) 180 | } catch { 181 | guard let `default` = `default` else { throw error } 182 | return `default` 183 | } 184 | } 185 | 186 | /// Returns the content matching the type of its destination 187 | public func get(_ field: String, `default`: T? = nil) throws -> T { 188 | do { 189 | guard let json = self[field] else { throw JSON.Error.badField(field) } 190 | return try T(json: json) 191 | } catch { 192 | guard let `default` = `default` else { throw error } 193 | return `default` 194 | } 195 | } 196 | 197 | /// Returns the content matching the type of its destination 198 | public func get(_ field: String, `default`: [T]? = nil) throws -> [T] { 199 | do { 200 | guard let array = self[field].array else { throw JSON.Error.badField(field) } 201 | return try array.map(T.init(json:)) 202 | } catch { 203 | guard let `default` = `default` else { throw error } 204 | return `default` 205 | } 206 | } 207 | } 208 | 209 | 210 | // MARK: - JSON Subscripts 211 | 212 | extension JSON { 213 | /// Treat this JSON as a JSON object and attempt to get or set its associated Dictionary values. 214 | public subscript(key: String) -> JSON? { 215 | get { 216 | guard case .object(let object) = self else { return nil } 217 | return object[key] 218 | } 219 | 220 | set { 221 | guard case .object(var object) = self else { return } 222 | object[key] = newValue 223 | self = .object(object) 224 | } 225 | } 226 | 227 | /** 228 | Treat this JSON as a JSON array and attempt to get or set its 229 | associated Array values. 230 | This will do nothing if you attempt to set outside of bounds. 231 | */ 232 | public subscript(index: Int) -> JSON? { 233 | get { 234 | guard case .array(let a) = self, a.indices ~= index else { return nil } 235 | return a[index] 236 | } 237 | 238 | set { 239 | guard case .array(var a) = self, a.indices ~= index else { return } 240 | 241 | if let newValue = newValue { a[index] = newValue } 242 | else { a.remove(at: index) } 243 | 244 | self = .array(a) 245 | } 246 | } 247 | } 248 | 249 | 250 | // MARK: - JSON Accessors 251 | 252 | extension JSON { 253 | 254 | /// Returns this enum's associated Dictionary value iff `self == .object(_), `nil` otherwise. 255 | public var object: [String: JSON]? { 256 | guard case .object(let o) = self else { return nil } 257 | return o 258 | } 259 | 260 | /// Returns this enum's associated Array value iff `self == .array(_)`, `nil` otherwise. 261 | public var array: [JSON]? { 262 | guard case .array(let a) = self else { return nil } 263 | return a 264 | } 265 | 266 | /// Returns this enum's associated String value iff `self == .string(_)`, `nil` otherwise. 267 | public var string: String? { 268 | guard case .string(let s) = self else { return nil } 269 | return s 270 | } 271 | 272 | /// Returns this enum's associated `Int64` value iff `self == .integer(i)`, `nil` otherwise. 273 | public var int64: Int64? { 274 | switch self { 275 | case .integer(let i): return i 276 | case .string(let s): return Int64(s) 277 | default: return nil 278 | } 279 | } 280 | 281 | /// Returns this enum's associated Bool value iff `self == .bool(_)`, `nil` otherwise. 282 | public var bool: Bool? { 283 | switch self { 284 | case .bool(let b): return b 285 | case .string(let s): return Bool(s) 286 | default: return nil 287 | } 288 | } 289 | 290 | /// Returns this enum's associated Double value iff `self == .double(_)`, `nil` otherwise. 291 | public var double: Double? { 292 | 293 | switch self { 294 | case .double(let d): return d 295 | case .string(let s): return Double(s) 296 | case .integer(let i): return Double(i) 297 | default: return nil 298 | } 299 | } 300 | } 301 | 302 | 303 | // MARK: Non RFC JSON types 304 | 305 | extension JSON { 306 | 307 | /// Returns this enum's associated `Int64` value as an `Int` iff `self == .integer(_)`, `nil` otherwise. 308 | public var int: Int? { 309 | switch self { 310 | case .integer(let i): return Int(exactly: i) 311 | case .string(let s): return Int(s) 312 | default: return nil 313 | } 314 | } 315 | 316 | /// Returns this enum's associated `Double` value as an `Float` iff `self == .double(_)`, `nil` otherwise. 317 | public var float: Float? { 318 | 319 | switch self { 320 | case .double(let d): return Float(d) 321 | case .string(let s): return Float(s) 322 | case .integer(let i): return Float(i) 323 | default: return nil 324 | } 325 | } 326 | } 327 | 328 | extension JSON { 329 | 330 | public var isObject: Bool { 331 | 332 | if case .object(_) = self { return true } 333 | else { return false } 334 | } 335 | 336 | public var isArray: Bool { 337 | 338 | if case .array(_) = self { return true } 339 | else { return false } 340 | } 341 | 342 | public var isInt: Bool { 343 | 344 | if case .integer(_) = self { return true } 345 | else { return false } 346 | } 347 | 348 | public var isDouble: Bool { 349 | 350 | if case .double(_) = self { return true } 351 | else { return false } 352 | } 353 | 354 | public var isBool: Bool { 355 | 356 | if case .bool(_) = self { return true } 357 | else { return false } 358 | } 359 | 360 | public var isString: Bool { 361 | 362 | if case .string(_) = self { return true } 363 | else { return false } 364 | } 365 | 366 | public var isNull: Bool { 367 | 368 | if case .null = self { return true } 369 | else { return false } 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /Sources/JSON/JSONConvertible.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | public protocol JSONConvertible: JSONInitializable, JSONRepresentable {} 4 | 5 | extension JSON: JSONConvertible { 6 | 7 | public init(json: JSON) throws { 8 | self = json 9 | } 10 | 11 | public func encoded() -> JSON { 12 | return self 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/JSON/JSONError.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | extension JSON { 4 | 5 | /// Represent an error resulting during mapping either, to or from an instance type. 6 | public enum Error: Swift.Error { 7 | 8 | // BadField indicates an error where a field was missing or was of the wrong type. The associated value represents the name of the field. 9 | case badField(String) 10 | /// When thrown during initialization it indicates a value in the JSON could not be converted to RFC 11 | case badValue(JSON) 12 | /// A number was not valid per the JSON spec. (handled in Parser?) 13 | case invalidNumber 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/JSON/JSONInitializable.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// Conforming types can be decoded from a JSON instance 4 | public protocol JSONInitializable { 5 | 6 | /// Initialize an instance of `Self` from JSON 7 | init(json: JSON) throws 8 | } 9 | 10 | 11 | // MARK: - Partial implementation 12 | 13 | extension JSONInitializable { 14 | 15 | public static func decode(_ json: JSON) throws -> Self { 16 | return try Self(json: json) 17 | } 18 | } 19 | 20 | 21 | // MARK: - Bool Conformance to JSONInitializable 22 | 23 | extension Bool: JSONInitializable { 24 | 25 | public init(json: JSON) throws { 26 | guard let b = json.bool else { throw JSON.Error.badValue(json) } 27 | self = b 28 | } 29 | } 30 | 31 | 32 | // MARK: - String Conformance to JSONInitializable 33 | 34 | extension String: JSONInitializable { 35 | 36 | public init(json: JSON) throws { 37 | guard let s = json.string else { throw JSON.Error.badValue(json) } 38 | self = s 39 | } 40 | } 41 | 42 | 43 | // MARK: - FloatingPointTypes: JSONInitializable 44 | 45 | extension Double: JSONInitializable { 46 | 47 | public init(json: JSON) throws { 48 | guard let d = json.double else { throw JSON.Error.badValue(json) } 49 | self = d 50 | } 51 | } 52 | 53 | extension Float: JSONInitializable { 54 | 55 | public init(json: JSON) throws { 56 | guard let f = json.float else { throw JSON.Error.badValue(json) } 57 | self = f 58 | } 59 | } 60 | 61 | 62 | // MARK: - IntegerTypes: JSONInitializable 63 | 64 | extension Int: JSONInitializable { 65 | 66 | public init(json: JSON) throws { 67 | guard let i = json.int else { throw JSON.Error.badValue(json) } 68 | self = i 69 | } 70 | } 71 | 72 | extension Int64: JSONInitializable { 73 | 74 | public init(json: JSON) throws { 75 | guard let i = json.int64 else { throw JSON.Error.badValue(json) } 76 | self = i 77 | } 78 | } 79 | 80 | 81 | // NOTE: track rdar://23433955 82 | 83 | // MARK: - Add decode to Optional JSONInitializables 84 | 85 | // TODO (vdka): add init(json: JSON) throws 86 | extension Optional where Wrapped: JSONInitializable { 87 | 88 | public init(json: JSON) throws { 89 | self = try Wrapped(json: json) 90 | } 91 | 92 | public static func decode(json: JSON) throws -> Optional { 93 | return try Optional(json: json) 94 | } 95 | } 96 | 97 | 98 | // MARK: - Add decode to RawRepresentable JSONInitializables 99 | 100 | extension RawRepresentable where RawValue: JSONInitializable { 101 | 102 | public init(json: JSON) throws { 103 | guard let value = try Self(rawValue: RawValue(json: json)) else { throw JSON.Error.badValue(json) } 104 | self = value 105 | } 106 | 107 | public static func decode(json: JSON) throws -> Self { 108 | return try Self(json: json) 109 | } 110 | } 111 | 112 | 113 | // MARK: - Add decode to Arrays of JSONInitializable 114 | 115 | extension Array where Element: JSONInitializable { 116 | 117 | public init(json: JSON) throws { 118 | guard let array = json.array else { throw JSON.Error.badValue(json) } 119 | self = try array.map(Element.init(json:)) 120 | } 121 | 122 | public static func decode(json: JSON) throws -> [Element] { 123 | return try Array(json: json) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Sources/JSON/JSONIterator.swift: -------------------------------------------------------------------------------- 1 | 2 | extension JSON: Sequence { 3 | 4 | public func makeIterator() -> AnyIterator { 5 | 6 | switch self { 7 | case .array(let array): 8 | var iterator = array.makeIterator() 9 | return AnyIterator { 10 | return iterator.next() 11 | } 12 | 13 | case .object(let object): 14 | var iterator = object.makeIterator() 15 | return AnyIterator { 16 | guard let (key, value) = iterator.next() else { return nil } 17 | 18 | return .object([key: value]) 19 | } 20 | 21 | default: 22 | 23 | var value: JSON? = self 24 | 25 | return AnyIterator { 26 | defer { value = nil } 27 | if case .null? = value { return nil } 28 | return value 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/JSON/JSONOptionalExtensions.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// WARNING: Internal type. Used to constrain an extension on Optional to be sudo non Generic. 4 | public protocol _JSON {} 5 | extension JSON: _JSON {} 6 | 7 | // Would be best if we could constrain extensions to be Non-Generic. Swift3? 8 | // TODO: Test setters ensure behaviour is predictable and expected when operating on nested JSON 9 | // TODO: Check if it is viable to use JSONRepresentable as the contraint and be rid of _JSON 10 | extension Optional where Wrapped: _JSON { 11 | 12 | /// returns the `JSON` value for key iff `Wrapped == JSON.object(_)` and there is a value for the key 13 | /// - Note: you will get better performance if you chain your subscript eg. ["key"]?.string This is because the compiler will retain more type information. 14 | public subscript(key: String) -> JSON? { 15 | get { 16 | return object?[key] 17 | } 18 | 19 | set { 20 | guard var json = self as? JSON else { return } 21 | guard case .object(_) = json else { return } 22 | json[key] = newValue 23 | self = json as? Wrapped 24 | } 25 | } 26 | 27 | /// returns the JSON value at index iff `Wrapped == JSON.array(_)` and the index is within the arrays bounds 28 | public subscript(index: Int) -> JSON? { 29 | get { 30 | guard let `self` = self as? JSON else { return nil } 31 | guard case .array(let a) = self, a.indices ~= index else { return nil } 32 | return a[index] 33 | } 34 | 35 | set { 36 | guard var a = (self as? JSON)?.array else { return } 37 | switch newValue { 38 | case .none: a.remove(at: index) 39 | case .some(let value): 40 | a[index] = value 41 | self = (JSON.array(a) as? Wrapped) 42 | } 43 | 44 | } 45 | } 46 | } 47 | 48 | 49 | // MARK: - Standard typed accessors 50 | 51 | extension Optional where Wrapped: _JSON { 52 | 53 | /// Returns an array of `JSON` iff `Wrapped == JSON.array(_)` 54 | public var array: [JSON]? { 55 | guard let `self` = self as? JSON else { return nil } 56 | return self.array 57 | } 58 | 59 | /// Returns a `JSON` object iff `Wrapped == JSON.object(_)` 60 | public var object: [String: JSON]? { 61 | guard let `self` = self as? JSON else { return nil } 62 | return self.object 63 | } 64 | 65 | /// Returns a `String` iff `Wrapped == JSON.string(_)` 66 | public var string: String? { 67 | guard let `self` = self as? JSON else { return nil } 68 | return self.string 69 | } 70 | 71 | /// Returns this enum's associated `Int64` iff `self == .integer(_)`, `nil` otherwise. 72 | public var int64: Int64? { 73 | guard let `self` = self as? JSON else { return nil } 74 | return self.int64 75 | } 76 | 77 | /// Returns a `Bool` iff `Wrapped == JSON.bool(_)` 78 | public var bool: Bool? { 79 | guard let `self` = self as? JSON else { return nil } 80 | return self.bool 81 | } 82 | 83 | /// Returns a `Double` iff `Wrapped == JSON.double(_)` 84 | public var double: Double? { 85 | guard let `self` = self as? JSON else { return nil } 86 | return self.double 87 | } 88 | } 89 | 90 | 91 | // MARK: Non RFC JSON types 92 | 93 | extension Optional where Wrapped: _JSON { 94 | 95 | /// Returns an `Int` iff `Wrapped == JSON.integer(_)` 96 | public var int: Int? { 97 | guard let `self` = self as? JSON else { return nil } 98 | return self.int 99 | } 100 | 101 | /// Returns an `Float` iff `Wrapped == JSON.double(_)` 102 | public var float: Float? { 103 | guard let `self` = self as? JSON else { return nil } 104 | return self.float 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/JSON/JSONParser.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if os(Linux) 4 | import SwiftGlibc.C 5 | #else 6 | import Darwin.C 7 | #endif 8 | 9 | extension JSON { 10 | 11 | public struct Parser { 12 | 13 | public struct Option: OptionSet { 14 | public init(rawValue: UInt8) { self.rawValue = rawValue } 15 | public let rawValue: UInt8 16 | 17 | /// Omit null values from `JSON.object`s & `JSON.array`s 18 | public static let omitNulls = Option(rawValue: 0b0001) 19 | 20 | /// Allows Parser to return top level objects that are not container types `{}` | `[]` as per RFC7159 21 | public static let allowFragments = Option(rawValue: 0b0010) 22 | 23 | /// Allow the Parser to remove comments 24 | public static let allowComments = Option(rawValue: 0b0100) 25 | } 26 | 27 | let omitNulls: Bool 28 | let allowComments: Bool 29 | 30 | var pointer: UnsafePointer 31 | var buffer: UnsafeBufferPointer 32 | 33 | /// Used to reduce the number of alloc's for parsing subsequent strings 34 | var stringBuffer: [UTF8.CodeUnit] = [] 35 | } 36 | } 37 | 38 | 39 | // MARK: - Initializers 40 | 41 | extension JSON.Parser { 42 | 43 | // assumes data is null terminated. 44 | // and that the buffer will not be de-allocated before completion (handled by JSON.Parser.parse(_:,options:) 45 | internal init(bufferPointer: UnsafeBufferPointer, options: Option) throws { 46 | 47 | self.buffer = bufferPointer 48 | 49 | guard let pointer = bufferPointer.baseAddress, buffer.endAddress != bufferPointer.baseAddress else { throw Error(byteOffset: 0, reason: .emptyStream) } 50 | 51 | self.pointer = pointer 52 | self.omitNulls = options.contains(.omitNulls) 53 | self.allowComments = options.contains(.allowComments) 54 | } 55 | } 56 | 57 | 58 | // MARK: - Public API 59 | 60 | extension JSON.Parser { 61 | 62 | public static func parse(_ buffer: UnsafeBufferPointer, options: Option = []) throws -> JSON { 63 | 64 | var parser = try JSON.Parser(bufferPointer: buffer, options: options) 65 | 66 | do { 67 | 68 | try parser.skipWhitespace() 69 | 70 | let rootValue = try parser.parseValue() 71 | 72 | if !options.contains(.allowFragments) { 73 | switch rootValue { 74 | case .array(_), .object(_): break 75 | 76 | default: throw Error.Reason.fragmentedJson 77 | } 78 | } 79 | 80 | // TODO (vkda): option to skip the trailing data check, useful for say streams see Jay's model 81 | 82 | try parser.skipWhitespace() 83 | 84 | guard parser.pointer == parser.buffer.endAddress else { throw Error.Reason.invalidSyntax } 85 | 86 | return rootValue 87 | } catch let error as Error.Reason { 88 | 89 | // We unwrap here because on we do this check prior to the do { } catch { } block. 90 | throw Error(byteOffset: parser.buffer.baseAddress!.distance(to: parser.pointer), reason: error) 91 | } 92 | } 93 | } 94 | 95 | import struct Foundation.Data 96 | 97 | extension JSON.Parser { 98 | 99 | public static func parse(_ data: Data, options: Option = []) throws -> JSON { 100 | 101 | return try data.withUnsafeBytes { (pointer: UnsafePointer) in 102 | 103 | let buffer = UnsafeBufferPointer(start: pointer, count: data.count) 104 | 105 | return try JSON.Parser.parse(buffer, options: options) 106 | } 107 | } 108 | 109 | public static func parse(_ data: [UTF8.CodeUnit], options: Option = []) throws -> JSON { 110 | 111 | return try data.withUnsafeBufferPointer { buffer in 112 | 113 | return try JSON.Parser.parse(buffer, options: options) 114 | } 115 | } 116 | 117 | public static func parse(_ string: String, options: Option = []) throws -> JSON { 118 | 119 | let data = Array(string.utf8) 120 | 121 | return try JSON.Parser.parse(data, options: options) 122 | } 123 | 124 | } 125 | 126 | 127 | // MARK: - Internals 128 | 129 | extension JSON.Parser { 130 | 131 | func peek(aheadBy n: Int = 0) -> UTF8.CodeUnit? { 132 | guard pointer.advanced(by: n) < buffer.endAddress else { 133 | return nil 134 | } 135 | return pointer.advanced(by: n).pointee 136 | } 137 | 138 | func hasPrefix(_ prefix: [UTF8.CodeUnit]) -> Bool { 139 | 140 | for (index, byte) in prefix.enumerated() { 141 | guard byte == peek(aheadBy: index) else { return false } 142 | } 143 | return true 144 | } 145 | 146 | /// - Precondition: pointer != buffer.endAddress. It is assumed before calling pop that you have 147 | @discardableResult 148 | mutating func pop() -> UTF8.CodeUnit { 149 | assert(pointer != buffer.endAddress) 150 | defer { pointer = pointer.advanced(by: 1) } 151 | return pointer.pointee 152 | } 153 | } 154 | 155 | extension JSON.Parser { 156 | 157 | mutating func skipWhitespace() throws { 158 | 159 | /// Returns whether a comment was skipped 160 | func skipComments() throws -> Bool { 161 | 162 | if hasPrefix(lineComment) { 163 | 164 | while let char = peek(), char != newline { 165 | 166 | pop() 167 | } 168 | return true 169 | } else if hasPrefix(blockCommentStart) { 170 | 171 | // don't be mislead by `/*/`. 172 | pop() // '/' 173 | pop() // '*' 174 | 175 | var depth: UInt = 1 176 | repeat { 177 | 178 | guard peek() != nil else { 179 | throw Error.Reason.unmatchedComment 180 | } 181 | 182 | if hasPrefix(blockCommentEnd) { 183 | 184 | depth -= 1 185 | } else if hasPrefix(blockCommentStart) { 186 | 187 | depth += 1 188 | } 189 | 190 | pop() 191 | } while depth > 0 192 | pop() // '/' 193 | return true 194 | } 195 | 196 | return false 197 | } 198 | 199 | while pointer != buffer.endAddress && pointer.pointee.isWhitespace { 200 | 201 | pop() 202 | } 203 | if allowComments { 204 | let wasComment = try skipComments() 205 | if wasComment { try skipWhitespace() } 206 | } 207 | } 208 | } 209 | 210 | extension JSON.Parser { 211 | 212 | /** 213 | - precondition: `pointer` is at the beginning of a literal 214 | - postcondition: `pointer` will be in the next non-`whiteSpace` position 215 | */ 216 | mutating func parseValue() throws -> JSON { 217 | 218 | assert(!pointer.pointee.isWhitespace) 219 | 220 | defer { _ = try? skipWhitespace() } 221 | switch peek() { 222 | case objectOpen?: 223 | 224 | let object = try parseObject() 225 | return object 226 | 227 | case arrayOpen?: 228 | 229 | let array = try parseArray() 230 | return array 231 | 232 | case quote?: 233 | 234 | let string = try parseString() 235 | return .string(string) 236 | 237 | case minus?, numbers?: 238 | 239 | let number = try parseNumber() 240 | return number 241 | 242 | case f?: 243 | 244 | pop() 245 | try assertFollowedBy(alse) 246 | return .bool(false) 247 | 248 | case t?: 249 | 250 | pop() 251 | try assertFollowedBy(rue) 252 | return .bool(true) 253 | 254 | case n?: 255 | 256 | pop() 257 | try assertFollowedBy(ull) 258 | return .null 259 | 260 | case slash? where allowComments: 261 | try skipWhitespace() 262 | return try parseValue() 263 | 264 | default: 265 | throw Error.Reason.invalidSyntax 266 | } 267 | } 268 | 269 | mutating func assertFollowedBy(_ chars: [UTF8.CodeUnit]) throws { 270 | 271 | for scalar in chars { 272 | guard scalar == pop() else { throw Error.Reason.invalidLiteral } 273 | } 274 | } 275 | 276 | mutating func parseObject() throws -> JSON { 277 | 278 | assert(peek() == objectOpen) 279 | pop() 280 | 281 | try skipWhitespace() 282 | 283 | guard peek() != objectClose else { 284 | pop() 285 | return .object([:]) 286 | } 287 | 288 | var tempDict: [String: JSON] = Dictionary(minimumCapacity: 6) 289 | var wasComma = false 290 | 291 | repeat { 292 | 293 | switch peek() { 294 | case comma?: 295 | 296 | guard !wasComma else { throw Error.Reason.trailingComma } 297 | 298 | wasComma = true 299 | pop() 300 | try skipWhitespace() 301 | 302 | case quote?: 303 | 304 | if tempDict.count > 0 && !wasComma { 305 | throw Error.Reason.expectedComma 306 | } 307 | 308 | let key = try parseString() 309 | try skipWhitespace() 310 | guard pop() == colon else { throw Error.Reason.expectedColon } 311 | try skipWhitespace() 312 | let value = try parseValue() 313 | wasComma = false 314 | 315 | switch value { 316 | case .null where omitNulls: 317 | break 318 | 319 | default: 320 | tempDict[key] = value 321 | } 322 | 323 | case objectClose?: 324 | 325 | guard !wasComma else { throw Error.Reason.trailingComma } 326 | 327 | pop() 328 | return .object(tempDict) 329 | 330 | default: 331 | throw Error.Reason.invalidSyntax 332 | } 333 | } while true 334 | } 335 | 336 | mutating func parseArray() throws -> JSON { 337 | 338 | assert(peek() == arrayOpen) 339 | pop() 340 | 341 | try skipWhitespace() 342 | 343 | // Saves the allocation of the tempArray 344 | guard peek() != arrayClose else { 345 | pop() 346 | return .array([]) 347 | } 348 | 349 | var tempArray: [JSON] = [] 350 | tempArray.reserveCapacity(6) 351 | 352 | var wasComma = false 353 | 354 | repeat { 355 | 356 | switch peek() { 357 | case comma?: 358 | 359 | guard !wasComma else { throw Error.Reason.invalidSyntax } 360 | guard tempArray.count > 0 else { throw Error.Reason.invalidSyntax } 361 | 362 | wasComma = true 363 | try skipComma() 364 | 365 | case arrayClose?: 366 | 367 | guard !wasComma else { throw Error.Reason.trailingComma } 368 | 369 | _ = pop() 370 | return .array(tempArray) 371 | 372 | case nil: 373 | throw Error.Reason.endOfStream 374 | 375 | default: 376 | 377 | if tempArray.count > 0 && !wasComma { 378 | throw Error.Reason.expectedComma 379 | } 380 | 381 | let value = try parseValue() 382 | try skipWhitespace() 383 | wasComma = false 384 | 385 | switch value { 386 | case .null where omitNulls: 387 | if peek() == comma { 388 | try skipComma() 389 | wasComma = true 390 | } 391 | 392 | default: 393 | tempArray.append(value) 394 | } 395 | } 396 | } while true 397 | } 398 | 399 | mutating func parseNumber() throws -> JSON { 400 | 401 | assert(numbers ~= peek()! || minus == peek()!) 402 | 403 | var seenExponent = false 404 | var seenDecimal = false 405 | 406 | let negative: Bool = { 407 | guard minus == peek() else { return false } 408 | pop() 409 | return true 410 | }() 411 | 412 | guard let next = peek(), numbers ~= next else { throw Error.Reason.invalidNumber } 413 | // Checks for leading zero's on numbers that are not '0' or '0.x' 414 | if next == zero { 415 | guard let following = peek(aheadBy: 1) else { 416 | pop() 417 | return .integer(0) 418 | } 419 | switch following { 420 | case decimal, e, E: break 421 | case _ where following.isTerminator: break 422 | default: throw Error.Reason.invalidNumber 423 | } 424 | } 425 | 426 | var significand: UInt64 = 0 427 | var mantisa: UInt64 = 0 428 | var divisor: Double = 10 429 | var exponent: UInt64 = 0 430 | var negativeExponent = false 431 | var didOverflow: Bool 432 | 433 | repeat { 434 | 435 | switch peek() { 436 | case numbers? where !seenDecimal && !seenExponent: 437 | 438 | (significand, didOverflow) = significand.multipliedReportingOverflow(by: 10) 439 | guard !didOverflow else { throw Error.Reason.numberOverflow } 440 | 441 | (significand, didOverflow) = significand.addingReportingOverflow(UInt64(pop() - zero)) 442 | guard !didOverflow else { throw Error.Reason.numberOverflow } 443 | 444 | case numbers? where seenDecimal && !seenExponent: 445 | 446 | divisor *= 10 447 | 448 | (mantisa, didOverflow) = mantisa.multipliedReportingOverflow(by: 10) 449 | guard !didOverflow else { throw Error.Reason.numberOverflow } 450 | 451 | (mantisa, didOverflow) = mantisa.addingReportingOverflow(UInt64(pop() - zero)) 452 | guard !didOverflow else { throw Error.Reason.numberOverflow } 453 | 454 | case numbers? where seenExponent: 455 | 456 | (exponent, didOverflow) = exponent.multipliedReportingOverflow(by: 10) 457 | guard !didOverflow else { throw Error.Reason.numberOverflow } 458 | 459 | (exponent, didOverflow) = exponent.addingReportingOverflow(UInt64(pop() - zero)) 460 | guard !didOverflow else { throw Error.Reason.numberOverflow } 461 | 462 | case decimal? where !seenExponent && !seenDecimal: 463 | 464 | pop() 465 | seenDecimal = true 466 | guard let next = peek(), numbers ~= next else { throw Error.Reason.invalidNumber } 467 | 468 | case E? where !seenExponent, 469 | e? where !seenExponent: 470 | 471 | pop() 472 | seenExponent = true 473 | 474 | if peek() == minus { 475 | 476 | negativeExponent = true 477 | pop() 478 | } else if peek() == plus { 479 | 480 | pop() 481 | } 482 | 483 | guard let next = peek(), numbers ~= next else { throw Error.Reason.invalidNumber } 484 | 485 | case let value? where value.isTerminator: 486 | fallthrough 487 | 488 | case nil: 489 | 490 | return try constructNumber( 491 | significand: significand, 492 | mantisa: seenDecimal ? mantisa : nil, 493 | exponent: seenExponent ? exponent : nil, 494 | divisor: divisor, 495 | negative: negative, 496 | negativeExponent: negativeExponent 497 | ) 498 | 499 | default: 500 | throw Error.Reason.invalidNumber 501 | } 502 | } while true 503 | } 504 | 505 | func constructNumber(significand: UInt64, mantisa: UInt64?, exponent: UInt64?, divisor: Double, negative: Bool, negativeExponent: Bool) throws -> JSON { 506 | 507 | if mantisa != nil || exponent != nil { 508 | var divisor = divisor 509 | 510 | divisor /= 10 511 | 512 | let number = Double(negative ? -1 : 1) * (Double(significand) + Double(mantisa ?? 0) / divisor) 513 | 514 | guard let exponent = exponent else { return .double(number) } 515 | return .double(Double(number) * pow(10, negativeExponent ? -Double(exponent) : Double(exponent))) 516 | } else { 517 | 518 | switch significand { 519 | case validUnsigned64BitInteger where !negative: 520 | return .integer(Int64(significand)) 521 | 522 | case UInt64(Int64.max) + 1 where negative: 523 | return .integer(Int64.min) 524 | 525 | case validUnsigned64BitInteger where negative: 526 | return .integer(-Int64(significand)) 527 | 528 | default: 529 | throw Error.Reason.numberOverflow 530 | } 531 | } 532 | } 533 | 534 | // TODO (vdka): refactor 535 | // TODO (vdka): option to _repair_ Unicode 536 | // NOTE(vdka): Not sure I ever will get to refactoring this, I just don't find Swift's String _comfortable_ to work with at a byte level. 537 | mutating func parseString() throws -> String { 538 | 539 | assert(peek() == quote) 540 | pop() 541 | 542 | var escaped = false 543 | stringBuffer.removeAll(keepingCapacity: true) 544 | 545 | repeat { 546 | 547 | guard let codeUnit = peek() else { throw Error.Reason.invalidEscape } 548 | pop() 549 | if codeUnit == backslash && !escaped { 550 | 551 | escaped = true 552 | } else if codeUnit == quote && !escaped { 553 | 554 | stringBuffer.append(0) 555 | return stringBuffer.withUnsafeBufferPointer { bufferPointer in 556 | return String(cString: unsafeBitCast(bufferPointer.baseAddress, to: UnsafePointer.self)) 557 | } 558 | } else if escaped { 559 | 560 | switch codeUnit { 561 | case r: 562 | stringBuffer.append(cr) 563 | 564 | case t: 565 | stringBuffer.append(tab) 566 | 567 | case n: 568 | stringBuffer.append(newline) 569 | 570 | case b: 571 | stringBuffer.append(backspace) 572 | 573 | case f: 574 | stringBuffer.append(formfeed) 575 | 576 | case quote: 577 | stringBuffer.append(quote) 578 | 579 | case slash: 580 | stringBuffer.append(slash) 581 | 582 | case backslash: 583 | stringBuffer.append(backslash) 584 | 585 | case u: 586 | let scalar = try parseUnicodeScalar() 587 | UTF8.encode(scalar, into: { stringBuffer.append($0) }) 588 | 589 | default: 590 | throw Error.Reason.invalidEscape 591 | } 592 | 593 | escaped = false 594 | 595 | } else if invalidUnicodeBytes.contains(codeUnit) || codeUnit == 0xC0 || codeUnit == 0xC1 { 596 | 597 | throw Error.Reason.invalidUnicode 598 | } else { 599 | 600 | stringBuffer.append(codeUnit) 601 | } 602 | } while true 603 | } 604 | } 605 | 606 | extension JSON.Parser { 607 | 608 | mutating func parseUnicodeEscape() throws -> UTF16.CodeUnit { 609 | 610 | var codeUnit: UInt16 = 0 611 | for _ in 0..<4 { 612 | let c = pop() 613 | codeUnit <<= 4 614 | switch c { 615 | case numbers: 616 | codeUnit += UInt16(c - 48) 617 | case alphaNumericLower: 618 | codeUnit += UInt16(c - 87) 619 | case alphaNumericUpper: 620 | codeUnit += UInt16(c - 55) 621 | default: 622 | throw Error.Reason.invalidEscape 623 | } 624 | } 625 | 626 | return codeUnit 627 | } 628 | 629 | mutating func parseUnicodeScalar() throws -> UnicodeScalar { 630 | 631 | // For multi scalar Unicodes eg. flags 632 | var buffer: [UTF16.CodeUnit] = [] 633 | 634 | let codeUnit = try parseUnicodeEscape() 635 | buffer.append(codeUnit) 636 | if UTF16.isLeadSurrogate(codeUnit) { 637 | guard pop() == backslash && pop() == u else { throw Error.Reason.invalidUnicode } 638 | let trailingSurrogate = try parseUnicodeEscape() 639 | guard UTF16.isTrailSurrogate(trailingSurrogate) else { throw Error.Reason.invalidUnicode } 640 | buffer.append(trailingSurrogate) 641 | } 642 | 643 | var gen = buffer.makeIterator() 644 | 645 | var utf = UTF16() 646 | 647 | switch utf.decode(&gen) { 648 | case .scalarValue(let scalar): 649 | return scalar 650 | 651 | case .emptyInput, .error: 652 | throw Error.Reason.invalidUnicode 653 | } 654 | } 655 | 656 | /// - Precondition: pointer will be on a comma character. 657 | mutating func skipComma() throws { 658 | assert(peek() == comma) 659 | pop() 660 | try skipWhitespace() 661 | } 662 | } 663 | 664 | extension JSON.Parser { 665 | 666 | public struct Error: Swift.Error, Equatable { 667 | 668 | public var byteOffset: Int 669 | 670 | public var reason: Reason 671 | 672 | public enum Reason: Swift.Error { 673 | 674 | case endOfStream 675 | case emptyStream 676 | case trailingComma 677 | case expectedComma 678 | case expectedColon 679 | case invalidEscape 680 | case invalidSyntax 681 | case invalidNumber 682 | case numberOverflow 683 | case invalidLiteral 684 | case invalidUnicode 685 | case fragmentedJson 686 | case unmatchedComment 687 | } 688 | 689 | public static func == (lhs: JSON.Parser.Error, rhs: JSON.Parser.Error) -> Bool { 690 | return lhs.byteOffset == rhs.byteOffset && lhs.reason == rhs.reason 691 | } 692 | } 693 | } 694 | 695 | // MARK: - Stdlib extensions 696 | 697 | extension UnsafeBufferPointer { 698 | 699 | var endAddress: UnsafePointer { 700 | 701 | return baseAddress!.advanced(by: endIndex) 702 | } 703 | } 704 | 705 | 706 | extension UTF8.CodeUnit { 707 | 708 | var isWhitespace: Bool { 709 | if self == space || self == tab || self == cr || self == newline || self == formfeed { 710 | return true 711 | } 712 | 713 | return false 714 | } 715 | 716 | var isTerminator: Bool { 717 | if self.isWhitespace || self == comma || self == objectClose || self == arrayClose { 718 | return true 719 | } 720 | 721 | return false 722 | } 723 | } 724 | -------------------------------------------------------------------------------- /Sources/JSON/JSONRepresentable.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// Used to declare that that a type can be represented as JSON 4 | public protocol JSONRepresentable { 5 | 6 | /* NOTE: This should be a throwing method. As if any of JSONRepresentable's fields are FloatingPoint.NaN or .infinity they 7 | cannot be represented as valid RFC conforming JSON. 8 | 9 | This isn't currently throwing because it is called by `*literalType` initializers in order to convert 10 | [JSONRepresentable] & [String: JSONRepresentable] 11 | */ 12 | 13 | /// Returns a `JSON` representation of `self` 14 | func encoded() -> JSON 15 | } 16 | 17 | 18 | // MARK: - JSON Conformance to JSONRepresentable 19 | 20 | extension JSON: JSONRepresentable { 21 | 22 | public init(_ value: JSONRepresentable) { 23 | self = value.encoded() 24 | } 25 | } 26 | 27 | 28 | // MARK: - Add `serialized` to `JSONRepresentable` 29 | 30 | extension JSONRepresentable { 31 | 32 | public func serialized(options: JSON.Serializer.Option = []) throws -> String { 33 | return try JSON.Serializer.serialize(self.encoded(), options: options) 34 | } 35 | } 36 | 37 | 38 | // NOTE: track http://www.openradar.me/23433955 39 | 40 | 41 | // MARK: - Add encoded to Optional JSONRepresentables 42 | 43 | extension Optional where Wrapped: JSONRepresentable { 44 | public func encoded() -> JSON { 45 | guard let `self` = self else { return JSON.null } 46 | return JSON(self) 47 | } 48 | } 49 | 50 | 51 | // MARK: - Add encoded to RawRepresentable JSONRepresentables 52 | 53 | extension RawRepresentable where RawValue: JSONRepresentable { 54 | 55 | public func encoded() -> JSON { 56 | return JSON(rawValue) 57 | } 58 | } 59 | 60 | 61 | // MARK: - Add encoded to Sequences of JSONRepresentable 62 | 63 | extension Sequence where Iterator.Element: JSONRepresentable { 64 | 65 | public func encoded() -> JSON { 66 | return .array(self.map({ $0.encoded() })) 67 | } 68 | } 69 | 70 | // MARK: - Add encoded to Sequences of [String: JSONRepresentable] 71 | 72 | extension Sequence where Iterator.Element == (key: String, value: JSONRepresentable) { 73 | 74 | public func encoded() -> JSON { 75 | var encoded: [String: JSON] = [:] 76 | for (key, value) in self { 77 | encoded[key] = value.encoded() 78 | } 79 | return .object(encoded) 80 | } 81 | } 82 | 83 | 84 | // MARK: - Bool Conformance to JSONRepresentable 85 | 86 | extension Bool: JSONRepresentable { 87 | 88 | public func encoded() -> JSON { 89 | return .bool(self) 90 | } 91 | } 92 | 93 | 94 | // MARK: - String Conformance to JSONRepresentable 95 | 96 | extension String: JSONRepresentable { 97 | 98 | public func encoded() -> JSON { 99 | return .string(self) 100 | } 101 | } 102 | 103 | 104 | // MARK: - FloatingPointTypes: JSONRepresentable 105 | 106 | extension Double: JSONRepresentable { 107 | 108 | public func encoded() -> JSON { 109 | return .double(self) 110 | } 111 | } 112 | 113 | extension Float: JSONRepresentable { 114 | 115 | public func encoded() -> JSON { 116 | return .double(Double(self)) 117 | } 118 | } 119 | 120 | 121 | // MARK: - IntegerTypes: JSONRepresentable 122 | 123 | // NOTE: This sucks. It is very repetitive and ugly, is there a possiblity of `extension IntegerType: JSONRepresentable` in the future? 124 | extension Int: JSONRepresentable { 125 | 126 | public func encoded() -> JSON { 127 | return .integer(Int64(self)) 128 | } 129 | } 130 | 131 | extension UInt8: JSONRepresentable { 132 | 133 | public func encoded() -> JSON { 134 | return .integer(Int64(self)) 135 | } 136 | } 137 | 138 | extension UInt16: JSONRepresentable { 139 | 140 | public func encoded() -> JSON { 141 | return .integer(Int64(self)) 142 | } 143 | } 144 | 145 | extension UInt32: JSONRepresentable { 146 | 147 | public func encoded() -> JSON { 148 | return .integer(Int64(self)) 149 | } 150 | } 151 | 152 | extension Int8: JSONRepresentable { 153 | 154 | public func encoded() -> JSON { 155 | return .integer(Int64(self)) 156 | } 157 | } 158 | 159 | extension Int16: JSONRepresentable { 160 | 161 | public func encoded() -> JSON { 162 | return .integer(Int64(self)) 163 | } 164 | } 165 | 166 | extension Int32: JSONRepresentable { 167 | 168 | public func encoded() -> JSON { 169 | return .integer(Int64(self)) 170 | } 171 | } 172 | 173 | extension Int64: JSONRepresentable { 174 | 175 | public func encoded() -> JSON { 176 | return .integer(self) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Sources/JSON/JSONSerializer.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | extension JSON { 4 | public struct Serializer { 5 | 6 | public struct Option: OptionSet { 7 | public init(rawValue: UInt8) { self.rawValue = rawValue } 8 | public let rawValue: UInt8 9 | 10 | /// Do not include nulls in the serialized output 11 | public static let omitNulls = Option(rawValue: 0b0001) 12 | 13 | /// Serialize with formatting for user readability 14 | public static let prettyPrint = Option(rawValue: 0b0010) 15 | 16 | /// will use windows style newlines for formatting. Boo. Implies `.prettyPrint` 17 | public static let windowsLineEndings = Option(rawValue: 0b0110) 18 | } 19 | 20 | init(json: JSON, options: Option = []) { 21 | self.omitNull = options.contains(.omitNulls) 22 | self.prettyPrint = options.contains(.prettyPrint) 23 | self.useWindowsLineEndings = options.contains(.windowsLineEndings) 24 | } 25 | 26 | let omitNull: Bool 27 | let prettyPrint: Bool 28 | let useWindowsLineEndings: Bool 29 | } 30 | } 31 | 32 | extension JSON.Serializer { 33 | public static func serialize(_ json: JSON, to stream: inout O, options: Option) throws { 34 | let writer = JSON.Serializer(json: json, options: options) 35 | try writer.writeValue(json, to: &stream) 36 | } 37 | 38 | public static func serialize(_ json: JSON, options: Option = []) throws -> String { 39 | var s = "" 40 | let writer = JSON.Serializer(json: json, options: options) 41 | try writer.writeValue(json, to: &s) 42 | return s 43 | } 44 | } 45 | 46 | extension JSON.Serializer { 47 | func writeValue(_ value: JSON, to stream: inout O, indentLevel: Int = 0) throws { 48 | switch value { 49 | case .array(let a): 50 | try writeArray(a, to: &stream, indentLevel: indentLevel) 51 | 52 | case .bool(let b): 53 | writeBool(b, to: &stream) 54 | 55 | case .double(let d): 56 | try writeDouble(d, to: &stream) 57 | 58 | case .integer(let i): 59 | writeInteger(i, to: &stream) 60 | 61 | case .null where !omitNull: 62 | writeNull(to: &stream) 63 | 64 | case .string(let s): 65 | writeString(s, to: &stream) 66 | 67 | case .object(let o): 68 | try writeObject(o, to: &stream, indentLevel: indentLevel) 69 | 70 | default: break 71 | } 72 | } 73 | } 74 | 75 | extension JSON.Serializer { 76 | func writeNewlineIfNeeded(to stream: inout O) { 77 | guard prettyPrint else { return } 78 | stream.write("\n") 79 | } 80 | 81 | func writeIndentIfNeeded(_ indentLevel: Int, to stream: inout O) { 82 | guard prettyPrint else { return } 83 | 84 | // TODO: Look into a more effective way of adding to a string. 85 | 86 | for _ in 0..(_ a: [JSON], to stream: inout O, indentLevel: Int = 0) throws { 95 | if a.isEmpty { 96 | stream.write("[]") 97 | return 98 | } 99 | 100 | stream.write("[") 101 | writeNewlineIfNeeded(to: &stream) 102 | var i = 0 103 | var nullsFound = 0 104 | for v in a { 105 | defer { i += 1 } 106 | if omitNull && v == .null { 107 | nullsFound += 1 108 | continue 109 | } 110 | if i != nullsFound { // check we have seen non null values 111 | stream.write(",") 112 | writeNewlineIfNeeded(to: &stream) 113 | } 114 | writeIndentIfNeeded(indentLevel + 1, to: &stream) 115 | try writeValue(v, to: &stream, indentLevel: indentLevel + 1) 116 | } 117 | writeNewlineIfNeeded(to: &stream) 118 | writeIndentIfNeeded(indentLevel, to: &stream) 119 | stream.write("]") 120 | } 121 | 122 | func writeObject(_ o: [String: JSON], to stream: inout O, indentLevel: Int = 0) throws { 123 | if o.isEmpty { 124 | stream.write("{}") 125 | return 126 | } 127 | 128 | stream.write("{") 129 | writeNewlineIfNeeded(to: &stream) 130 | var i = 0 131 | var nullsFound = 0 132 | for (key, value) in o { 133 | defer { i += 1 } 134 | if omitNull && value == .null { 135 | nullsFound += 1 136 | continue 137 | } 138 | if i != nullsFound { // check we have seen non null values 139 | stream.write(",") 140 | writeNewlineIfNeeded(to: &stream) 141 | } 142 | writeIndentIfNeeded(indentLevel + 1, to: &stream) 143 | writeString(key, to: &stream) 144 | stream.write(prettyPrint ? ": " : ":") 145 | try writeValue(value, to: &stream, indentLevel: indentLevel + 1) 146 | } 147 | writeNewlineIfNeeded(to: &stream) 148 | writeIndentIfNeeded(indentLevel, to: &stream) 149 | stream.write("}") 150 | } 151 | 152 | func writeBool(_ b: Bool, to stream: inout O) { 153 | switch b { 154 | case true: 155 | stream.write("true") 156 | 157 | case false: 158 | stream.write("false") 159 | } 160 | } 161 | 162 | func writeNull(to stream: inout O) { 163 | stream.write("null") 164 | } 165 | 166 | func writeInteger(_ i: Int64, to stream: inout O) { 167 | stream.write(i.description) 168 | } 169 | 170 | func writeDouble(_ d: Double, to stream: inout O) throws { 171 | guard d.isFinite else { throw JSON.Serializer.Error.invalidNumber } 172 | stream.write(d.description) 173 | } 174 | 175 | static let controlCharacters: ClosedRange = (0...0x1F) 176 | 177 | func writeString(_ s: String, to stream: inout O) { 178 | stream.write("\"") 179 | for char in s.unicodeScalars { 180 | 181 | switch char.value { 182 | case numericCast(quote): 183 | stream.write("\\\"") 184 | 185 | case numericCast(backslash): 186 | stream.write("\\\\") 187 | 188 | case JSON.Serializer.controlCharacters: 189 | 190 | // If possible escape in the I ima 191 | switch char.value { 192 | case numericCast(backspace): 193 | stream.write("\\b") 194 | 195 | case numericCast(formfeed): 196 | stream.write("\\f") 197 | 198 | case numericCast(newline): 199 | stream.write("\\n") 200 | 201 | case numericCast(cr): 202 | stream.write("\\r") 203 | 204 | case numericCast(tab): 205 | stream.write("\\t") 206 | 207 | default: 208 | stream.write("\\u") 209 | let str = String(char.value, radix: 16, uppercase: true) 210 | if str.count == 1 { 211 | 212 | stream.write("000\(str)") 213 | } else { 214 | 215 | stream.write("00\(str)") 216 | } 217 | } 218 | 219 | default: 220 | 221 | let character = Character(char) 222 | character.write(to: &stream) 223 | } 224 | } 225 | stream.write("\"") 226 | } 227 | } 228 | 229 | extension JSON.Serializer { 230 | public enum Error: String, Swift.Error { 231 | case invalidNumber 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Tests/JSONTests/AccessorTests.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | import XCTest 4 | import Foundation 5 | @testable import JSON 6 | 7 | class AccessorTests: XCTestCase { 8 | 9 | let json: JSON = 10 | [ 11 | "array": [1, 2, 3] as JSON, 12 | "object": ["Hello": "World", "Goodbye": "Brisbane"] as JSON, 13 | "intLiteral": 1, 14 | "intString": "1", 15 | "floatLiteral": 6.28, 16 | "floatString": "6.28", 17 | "string": "hello", 18 | "trueLiteral": true, 19 | "falseLiteral": false, 20 | "trueString": "true", 21 | "falseString": "false", 22 | "nullLiteral": JSON.null 23 | ] 24 | 25 | func testInts() { 26 | 27 | var value: Int 28 | do { 29 | 30 | value = try json.get("intLiteral") 31 | XCTAssert(value == 1) 32 | value = try json.get("intString") 33 | XCTAssert(value == 1) 34 | } catch { 35 | XCTFail("Failed to access a member: \(error)") 36 | } 37 | } 38 | 39 | func testFloatingPoints() { 40 | 41 | var value: Double 42 | do { 43 | 44 | value = try json.get("floatLiteral") 45 | XCTAssert(value == 6.28) 46 | value = try json.get("floatString") 47 | XCTAssert(value == 6.28) 48 | } catch { 49 | XCTFail("Failed to access a member: \(error)") 50 | } 51 | } 52 | 53 | func testBool() { 54 | 55 | var value: Bool 56 | do { 57 | 58 | value = try json.get("trueLiteral") 59 | XCTAssert(value == true) 60 | value = try json.get("trueString") 61 | XCTAssert(value == true) 62 | value = try json.get("falseLiteral") 63 | XCTAssert(value == false) 64 | value = try json.get("falseString") 65 | XCTAssert(value == false) 66 | } catch { 67 | XCTFail("Failed to access a member: \(error)") 68 | } 69 | } 70 | 71 | func testNull() { 72 | 73 | var value: Bool? 74 | do { 75 | 76 | value = try json.get("nullLiteral") 77 | XCTAssert(value == nil) 78 | value = try json.get("404 key not found") 79 | XCTAssert(value == nil) 80 | } catch { 81 | XCTFail("Failed to access a member: \(error)") 82 | } 83 | } 84 | 85 | func testDefaulting() { 86 | 87 | enum Color: String { case teal, unknown } 88 | 89 | let json: JSON = ["name": "Harry", "age": 38, "color": "teal"] 90 | 91 | do { 92 | var name: String 93 | 94 | name = try json.get("404", default: "vdka") 95 | XCTAssert(name == "vdka") 96 | 97 | name = try json.get("name", default: "Bob") 98 | XCTAssert(name == "Harry") 99 | 100 | name = try json.get("age", default: "Julia") 101 | XCTAssert(name == "Julia") 102 | 103 | var color: Color 104 | 105 | color = try json.get("color", default: Color.unknown) 106 | XCTAssert(color == .teal) 107 | 108 | color = try json.get("404", default: Color.unknown) 109 | XCTAssert(color == .unknown) 110 | 111 | } catch { 112 | XCTFail("An error occured: \(error)") 113 | } 114 | } 115 | 116 | func testIterator() { 117 | 118 | var values: [JSON] = [] 119 | 120 | for value in json["array"]! { 121 | values.append(value) 122 | } 123 | XCTAssert(values == [1, 2, 3] as [JSON]) 124 | values.removeAll() 125 | 126 | for value in json["intLiteral"]! { 127 | values.append(value) 128 | } 129 | XCTAssert(values == [1] as [JSON]) 130 | values.removeAll() 131 | 132 | for value in json["nullLiteral"]! { 133 | values.append(value) 134 | } 135 | XCTAssert(values == []) 136 | } 137 | } 138 | 139 | #if os(Linux) 140 | extension JSONTests: XCTestCaseProvider { 141 | var allTests : [(String, () throws -> Void)] { 142 | return [ 143 | ("testSerializeArray", testSerializeArray), 144 | ("testParse", testParse), 145 | ("testSanity", testSanity), 146 | ("testAccessors", testAccessors), 147 | ("testMutation", testMutation), 148 | ] 149 | } 150 | } 151 | #endif 152 | -------------------------------------------------------------------------------- /Tests/JSONTests/FixtureSupport.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | public func urlForFixture(_ name: String) -> URL { 5 | 6 | let parent = (#file).components(separatedBy: "/").dropLast().joined(separator: "/") 7 | let url = URL(string: "file://\(parent)/Fixtures/\(name).json")! 8 | print("Loading fixture from url \(url)") 9 | return url 10 | } 11 | 12 | public func loadFixture(_ name: String) -> [UInt8] { 13 | 14 | let url = urlForFixture(name) 15 | let data = Array(try! String(contentsOf: url).utf8) 16 | return data 17 | } 18 | 19 | public func loadFixtureData(_ name: String) -> Foundation.Data { 20 | 21 | let url = urlForFixture(name) 22 | let data = try! Foundation.Data(contentsOf: url) 23 | return data 24 | } 25 | -------------------------------------------------------------------------------- /Tests/JSONTests/ModelMapTests.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | import XCTest 4 | import Foundation 5 | @testable import JSON 6 | 7 | class ModelMappingTests: XCTestCase { 8 | 9 | let json: JSON = { 10 | let bytes = loadFixture("large") 11 | return try! JSON.Parser.parse(bytes) 12 | }() 13 | 14 | func testMapToModels() { 15 | 16 | guard let userJson = json.array?.first else { 17 | XCTFail() 18 | return 19 | } 20 | 21 | _ = try! User(json: userJson) 22 | } 23 | 24 | } 25 | 26 | #if os(Linux) 27 | extension JSONTests: XCTestCaseProvider { 28 | var allTests : [(String, () throws -> Void)] { 29 | return [ 30 | ("testSerializeArray", testSerializeArray), 31 | ("testParse", testParse), 32 | ("testSanity", testSanity), 33 | ("testAccessors", testAccessors), 34 | ("testMutation", testMutation), 35 | ] 36 | } 37 | } 38 | #endif 39 | 40 | enum Currency: String { case AUD, EUR, GBP, USD } 41 | 42 | struct Money { 43 | var minorUnits: Int 44 | var currency: Currency 45 | 46 | } 47 | 48 | struct Person { 49 | var name: String 50 | var age: Int 51 | var accountBalances: [Money] 52 | var petName: String? 53 | } 54 | 55 | extension Money: JSONConvertible { 56 | 57 | func encoded() -> JSON { 58 | return 59 | [ 60 | "minorUnits": minorUnits, 61 | "currencyCode": currency.encoded() 62 | ] 63 | } 64 | 65 | init(json: JSON) throws { 66 | self.minorUnits = try json.get("minorUnits") 67 | self.currency = try json.get("currencyCode") 68 | } 69 | } 70 | 71 | 72 | extension Person: JSONConvertible { 73 | 74 | func encoded() -> JSON { 75 | return 76 | [ 77 | "name": name, 78 | "age": age, 79 | "accountBalances": accountBalances.encoded() 80 | ] 81 | } 82 | 83 | init(json: JSON) throws { 84 | self.name = try json.get("name") 85 | self.age = try json.get("age") 86 | self.accountBalances = try json.get("accountBalances") 87 | self.petName = try json.get("petName") 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /Tests/JSONTests/ParserPerformance.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | import JSON 4 | 5 | fileprivate let n = 5 6 | 7 | class ParserBenchmarks: XCTestCase { 8 | 9 | 10 | func testParseLargeJson() { 11 | 12 | let data = loadFixture("large") 13 | 14 | measure { 15 | for _ in 0.. Void)] { 118 | return [ 119 | ("testParseLargeJson", testParseLargeJson), 120 | ("testParseLargeMinJson", testParseLargeMinJson), 121 | ("testParseLargeJson_Foundation", testParseLargeJson_Foundation), 122 | ("testParseLargeMinJson_Foundation", testParseLargeMinJson_Foundation), 123 | ] 124 | } 125 | } 126 | #endif 127 | -------------------------------------------------------------------------------- /Tests/JSONTests/ParserTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | @testable import JSON 4 | 5 | class ParsingTests: XCTestCase { 6 | 7 | func test_FailOnEmpty() { 8 | 9 | expect("", toThrowWithReason: .emptyStream) 10 | } 11 | 12 | func test_CompletelyWrong() { 13 | 14 | expect("", toThrowWithReason: .invalidSyntax) 15 | } 16 | 17 | func testExtraTokensThrow() { 18 | 19 | expect("{'hello':'world'} blah", toThrowWithReason: .invalidSyntax) 20 | } 21 | 22 | 23 | // MARK: - Null 24 | 25 | func testNullParses() { 26 | 27 | expect("null", toParseTo: .null) 28 | } 29 | 30 | func testNullThrowsOnMismatch() { 31 | 32 | expect("nall", toThrowWithReason: .invalidLiteral) 33 | } 34 | 35 | func testNullSkipInObject() { 36 | 37 | expect("{'key': null}", toParseTo: [:], withOptions: .omitNulls) 38 | } 39 | 40 | func testNullSkipInArray() { 41 | 42 | expect("['someString', true, null, 1]", toParseTo: ["someString", true, 1], withOptions: .omitNulls) 43 | } 44 | 45 | func testNullSkipFragment() { 46 | 47 | // not sure what to expect here. but so long as it's consistent. 48 | expect("null", toParseTo: .null, withOptions: [.omitNulls, .allowFragments]) 49 | } 50 | 51 | func testFragmentNormallyThrows() { 52 | 53 | expect("'frag out'", toThrowWithReason: .fragmentedJson, withOptions: []) 54 | } 55 | 56 | 57 | // MARK: - Bools 58 | 59 | func testTrueParses() { 60 | 61 | expect("true", toParseTo: true) 62 | } 63 | 64 | func testTrueThrowsOnMismatch() { 65 | 66 | expect("tRue", toThrowWithReason: .invalidLiteral) 67 | } 68 | 69 | func testFalseParses() { 70 | 71 | expect("false", toParseTo: false) 72 | } 73 | 74 | func testBoolean_False_Mismatch() { 75 | 76 | expect("fals ", toThrowWithReason: .invalidLiteral) 77 | } 78 | 79 | 80 | // MARK: - Arrays 81 | 82 | func testArray_JustComma() { 83 | 84 | expect("[,]", toThrowWithReason: .invalidSyntax) 85 | } 86 | 87 | func testArray_JustNull() { 88 | 89 | expect("[ null ]", toParseTo: [JSON.null]) 90 | } 91 | 92 | func testArray_ZeroBegining() { 93 | 94 | expect("[0, 1] ", toParseTo: [0, 1]) 95 | } 96 | 97 | func testArray_ZeroBeginingWithWhitespace() { 98 | 99 | expect("[0 , 1] ", toParseTo: [0, 1]) 100 | } 101 | 102 | func testArray_NullsBoolsNums_Normal_Minimal_RootParser() { 103 | 104 | expect("[null,true,false,12,-10,-24.3,18.2e9]", toParseTo: 105 | [JSON.null, true, false, 12, -10, -24.3, 18200000000.0] 106 | ) 107 | } 108 | 109 | func testArray_NullsBoolsNums_Normal_MuchWhitespace() { 110 | 111 | expect(" \t[\n null ,true, \n-12.3 , false\r\n]\n ", toParseTo: 112 | [JSON.null, true, -12.3, false] 113 | ) 114 | } 115 | 116 | func testArray_NullsAndBooleans_Bad_MissingEnd() { 117 | 118 | expect("[\n null ,true, \nfalse\r\n\n ", toThrowWithReason: .endOfStream) 119 | } 120 | 121 | func testArray_NullsAndBooleans_Bad_MissingComma() { 122 | 123 | expect("[\n null true, \nfalse\r\n]\n ", toThrowWithReason: .expectedComma) 124 | } 125 | 126 | func testArray_NullsAndBooleans_Bad_ExtraComma() { 127 | 128 | expect("[\n null , , true, \nfalse\r\n]\n ", toThrowWithReason: .invalidSyntax) 129 | } 130 | 131 | func testArray_NullsAndBooleans_Bad_TrailingComma() { 132 | 133 | expect("[\n null ,true, \nfalse\r\n, ]\n ", toThrowWithReason: .trailingComma) 134 | } 135 | 136 | 137 | // MARK: - Numbers 138 | 139 | func testNumber_Int_ZeroWithTrailingWhitespace() { 140 | 141 | expect("0 ", toParseTo: 0) 142 | } 143 | 144 | func testNumber_Int_Zero() { 145 | 146 | expect("0", toParseTo: 0) 147 | } 148 | 149 | func testNumber_Int_One() { 150 | 151 | expect("1", toParseTo: 1) 152 | } 153 | 154 | func testNumber_Int_Basic() { 155 | 156 | expect("24", toParseTo: 24) 157 | } 158 | 159 | func testNumber_IntMin() { 160 | 161 | expect(Int.min.description, toParseTo: .integer(Int64.min)) 162 | } 163 | 164 | func testNumber_IntMax() { 165 | 166 | expect(Int.max.description, toParseTo: .integer(Int64.max)) 167 | } 168 | 169 | func testNumber_Int_Negative() { 170 | 171 | expect("-32", toParseTo: -32) 172 | } 173 | 174 | func testNumber_Int_Garbled() { 175 | 176 | expect("42-4", toThrowWithReason: .invalidNumber) 177 | } 178 | 179 | func testNumber_Int_LeadingZero() { 180 | 181 | expect("007", toThrowWithReason: .invalidNumber) 182 | } 183 | 184 | func testNumber_Int_Overflow() { 185 | 186 | expect("9223372036854775808", toThrowWithReason: .numberOverflow) 187 | expect("18446744073709551616", toThrowWithReason: .numberOverflow) 188 | expect("18446744073709551616", toThrowWithReason: .numberOverflow) 189 | } 190 | 191 | 192 | func testNumber_Double_Overflow() { 193 | 194 | expect("18446744073709551616.0", toThrowWithReason: .numberOverflow) 195 | expect("1.18446744073709551616", toThrowWithReason: .numberOverflow) 196 | expect("1e18446744073709551616", toThrowWithReason: .numberOverflow) 197 | expect("184467440737095516106.0", toThrowWithReason: .numberOverflow) 198 | expect("1.184467440737095516106", toThrowWithReason: .numberOverflow) 199 | expect("1e184467440737095516106", toThrowWithReason: .numberOverflow) 200 | } 201 | 202 | func testNumber_Dbl_LeadingZero() { 203 | 204 | expect("006.123", toThrowWithReason: .invalidNumber) 205 | } 206 | 207 | func testNumber_Dbl_Basic() { 208 | 209 | expect("46.57", toParseTo: 46.57) 210 | } 211 | 212 | func testNumber_Dbl_ZeroSomething() { 213 | 214 | expect("0.98", toParseTo: 0.98) 215 | } 216 | 217 | func testNumber_Dbl_MinusZeroSomething() { 218 | 219 | expect("-0.98", toParseTo: -0.98) 220 | } 221 | 222 | func testNumber_Dbl_ThrowsOnMinus() { 223 | 224 | expect("-", toThrowWithReason: .invalidNumber) 225 | } 226 | 227 | func testNumber_Dbl_MinusDecimal() { 228 | 229 | expect("-.1", toThrowWithReason: .invalidNumber) 230 | } 231 | 232 | func testNumber_Dbl_Incomplete() { 233 | 234 | expect("24.", toThrowWithReason: .invalidNumber) 235 | } 236 | 237 | func testNumber_Dbl_Negative() { 238 | 239 | expect("-24.34", toParseTo: -24.34) 240 | } 241 | 242 | func testNumber_Dbl_Negative_WrongChar() { 243 | 244 | expect("-24.3a4", toThrowWithReason: .invalidNumber) 245 | } 246 | 247 | func testNumber_Dbl_Negative_TwoDecimalPoints() { 248 | 249 | expect("-24.3.4", toThrowWithReason: .invalidNumber) 250 | } 251 | 252 | func testNumber_Dbl_Negative_TwoMinuses() { 253 | 254 | expect("--24.34", toThrowWithReason: .invalidNumber) 255 | } 256 | 257 | // http://seriot.ch/parsing_json.html 258 | func testNumber_Double_ZeroExpOne() { 259 | 260 | expect("0e1", toParseTo: 0.0) 261 | } 262 | 263 | func testNumber_Double_Exp_Normal() { 264 | 265 | expect("-24.3245e2", toParseTo: -2432.45) 266 | } 267 | 268 | func testNumber_Double_Exp_Positive() { 269 | 270 | expect("-24.3245e+2", toParseTo: -2432.45) 271 | } 272 | 273 | // TODO (vdka): floating point accuracy 274 | // Potential to fix through using Darwin.C.pow but, isn't that a dependency? 275 | // Maybe reimplement C's gross lookup table pow method 276 | // http://opensource.apple.com/source/Libm/Libm-2026/Source/Intel/expf_logf_powf.c 277 | // http://opensource.apple.com/source/Libm/Libm-315/Source/ARM/powf.c 278 | // May be hard to do this fast and correct in pure swift. 279 | func testNumber_Double_Exp_Negative() { 280 | 281 | // FIXME (vdka): Fix floating point number types 282 | expect("-24.3245e-2", toParseTo: -24.3245e-2) 283 | } 284 | 285 | func testNumber_Double_ExactnessNoExponent() { 286 | 287 | expect("-123451123442342.12124234", toParseTo: -123451123442342.12124234) 288 | } 289 | 290 | func testNumber_Double_ExactnessWithExponent() { 291 | 292 | expect("-123456789.123456789e-150", toParseTo: -123456789.123456789e-150) 293 | } 294 | 295 | func testNumber_Double_Exp_NoFrac() { 296 | 297 | expect("24E2", toParseTo: 2400.0) 298 | } 299 | 300 | func testNumber_Double_Exp_TwoEs() { 301 | 302 | expect("-24.3245eE2", toThrowWithReason: .invalidNumber) 303 | } 304 | 305 | 306 | // MARK: - Strings & Unicode 307 | 308 | func testEscape_Solidus() { 309 | 310 | expect("'\\/'", toParseTo: "/") 311 | } 312 | 313 | func testLonelyReverseSolidus() { 314 | 315 | expect("'\\'", toThrowWithReason: .invalidEscape) 316 | } 317 | 318 | func testEscape_Unicode_Normal() { 319 | 320 | expect("'\\u0048'", toParseTo: "H") 321 | } 322 | 323 | func testEscape_Unicode_Invalid() { 324 | 325 | expect("'\\uD83d\\udQ24'", toThrowWithReason: .invalidEscape) 326 | } 327 | 328 | func testEscape_Unicode_Complex() { 329 | 330 | expect("'\\ud83d\\ude24'", toParseTo: "\u{1F624}") 331 | } 332 | 333 | func testEscape_Unicode_Complex_MixedCase() { 334 | 335 | expect("'\\uD83d\\udE24'", toParseTo: "\u{1F624}") 336 | } 337 | 338 | func testEscape_Unicode_InvalidUnicode_MissingDigit() { 339 | 340 | expect("'\\u048'", toThrowWithReason: .invalidEscape) 341 | } 342 | 343 | func testEscape_Unicode_InvalidUnicode_MissingAllDigits() { 344 | 345 | expect("'\\u'", toThrowWithReason: .invalidEscape) 346 | } 347 | 348 | func testString_Empty() { 349 | 350 | expect("''", toParseTo: "") 351 | } 352 | 353 | func testString_Normal() { 354 | 355 | expect("'hello world'", toParseTo: "hello world") 356 | } 357 | 358 | func testString_Normal_Backslashes() { 359 | 360 | // This looks insane and kinda is. The rule is the right side just halve, the left side quarter. 361 | expect("'C:\\\\\\\\share\\\\path\\\\file'", toParseTo: "C:\\\\share\\path\\file") 362 | } 363 | 364 | func testString_Normal_WhitespaceInside() { 365 | 366 | expect("'he \\r\\n l \\t l \\n o wo\\rrld '", toParseTo: "he \r\n l \t l \n o wo\rrld ") 367 | } 368 | 369 | func testString_StartEndWithSpaces() { 370 | 371 | expect("' hello world '", toParseTo: " hello world ") 372 | } 373 | 374 | // NOTE(vdka): This cannot be fixed until I find a better way to initialize strings 375 | func testString_Null() { 376 | 377 | expect("'\\u0000'", toParseTo: "\u{0000}") 378 | } 379 | 380 | func testString_Unicode_SimpleUnescaped() { 381 | 382 | expect("'€𝄞'", toParseTo: "€𝄞") 383 | } 384 | 385 | // NOTE(vdka): Swift changes the value if we encode 0xFF into a string. 386 | func testString_InvalidUnicodeByte() { 387 | 388 | let expectedError = JSON.Parser.Error.Reason.invalidUnicode 389 | do { 390 | 391 | let val = try JSON.Parser.parse([quote, 0xFF, quote]) 392 | 393 | XCTFail("expected to throw \(expectedError) but got \(val)") 394 | } catch let error as JSON.Parser.Error { 395 | 396 | XCTAssertEqual(error.reason, expectedError) 397 | } catch { 398 | 399 | XCTFail("expected to throw \(expectedError) but got a different error type!.") 400 | } 401 | } 402 | 403 | func testString_Unicode_NoTrailingSurrogate() { 404 | 405 | expect("'\\ud83d'", toThrowWithReason: .invalidUnicode) 406 | } 407 | 408 | func testString_Unicode_InvalidTrailingSurrogate() { 409 | 410 | expect("'\\ud83d\\u0040'", toThrowWithReason: .invalidUnicode) 411 | } 412 | 413 | func testString_Unicode_RegularChar() { 414 | 415 | expect("'hel\\u006co world'", toParseTo: "hello world") 416 | } 417 | 418 | func testString_Unicode_SpecialCharacter_CoolA() { 419 | 420 | expect("'h\\u01cdw'", toParseTo: "hǍw") 421 | } 422 | 423 | func testString_Unicode_SpecialCharacter_HebrewShin() { 424 | 425 | expect("'h\\u05e9w'", toParseTo: "hשw") 426 | } 427 | 428 | func testString_Unicode_SpecialCharacter_QuarterTo() { 429 | 430 | expect("'h\\u25d5w'", toParseTo: "h◕w") 431 | } 432 | 433 | func testString_Unicode_SpecialCharacter_EmojiSimple() { 434 | 435 | expect("'h\\ud83d\\ude3bw'", toParseTo: "h😻w") 436 | } 437 | 438 | func testString_Unicode_SpecialCharacter_EmojiComplex() { 439 | 440 | expect("'h\\ud83c\\udde8\\ud83c\\uddffw'", toParseTo: "h🇨🇿w") 441 | } 442 | 443 | func testString_SpecialCharacter_QuarterTo() { 444 | 445 | expect("'h◕w'", toParseTo: "h◕w") 446 | } 447 | 448 | func testString_SpecialCharacter_EmojiSimple() { 449 | 450 | expect("'h😻w'", toParseTo: "h😻w") 451 | } 452 | 453 | func testString_SpecialCharacter_EmojiComplex() { 454 | 455 | expect("'h🇨🇿w'", toParseTo: "h🇨🇿w") 456 | } 457 | 458 | func testString_BackspaceEscape() { 459 | 460 | let backspace = Character(UnicodeScalar(0x08)) 461 | 462 | expect("'\\b'", toParseTo: String(backspace).encoded()) 463 | } 464 | 465 | func testEscape_FormFeed() { 466 | 467 | let formfeed = Character(UnicodeScalar(0x0C)) 468 | 469 | expect("'\\f'", toParseTo: String(formfeed).encoded()) 470 | 471 | } 472 | 473 | func testString_ContainingEscapedQuotes() { 474 | 475 | expect("'\\\"\\\"'", toParseTo: "\"\"") 476 | } 477 | 478 | func testString_ContainingSlash() { 479 | 480 | expect("'http:\\/\\/example.com'", toParseTo: "http://example.com") 481 | } 482 | 483 | func testString_ContainingInvalidEscape() { 484 | 485 | expect("'\\a'", toThrowWithReason: .invalidEscape) 486 | } 487 | 488 | 489 | // MARK: - Objects 490 | 491 | func testObject_Empty() { 492 | 493 | expect("{}", toParseTo: [:]) 494 | } 495 | 496 | func testObject_JustComma() { 497 | 498 | expect("{,}", toThrowWithReason: .trailingComma) 499 | } 500 | 501 | func testObject_SyntaxError() { 502 | 503 | expect("{'hello': 'failure'; 'goodbye': true}", toThrowWithReason: .invalidSyntax) 504 | } 505 | 506 | func testObject_TrailingComma() { 507 | 508 | expect("{'someKey': true,,}", toThrowWithReason: .trailingComma) 509 | } 510 | 511 | func testObject_MissingComma() { 512 | 513 | expect("{'someKey': true 'someOther': false}", toThrowWithReason: .expectedComma) 514 | } 515 | 516 | func testObject_MissingColon() { 517 | 518 | expect("{'someKey' true}", toThrowWithReason: .expectedColon) 519 | } 520 | 521 | func testObject_Example1() { 522 | expect("{\t'hello': 'wor🇨🇿ld', \n\t 'val': 1234, 'many': [\n-12.32, null, 'yo'\r], 'emptyDict': {}, 'dict': {'arr':[]}, 'name': true}", toParseTo: 523 | [ 524 | "hello": "wor🇨🇿ld", 525 | "val": 1234, 526 | "many": [-12.32, JSON.null, "yo"] as JSON, 527 | "emptyDict": [:] as JSON, 528 | "dict": ["arr": [] as JSON] as JSON, 529 | "name": true 530 | ] 531 | ) 532 | } 533 | 534 | func testTrollBlockComment() { 535 | 536 | expect("/*/ {'key':'harry'}", toThrowWithReason: .unmatchedComment, withOptions: .allowComments) 537 | } 538 | 539 | func testLineComment_start() { 540 | 541 | expect("// This is a comment\n{'key':true}", toParseTo: ["key": true], withOptions: .allowComments) 542 | } 543 | 544 | func testLineComment_endWithNewline() { 545 | 546 | expect("// This is a comment\n{'key':true}", toParseTo: ["key": true], withOptions: .allowComments) 547 | expect("{'key':true}// This is a comment\n", toParseTo: ["key": true], withOptions: .allowComments) 548 | } 549 | 550 | func testLineComment_end() { 551 | 552 | expect("{'key':true}// This is a comment", toParseTo: ["key": true], withOptions: .allowComments) 553 | expect("{'key':true}\n// This is a comment", toParseTo: ["key": true], withOptions: .allowComments) 554 | } 555 | 556 | func testLineComment_withinRootObject() { 557 | 558 | expect("{\n'key':true,\n// commented!\n'key2':false\n}", toParseTo: ["key": true, "key2": false], withOptions: .allowComments) 559 | } 560 | 561 | func testBlockComment_start() { 562 | 563 | expect("/* This is a comment */{'key':true}", toParseTo: ["key": true], withOptions: .allowComments) 564 | } 565 | 566 | func testBlockComment_end() { 567 | 568 | expect("{'key':true}/* This is a comment */", toParseTo: ["key": true], withOptions: .allowComments) 569 | expect("{'key':true}\n/* This is a comment */", toParseTo: ["key": true], withOptions: .allowComments) 570 | } 571 | 572 | func testBlockCommentNested() { 573 | 574 | expect("[true]/* a /* b */ /* c */ d */", toParseTo: [true], withOptions: .allowComments) 575 | } 576 | 577 | func testBlockComment_withinRootObject() { 578 | 579 | expect("{'key':true,/* foo */'key2':false/* bar */}", toParseTo: ["key": true, "key2": false], withOptions: .allowComments) 580 | } 581 | 582 | func testDetailedError() { 583 | 584 | expect("0xbadf00d", toThrowAtByteOffset: 0, withReason: .invalidNumber) 585 | expect("false blah", toThrowAtByteOffset: 6, withReason: .invalidSyntax) 586 | } 587 | 588 | // - MARK: Smoke tests 589 | 590 | func testStringParsing() { 591 | 592 | let jsonString = "{'hello':'world'}".replacingOccurrences(of: "'", with: "\"") 593 | do { 594 | 595 | _ = try JSON.Parser.parse(jsonString) 596 | } catch { 597 | XCTFail("Parsing failed with \(error)") 598 | } 599 | } 600 | 601 | func testFoundationData() { 602 | 603 | let jsonString = "{'hello':'world'}".replacingOccurrences(of: "'", with: "\"") 604 | let data = Data(bytes: Array(jsonString.utf8)) 605 | do { 606 | 607 | _ = try JSON.Parser.parse(data) 608 | } catch { 609 | XCTFail("Parsing failed with \(error)") 610 | } 611 | } 612 | 613 | func testUnsafeBufferPointer() { 614 | 615 | let jsonString = "{'hello':'world'}".replacingOccurrences(of: "'", with: "\"") 616 | let data = Array(jsonString.utf8) 617 | 618 | data.withUnsafeBufferPointer { buffer in 619 | 620 | do { 621 | 622 | _ = try JSON.Parser.parse(data) 623 | } catch { 624 | XCTFail("Parsing failed with \(error)") 625 | } 626 | } 627 | } 628 | } 629 | 630 | extension ParsingTests { 631 | 632 | func expect(_ input: String, toThrowWithReason expectedError: JSON.Parser.Error.Reason, withOptions options: JSON.Parser.Option = [.allowFragments], 633 | file: StaticString = #file, line: UInt = #line) { 634 | 635 | let input = input.replacingOccurrences(of: "'", with: "\"") 636 | 637 | let data = Array(input.utf8) 638 | 639 | do { 640 | 641 | let val = try JSON.Parser.parse(data, options: options) 642 | 643 | XCTFail("expected to throw \(expectedError) but got \(val)", file: file, line: line) 644 | } catch let error as JSON.Parser.Error { 645 | 646 | XCTAssertEqual(error.reason, expectedError, file: file, line: line) 647 | } catch { 648 | 649 | XCTFail("expected to throw \(expectedError) but got a different error type!.") 650 | } 651 | } 652 | 653 | func expect(_ input: String, toThrowAtByteOffset expectedOffset: Int, withReason expectedReason: JSON.Parser.Error.Reason, 654 | withOptions options: JSON.Parser.Option = [.allowFragments], 655 | file: StaticString = #file, line: UInt = #line) { 656 | 657 | let input = input.replacingOccurrences(of: "'", with: "\"") 658 | 659 | let data = Array(input.utf8) 660 | 661 | do { 662 | 663 | let val = try JSON.Parser.parse(data, options: options) 664 | 665 | XCTFail("expected to throw with reason \(expectedReason) but got \(val)", file: file, line: line) 666 | } catch let error as JSON.Parser.Error { 667 | 668 | 669 | XCTAssertEqual(error.byteOffset, expectedOffset, file: file, line: line) 670 | } catch { 671 | 672 | XCTFail("expected to throw JSON.Parser.Error but got a different error type!.") 673 | } 674 | } 675 | 676 | func expect(_ input: String, toParseTo expected: JSON, withOptions options: JSON.Parser.Option = [.allowFragments], 677 | file: StaticString = #file, line: UInt = #line) { 678 | 679 | let input = input.replacingOccurrences(of: "'", with: "\"") 680 | 681 | let data = Array(input.utf8) 682 | 683 | do { 684 | let output = try JSON.Parser.parse(data, options: options) 685 | 686 | XCTAssertEqual(output, expected, file: file, line: line) 687 | } catch { 688 | XCTFail("\(error)", file: file, line: line) 689 | } 690 | } 691 | } 692 | 693 | #if os(Linux) 694 | extension ParsingTests { 695 | static var allTests : [(String, (ParsingTests) -> () throws -> Void)] { 696 | return [ 697 | ("test_FailOnEmpty", testPrepareForReading_FailOnEmpty), 698 | ("testExtraTokensThrow", testExtraTokensThrow), 699 | ("testNullParses", testNullParses), 700 | ("testNullThrowsOnMismatch", testNullThrowsOnMismatch), 701 | ("testTrueParses", testTrueParses), 702 | ("testTrueThrowsOnMismatch", testTrueThrowsOnMismatch), 703 | ("testFalseParses", testFalseParses), 704 | ("testBoolean_False_Mismatch", testBoolean_False_Mismatch), 705 | ("testArray_NullsBoolsNums_Normal_Minimal_RootParser", testArray_NullsBoolsNums_Normal_Minimal_RootParser), 706 | ("testArray_NullsBoolsNums_Normal_MuchWhitespace", testArray_NullsBoolsNums_Normal_MuchWhitespace), 707 | ("testArray_NullsAndBooleans_Bad_MissingEnd", testArray_NullsAndBooleans_Bad_MissingEnd), 708 | ("testArray_NullsAndBooleans_Bad_MissingComma", testArray_NullsAndBooleans_Bad_MissingComma), 709 | ("testArray_NullsAndBooleans_Bad_ExtraComma", testArray_NullsAndBooleans_Bad_ExtraComma), 710 | ("testArray_NullsAndBooleans_Bad_TrailingComma", testArray_NullsAndBooleans_Bad_TrailingComma), 711 | ("testNumber_Int_Zero", testNumber_Int_Zero), 712 | ("testNumber_Int_One", testNumber_Int_One), 713 | ("testNumber_Int_Basic", testNumber_Int_Basic), 714 | ("testNumber_Int_Negative", testNumber_Int_Negative), 715 | ("testNumber_Dbl_Basic", testNumber_Dbl_Basic), 716 | ("testNumber_Dbl_ZeroSomething", testNumber_Dbl_ZeroSomething), 717 | ("testNumber_Dbl_MinusZeroSomething", testNumber_Dbl_MinusZeroSomething), 718 | ("testNumber_Dbl_Incomplete", testNumber_Dbl_Incomplete), 719 | ("testNumber_Dbl_Negative", testNumber_Dbl_Negative), 720 | ("testNumber_Dbl_Negative_WrongChar", testNumber_Dbl_Negative_WrongChar), 721 | ("testNumber_Dbl_Negative_TwoDecimalPoints", testNumber_Dbl_Negative_TwoDecimalPoints), 722 | ("testNumber_Dbl_Negative_TwoMinuses", testNumber_Dbl_Negative_TwoMinuses), 723 | ("testNumber_Double_Exp_Normal", testNumber_Double_Exp_Normal), 724 | ("testNumber_Double_Exp_Positive", testNumber_Double_Exp_Positive), 725 | ("testNumber_Double_Exp_Negative", testNumber_Double_Exp_Negative), 726 | ("testNumber_Double_Exp_NoFrac", testNumber_Double_Exp_NoFrac), 727 | ("testNumber_Double_Exp_TwoEs", testNumber_Double_Exp_TwoEs), 728 | ("testEscape_Unicode_Normal", testEscape_Unicode_Normal), 729 | ("testEscape_Unicode_InvalidUnicode_MissingDigit", testEscape_Unicode_InvalidUnicode_MissingDigit), 730 | ("testEscape_Unicode_InvalidUnicode_MissingAllDigits", testEscape_Unicode_InvalidUnicode_MissingAllDigits), 731 | ("testString_Empty", testString_Empty), 732 | ("testString_Normal", testString_Normal), 733 | ("testString_Normal_WhitespaceInside", testString_Normal_WhitespaceInside), 734 | ("testString_StartEndWithSpaces", testString_StartEndWithSpaces), 735 | ("testString_Unicode_RegularChar", testString_Unicode_RegularChar), 736 | ("testString_Unicode_SpecialCharacter_CoolA", testString_Unicode_SpecialCharacter_CoolA), 737 | ("testString_Unicode_SpecialCharacter_HebrewShin", testString_Unicode_SpecialCharacter_HebrewShin), 738 | ("testString_Unicode_SpecialCharacter_QuarterTo", testString_Unicode_SpecialCharacter_QuarterTo), 739 | ("testString_Unicode_SpecialCharacter_EmojiSimple", testString_Unicode_SpecialCharacter_EmojiSimple), 740 | ("testString_Unicode_SpecialCharacter_EmojiComplex", testString_Unicode_SpecialCharacter_EmojiComplex), 741 | ("testString_SpecialCharacter_QuarterTo", testString_SpecialCharacter_QuarterTo), 742 | ("testString_SpecialCharacter_EmojiSimple", testString_SpecialCharacter_EmojiSimple), 743 | ("testString_SpecialCharacter_EmojiComplex", testString_SpecialCharacter_EmojiComplex), 744 | ("testObject_Empty", testObject_Empty), 745 | ("testObject_Example1", testObject_Example1), 746 | ("testDetailedError", testDetailedError), 747 | ] 748 | } 749 | } 750 | #endif 751 | -------------------------------------------------------------------------------- /Tests/JSONTests/PublicAPITests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | import Foundation 4 | @testable import JSON 5 | 6 | class JSONTests: XCTestCase { 7 | 8 | let json: JSON = 9 | [ 10 | "name": "Bob", "age": 51, "nice": true, "hairy": false, "height": 182.43, 11 | "pets": ["Harry", "Peter"] as JSON, 12 | "roles": [ 13 | ["title": "Developer", "timeSpent": 2] as JSON, 14 | ["title": "Student", "timeSpent": 3] as JSON 15 | ] as JSON 16 | ] 17 | 18 | func testSanity() { 19 | 20 | func assertSymmetricJSONConversion(_ json: JSON, options: JSON.Serializer.Option = [], line: UInt = #line) { 21 | do { 22 | let json2 = try JSON.Parser.parse(json.serialized(options: options)) 23 | XCTAssertEqual(json, json2, line: line) 24 | } catch { 25 | XCTFail(line: line) 26 | } 27 | } 28 | 29 | assertSymmetricJSONConversion([1, [2, 3] as JSON]) 30 | 31 | assertSymmetricJSONConversion([1, 25]) 32 | assertSymmetricJSONConversion(["key": "value", "key2": 2]) // TODO: Investigate 33 | 34 | assertSymmetricJSONConversion([]) 35 | assertSymmetricJSONConversion([], options: [.prettyPrint]) 36 | assertSymmetricJSONConversion([:]) 37 | assertSymmetricJSONConversion([:], options: [.prettyPrint]) 38 | assertSymmetricJSONConversion([[:] as JSON, [:] as JSON]) 39 | 40 | assertSymmetricJSONConversion(json) 41 | assertSymmetricJSONConversion(["symbols": "œ∑´®†¥¨ˆøπ“‘«åß∂ƒ©˙∆˚¬…æΩ≈ç√∫˜µ≤≥÷Œ„´‰ˇÁ¨ˆØ∏”’»ÅÍÎÏ˝ÓÔÒÚÆ¸˛Ç◊ı˜Â¯˘¿"]) 42 | assertSymmetricJSONConversion(["emojis": "👍🏽🍉🇦🇺"]) 43 | assertSymmetricJSONConversion(["👍🏽", "🍉", "🇦🇺"]) 44 | 45 | } 46 | 47 | func testAccessors() { 48 | XCTAssertEqual(json["name"].string, "Bob") 49 | XCTAssertEqual(json["age"].int, 51) 50 | XCTAssertEqual(json["nice"].bool, true) 51 | XCTAssertEqual(json["hairy"].bool, false) 52 | XCTAssertEqual(json["height"].double, 182.43) 53 | XCTAssertEqual(json["pets"].array?.flatMap({ $0.string }) ?? [], ["Harry", "Peter"]) 54 | XCTAssertEqual(json["pets"][0].string, "Harry") 55 | XCTAssertEqual(json["pets"][1].string, "Peter") 56 | XCTAssertEqual(json["roles"][0]["title"].string, "Developer") 57 | XCTAssertEqual(json["roles"][0]["timeSpent"].int, 2) 58 | XCTAssertEqual(json["roles"][1]["title"].string, "Student") 59 | XCTAssertEqual(json["roles"][1]["timeSpent"].int, 3) 60 | XCTAssertEqual(json["roles"][0].object!, ["title": .string("Developer"), "timeSpent": .integer(2)]) 61 | XCTAssertEqual(json["roles"][1].object!, ["title": .string("Student"), "timeSpent": .integer(3)]) 62 | 63 | XCTAssertEqual(json["name"].int, nil) 64 | XCTAssertEqual(json["name"].bool, nil) 65 | XCTAssertEqual(json["name"].int64, nil) 66 | XCTAssertEqual(json["name"].double, nil) 67 | XCTAssertEqual(json["roles"][1000], nil) 68 | XCTAssertEqual(json[0], nil) 69 | } 70 | 71 | func testMutation() { 72 | var json: JSON = ["height": 1.90, "array": [1, 2, 3] as JSON] 73 | XCTAssertEqual(json["height"].double, 1.90) 74 | json["height"] = 1.91 75 | XCTAssertEqual(json["height"].double, 1.91) 76 | 77 | XCTAssertEqual(json["array"][0], 1) 78 | json["array"][0] = 4 79 | XCTAssertEqual(json["array"], [4, 2, 3]) 80 | } 81 | } 82 | 83 | #if os(Linux) 84 | extension JSONTests: XCTestCaseProvider { 85 | var allTests : [(String, () throws -> Void)] { 86 | return [ 87 | ("testSerializeArray", testSerializeArray), 88 | ("testParse", testParse), 89 | ("testSanity", testSanity), 90 | ("testAccessors", testAccessors), 91 | ("testMutation", testMutation), 92 | ] 93 | } 94 | } 95 | #endif 96 | -------------------------------------------------------------------------------- /Tests/JSONTests/SerializerPerformance.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | import JSON 4 | 5 | let largeJsonData = loadFixture("large") 6 | let largeJsonFoundationData = loadFixtureData("large") 7 | 8 | let largeJson = try! JSON.Parser.parse(largeJsonData) 9 | 10 | class SerializerBenchmarks: XCTestCase { 11 | 12 | override func setUp() { 13 | super.setUp() 14 | do { 15 | _ = try JSON.Serializer.serialize(largeJson) 16 | } catch {} 17 | } 18 | 19 | func testSerializerPerformance() { 20 | 21 | measure { 22 | do { 23 | _ = try JSON.Serializer.serialize(largeJson) 24 | } catch { XCTFail() } 25 | } 26 | } 27 | 28 | func testSerializerPrettyPrintedPerformance() { 29 | 30 | measure { 31 | do { 32 | _ = try JSON.Serializer.serialize(largeJson, options: [.prettyPrint]) 33 | } catch { XCTFail() } 34 | } 35 | } 36 | 37 | func testSerializerFoundationPerformance() { 38 | 39 | let nsJson = try! JSONSerialization.jsonObject(with: largeJsonFoundationData, options: []) 40 | 41 | measure { 42 | do { 43 | try JSONSerialization.data(withJSONObject: nsJson, options: []) 44 | } catch { XCTFail() } 45 | } 46 | } 47 | 48 | func testSerializerFoundationPrettyPrintedPerformance() { 49 | 50 | let nsJson = try! JSONSerialization.jsonObject(with: largeJsonFoundationData, options: []) 51 | 52 | measure { 53 | do { 54 | try JSONSerialization.data(withJSONObject: nsJson, options: .prettyPrinted) 55 | } catch { XCTFail() } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/JSONTests/SerializerTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | @testable import JSON 4 | 5 | class SerializerTests: XCTestCase { 6 | 7 | func testBools() { 8 | 9 | expect(true, toSerializeTo: "true") 10 | expect(false, toSerializeTo: "false") 11 | } 12 | 13 | func testNull() { 14 | 15 | expect(.null, toSerializeTo: "null") 16 | expect(.null, toSerializeTo: "", withOptions: .omitNulls) 17 | } 18 | 19 | func testSerializeNumber() { 20 | 21 | expect(1, toSerializeTo: "1") 22 | expect(-1, toSerializeTo: "-1") 23 | expect(0.1, toSerializeTo: "0.1") 24 | expect(-0.1, toSerializeTo: "-0.1") 25 | expect(1e100, toSerializeTo: "1e+100") 26 | expect(-1e100, toSerializeTo: "-1e+100") 27 | expect(123456.789, toSerializeTo: "123456.789") 28 | expect(-123456.789, toSerializeTo: "-123456.789") 29 | } 30 | 31 | func testEmptyString() { 32 | 33 | expect("", toSerializeTo: "''") 34 | } 35 | 36 | func testSimpleString() { 37 | 38 | expect("simple", toSerializeTo: "'simple'") 39 | } 40 | 41 | func testEscapeReverseSolidusString() { 42 | 43 | expect("\\", toSerializeTo: "'\\\\'") 44 | } 45 | 46 | func testEscapeQuoteString() { 47 | 48 | expect("\"", toSerializeTo: "'\\\"'") 49 | } 50 | 51 | func testFlagUnicodeString() { 52 | 53 | expect("🇦🇺", toSerializeTo: "'🇦🇺'") 54 | } 55 | 56 | func testEmptyObject() { 57 | 58 | expect([:], toSerializeTo: "{}") 59 | } 60 | 61 | func testEmptyObjectPretty() { 62 | 63 | expect([:], toSerializeTo: "{}", withOptions: .prettyPrint) 64 | } 65 | 66 | func testSinglePairObject() { 67 | 68 | expect(["key": "value"], toSerializeTo: "{'key':'value'}") 69 | } 70 | 71 | func testSinglePairObjectPretty() { 72 | 73 | expect(["key": "value"], toSerializeTo: "{\n 'key': 'value'\n}", withOptions: .prettyPrint) 74 | } 75 | 76 | func testObjectNullValue() { 77 | 78 | expect(["hello": JSON.null], toSerializeTo: "{'hello':null}") 79 | } 80 | 81 | func testObjectNullValueOmitNulls() { 82 | 83 | expect(["hello": JSON.null, "key": true], toSerializeTo: "{'key':true}", withOptions: .omitNulls) 84 | } 85 | 86 | func testObjectNullValueOmitNullsPretty() { 87 | 88 | expect(["hello": JSON.null, "key": true], toSerializeTo: "{\n 'key': true\n}", withOptions: [.omitNulls, .prettyPrint]) 89 | } 90 | 91 | // NOTE(vdka): This isn't likely worth fixing. 92 | func testObjectSingleNullValueOmitNullsPretty() { 93 | 94 | expect(["key": JSON.null], toSerializeTo: "{\n\n}", withOptions: [.omitNulls, .prettyPrint]) 95 | } 96 | 97 | func testEmptyArray() { 98 | 99 | expect([], toSerializeTo: "[]") 100 | } 101 | 102 | func testEmptyArrayPretty() { 103 | 104 | expect([], toSerializeTo: "[]", withOptions: .prettyPrint) 105 | } 106 | 107 | func testArrayNullValueOmitNulls() { 108 | 109 | expect([true, JSON.null, false], toSerializeTo: "[true,false]", withOptions: .omitNulls) 110 | } 111 | 112 | func testArraySingleNullValueOmitNulls() { 113 | 114 | expect([JSON.null], toSerializeTo: "[]", withOptions: .omitNulls) 115 | } 116 | 117 | func testArraySingleNullValueOmitNullsPretty() { 118 | 119 | expect([JSON.null], toSerializeTo: "[\n\n]", withOptions: [.omitNulls, .prettyPrint]) 120 | } 121 | 122 | func testSingleElementArray() { 123 | 124 | expect(["value"], toSerializeTo: "['value']") 125 | } 126 | 127 | func testSingleElementArrayPretty() { 128 | 129 | expect(["value"], toSerializeTo: "[\n 'value'\n]", withOptions: .prettyPrint) 130 | } 131 | 132 | func testArrayNested() { 133 | 134 | expect([true, [false, [JSON.null] as JSON] as JSON], toSerializeTo: "[true,[false,[null]]]") 135 | } 136 | 137 | func testObjectNested() { 138 | 139 | expect(["a": ["b": ["c": true] as JSON] as JSON], toSerializeTo: "{'a':{'b':{'c':true}}}") 140 | } 141 | 142 | func testNestedObjectArray() { 143 | 144 | expect([["a": true] as JSON, ["b": [false] as JSON] as JSON], toSerializeTo: "[{'a':true},{'b':[false]}]") 145 | } 146 | 147 | func testEscapeControlString() { 148 | let pairs: [(String, String)] = 149 | [ 150 | ("\u{00}", "'\\u0000'"), 151 | ("\u{01}", "'\\u0001'"), 152 | ("\u{02}", "'\\u0002'"), 153 | ("\u{03}", "'\\u0003'"), 154 | ("\u{04}", "'\\u0004'"), 155 | ("\u{05}", "'\\u0005'"), 156 | ("\u{06}", "'\\u0006'"), 157 | ("\u{07}", "'\\u0007'"), 158 | ("\u{08}", "'\\b'"), 159 | ("\u{09}", "'\\t'"), 160 | ("\u{0A}", "'\\n'"), 161 | ("\u{0B}", "'\\u000B'"), 162 | ("\u{0C}", "'\\f'"), 163 | ("\u{0D}", "'\\r'"), 164 | ("\u{0E}", "'\\u000E'"), 165 | ("\u{0F}", "'\\u000F'"), 166 | ("\u{10}", "'\\u0010'"), 167 | ("\u{11}", "'\\u0011'"), 168 | ("\u{12}", "'\\u0012'"), 169 | ("\u{13}", "'\\u0013'"), 170 | ("\u{14}", "'\\u0014'"), 171 | ("\u{15}", "'\\u0015'"), 172 | ("\u{16}", "'\\u0016'"), 173 | ("\u{17}", "'\\u0017'"), 174 | ("\u{18}", "'\\u0018'"), 175 | ("\u{19}", "'\\u0019'"), 176 | ("\u{1A}", "'\\u001A'"), 177 | ("\u{1B}", "'\\u001B'"), 178 | ("\u{1C}", "'\\u001C'"), 179 | ("\u{1D}", "'\\u001D'"), 180 | ("\u{1E}", "'\\u001E'"), 181 | ("\u{1F}", "'\\u001F'") 182 | ] 183 | 184 | for (given, expected) in pairs { 185 | 186 | expect(given.encoded(), toSerializeTo: expected) 187 | } 188 | } 189 | } 190 | 191 | extension SerializerTests { 192 | 193 | func expect(_ input: JSON, toSerializeTo expected: String, withOptions options: JSON.Serializer.Option = [], 194 | file: StaticString = #file, line: UInt = #line) { 195 | 196 | let expected = expected.replacingOccurrences(of: "'", with: "\"") 197 | 198 | do { 199 | 200 | let result = try JSON.Serializer.serialize(input, options: options) 201 | XCTAssertEqual(result, expected, file: file, line: line) 202 | } catch { 203 | XCTFail("\(error)", file: file, line: line) 204 | } 205 | } 206 | 207 | func expect(_ input: JSON, toThrow expectedError: JSON.Serializer.Error, withOptions options: JSON.Serializer.Option = [], 208 | file: StaticString = #file, line: UInt = #line) { 209 | 210 | do { 211 | 212 | _ = try JSON.Serializer.serialize(input, options: options) 213 | } catch let error as JSON.Serializer.Error { 214 | 215 | XCTAssertEqual(error, expectedError, file: file, line: line) 216 | } catch { 217 | 218 | XCTFail("expected to throw \(expectedError) but got a different error type!.") 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /Tests/JSONTests/UserModel.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | import JSON 4 | 5 | // The model that the JSON in large.json in Fixtures models. 6 | 7 | public struct User { 8 | public let id: String 9 | public let index: Int 10 | public let guid: String 11 | public let isActive: Bool 12 | public let balance: String 13 | public let picture: String 14 | public let age: Int 15 | public let eyeColor: Color 16 | public let name: String 17 | public let gender: Gender 18 | public let company: String 19 | public let email: String 20 | public let phone: String 21 | public let address: String 22 | public let about: String 23 | public let registered: String 24 | public let latitude: Double 25 | public let longitude: Double 26 | public let tags: [String] 27 | public let friends: [Friend] 28 | public let greeting: String 29 | public let favoriteFruit: String 30 | 31 | public enum Color: String { 32 | case red 33 | case green 34 | case blue 35 | case brown 36 | } 37 | 38 | public enum Gender: String { 39 | case male 40 | case female 41 | } 42 | 43 | public struct Friend { 44 | public let id: Int 45 | public let name: String 46 | } 47 | } 48 | 49 | // MARK: - vdka/json 50 | 51 | extension User.Friend: JSONInitializable { 52 | 53 | public init(json: JSON) throws { 54 | self.id = try json.get("id") 55 | self.name = try json.get("name") 56 | } 57 | } 58 | 59 | extension User: JSONInitializable { 60 | 61 | public init(json: JSON) throws { 62 | self.id = try json.get("_id") 63 | self.index = try json.get("index") 64 | self.guid = try json.get("guid") 65 | self.isActive = try json.get("isActive") 66 | self.balance = try json.get("balance") 67 | self.picture = try json.get("picture") 68 | self.age = try json.get("age") 69 | self.eyeColor = try json.get("eyeColor") 70 | self.name = try json.get("name") 71 | self.gender = try json.get("gender") 72 | self.company = try json.get("company") 73 | self.email = try json.get("email") 74 | self.phone = try json.get("phone") 75 | self.address = try json.get("address") 76 | self.about = try json.get("about") 77 | self.registered = try json.get("registered") 78 | self.latitude = try json.get("latitude") 79 | self.longitude = try json.get("longitude") 80 | self.tags = try json.get("tags") 81 | self.friends = try json.get("friends") 82 | self.greeting = try json.get("greeting") 83 | self.favoriteFruit = try json.get("favoriteFruit") 84 | } 85 | } 86 | 87 | 88 | // MARK: - Foundation 89 | 90 | enum FoundationJSONError: Error { 91 | case typeMismatch 92 | } 93 | 94 | extension User.Friend { 95 | 96 | public init(foundationJSON json: Any) throws { 97 | guard 98 | let json = json as? [String: Any], 99 | let id = json["id"] as? Int, 100 | let name = json["name"] as? String 101 | else { throw FoundationJSONError.typeMismatch } 102 | self.id = id 103 | self.name = name 104 | } 105 | } 106 | 107 | extension User { 108 | 109 | public init(foundationJSON json: Any) throws { 110 | guard 111 | let json = json as? [String: Any], 112 | let id = json["_id"] as? String, 113 | let index = json["index"] as? Int, 114 | let guid = json["guid"] as? String, 115 | let isActive = json["isActive"] as? Bool, 116 | let balance = json["balance"] as? String, 117 | let picture = json["picture"] as? String, 118 | let age = json["age"] as? Int, 119 | let eyeColorRawValue = json["eyeColor"] as? String, 120 | let eyeColor = Color(rawValue: eyeColorRawValue), 121 | let name = json["name"] as? String, 122 | let genderRawValue = json["gender"] as? String, 123 | let gender = Gender(rawValue: genderRawValue), 124 | let company = json["company"] as? String, 125 | let email = json["email"] as? String, 126 | let phone = json["phone"] as? String, 127 | let address = json["address"] as? String, 128 | let about = json["about"] as? String, 129 | let registered = json["registered"] as? String, 130 | let latitude = json["latitude"] as? Double, 131 | let longitude = json["longitude"] as? Double, 132 | let tags = json["tags"] as? [String], 133 | let friendsObjects = json["friends"] as? [Any], 134 | let greeting = json["greeting"] as? String, 135 | let favoriteFruit = json["favoriteFruit"] as? String 136 | else { throw FoundationJSONError.typeMismatch } 137 | 138 | self.id = id 139 | self.index = index 140 | self.guid = guid 141 | self.isActive = isActive 142 | self.balance = balance 143 | self.picture = picture 144 | self.age = age 145 | self.eyeColor = eyeColor 146 | self.name = name 147 | self.gender = gender 148 | self.company = company 149 | self.email = email 150 | self.phone = phone 151 | self.address = address 152 | self.about = about 153 | self.registered = registered 154 | self.latitude = latitude 155 | self.longitude = longitude 156 | self.tags = tags 157 | self.friends = try friendsObjects.map(Friend.init) 158 | self.greeting = greeting 159 | self.favoriteFruit = favoriteFruit 160 | } 161 | } 162 | 163 | 164 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | 4 | XCTMain([ 5 | testCase(JSONTests.allTests), 6 | ]) 7 | --------------------------------------------------------------------------------