├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── AnyCodable-FlightSchool.podspec ├── AnyCodable.playground ├── Contents.swift └── contents.xcplayground ├── AnyCodable.xcconfig ├── AnyCodable.xcodeproj ├── AnyCodableTests_Info.plist ├── AnyCodable_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── AnyCodable-Package.xcscheme ├── AnyCodable.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE.md ├── Package.swift ├── README.md ├── Sources └── AnyCodable │ ├── AnyCodable.swift │ ├── AnyDecodable.swift │ └── AnyEncodable.swift └── Tests └── AnyCodableTests ├── AnyCodableTests.swift ├── AnyDecodableTests.swift └── AnyEncodableTests.swift /.gitattributes: -------------------------------------------------------------------------------- 1 | Brewfile linguist-detectable=false 2 | Makefile linguist-detectable=false 3 | *.podspec linguist-detectable=false 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [mattt] 2 | custom: https://flight.school/books/codable 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | macos: 11 | runs-on: macos-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v1 16 | - name: Build and Test 17 | run: swift test 18 | 19 | linux: 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | matrix: 24 | swift: 25 | - "5.1" 26 | - "5.2" 27 | - "5.3" 28 | - latest 29 | 30 | container: 31 | image: swift:${{ matrix.swift }} 32 | 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v1 36 | - name: Build and Test 37 | run: swift test --enable-test-discovery 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*.*.*" 7 | 8 | jobs: 9 | release: 10 | name: Push release to CocoaPods Trunk 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Install Cocoapods 19 | run: gem install cocoapods 20 | 21 | - name: Deploy to Cocoapods 22 | run: | 23 | set -eo pipefail 24 | pod lib lint --allow-warnings 25 | pod trunk push --allow-warnings 26 | env: 27 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DeriveredData/ 6 | *.xcodeproj/* 7 | !*.xcodeproj/project.pbxproj 8 | !*.xcodeproj/xcshareddata/ 9 | !*.xcworkspace/contents.xcworkspacedata 10 | /*.gcno 11 | **/xcshareddata/WorkspaceSettings.xcsettings 12 | Carthage/Build/ 13 | /.swiftpm 14 | -------------------------------------------------------------------------------- /AnyCodable-FlightSchool.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'AnyCodable-FlightSchool' 3 | s.module_name = 'AnyCodable' 4 | s.version = '0.6.7' 5 | s.summary = 'Type-erased wrappers for Encodable, Decodable, and Codable values.' 6 | 7 | s.description = <<-DESC 8 | This functionality is discussed in Chapter 3 of Flight School Guide to Swift Codable. 9 | DESC 10 | 11 | s.homepage = 'https://flight.school/books/codable/' 12 | 13 | s.license = { type: 'MIT', file: 'LICENSE.md' } 14 | 15 | s.author = { 'Mattt' => 'mattt@flight.school' } 16 | 17 | s.social_media_url = 'https://twitter.com/mattt' 18 | 19 | s.framework = 'Foundation' 20 | 21 | s.ios.deployment_target = '9.0' 22 | s.osx.deployment_target = '10.10' 23 | s.watchos.deployment_target = '2.0' 24 | s.tvos.deployment_target = '9.0' 25 | 26 | s.source = { git: 'https://github.com/Flight-School/AnyCodable.git', tag: s.version.to_s } 27 | 28 | s.source_files = 'Sources/**/*.swift' 29 | 30 | s.swift_version = '5.1' 31 | end 32 | -------------------------------------------------------------------------------- /AnyCodable.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AnyCodable 3 | 4 | let dictionary: [String: AnyEncodable] = [ 5 | "boolean": true, 6 | "integer": 1, 7 | "double": 3.141592653589793, 8 | "string": "string", 9 | "array": [1, 2, 3], 10 | "nested": [ 11 | "a": "alpha", 12 | "b": "bravo", 13 | "c": "charlie" 14 | ], 15 | "null": nil 16 | ] 17 | 18 | let encoder = JSONEncoder() 19 | let data = try encoder.encode(dictionary) 20 | 21 | let string = String(data: data, encoding: .utf8) 22 | 23 | let decoder = JSONDecoder() 24 | let object = try decoder.decode([String: AnyDecodable].self, from: data) 25 | -------------------------------------------------------------------------------- /AnyCodable.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /AnyCodable.xcconfig: -------------------------------------------------------------------------------- 1 | IPHONEOS_DEPLOYMENT_TARGET = 8.0 2 | MACOSX_DEPLOYMENT_TARGET = 10.10 3 | TVOS_DEPLOYMENT_TARGET = 9.0 4 | WATCHOS_DEPLOYMENT_TARGET = 2.0 5 | MACH_O_TYPE = staticlib 6 | -------------------------------------------------------------------------------- /AnyCodable.xcodeproj/AnyCodableTests_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 | -------------------------------------------------------------------------------- /AnyCodable.xcodeproj/AnyCodable_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 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | 1 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AnyCodable.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | "AnyCodable::AnyCodablePackageTests::ProductTarget" /* AnyCodablePackageTests */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = OBJ_41 /* Build configuration list for PBXAggregateTarget "AnyCodablePackageTests" */; 13 | buildPhases = ( 14 | ); 15 | dependencies = ( 16 | OBJ_44 /* PBXTargetDependency */, 17 | ); 18 | name = AnyCodablePackageTests; 19 | productName = AnyCodablePackageTests; 20 | }; 21 | /* End PBXAggregateTarget section */ 22 | 23 | /* Begin PBXBuildFile section */ 24 | OBJ_30 /* AnyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* AnyCodable.swift */; }; 25 | OBJ_31 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* AnyDecodable.swift */; }; 26 | OBJ_32 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* AnyEncodable.swift */; }; 27 | OBJ_39 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; 28 | OBJ_50 /* AnyCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* AnyCodableTests.swift */; }; 29 | OBJ_51 /* AnyDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* AnyDecodableTests.swift */; }; 30 | OBJ_52 /* AnyEncodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* AnyEncodableTests.swift */; }; 31 | OBJ_54 /* AnyCodable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "AnyCodable::AnyCodable::Product" /* AnyCodable.framework */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | F8558960228E1831001D1245 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = OBJ_1 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = "AnyCodable::AnyCodable"; 40 | remoteInfo = AnyCodable; 41 | }; 42 | F8558961228E1831001D1245 /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = OBJ_1 /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = "AnyCodable::AnyCodableTests"; 47 | remoteInfo = AnyCodableTests; 48 | }; 49 | /* End PBXContainerItemProxy section */ 50 | 51 | /* Begin PBXFileReference section */ 52 | "AnyCodable::AnyCodable::Product" /* AnyCodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AnyCodable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | "AnyCodable::AnyCodableTests::Product" /* AnyCodableTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = AnyCodableTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | OBJ_10 /* AnyDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = ""; }; 55 | OBJ_11 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = ""; }; 56 | OBJ_14 /* AnyCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyCodableTests.swift; sourceTree = ""; }; 57 | OBJ_15 /* AnyDecodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyDecodableTests.swift; sourceTree = ""; }; 58 | OBJ_16 /* AnyEncodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodableTests.swift; sourceTree = ""; }; 59 | OBJ_20 /* AnyCodable.xcworkspace */ = {isa = PBXFileReference; lastKnownFileType = wrapper.workspace; path = AnyCodable.xcworkspace; sourceTree = SOURCE_ROOT; }; 60 | OBJ_21 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; 61 | OBJ_22 /* AnyCodable-FlightSchool.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = "AnyCodable-FlightSchool.podspec"; sourceTree = ""; }; 62 | OBJ_23 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 63 | OBJ_24 /* AnyCodable.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AnyCodable.xcconfig; sourceTree = ""; }; 64 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 65 | OBJ_9 /* AnyCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyCodable.swift; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | OBJ_33 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 0; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | OBJ_53 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 0; 79 | files = ( 80 | OBJ_54 /* AnyCodable.framework in Frameworks */, 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | /* End PBXFrameworksBuildPhase section */ 85 | 86 | /* Begin PBXGroup section */ 87 | OBJ_12 /* Tests */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | OBJ_13 /* AnyCodableTests */, 91 | ); 92 | name = Tests; 93 | sourceTree = SOURCE_ROOT; 94 | }; 95 | OBJ_13 /* AnyCodableTests */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | OBJ_14 /* AnyCodableTests.swift */, 99 | OBJ_15 /* AnyDecodableTests.swift */, 100 | OBJ_16 /* AnyEncodableTests.swift */, 101 | ); 102 | name = AnyCodableTests; 103 | path = Tests/AnyCodableTests; 104 | sourceTree = SOURCE_ROOT; 105 | }; 106 | OBJ_17 /* Products */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | "AnyCodable::AnyCodableTests::Product" /* AnyCodableTests.xctest */, 110 | "AnyCodable::AnyCodable::Product" /* AnyCodable.framework */, 111 | ); 112 | name = Products; 113 | sourceTree = BUILT_PRODUCTS_DIR; 114 | }; 115 | OBJ_5 = { 116 | isa = PBXGroup; 117 | children = ( 118 | OBJ_6 /* Package.swift */, 119 | OBJ_7 /* Sources */, 120 | OBJ_12 /* Tests */, 121 | OBJ_17 /* Products */, 122 | OBJ_20 /* AnyCodable.xcworkspace */, 123 | OBJ_21 /* LICENSE.md */, 124 | OBJ_22 /* AnyCodable-FlightSchool.podspec */, 125 | OBJ_23 /* README.md */, 126 | OBJ_24 /* AnyCodable.xcconfig */, 127 | ); 128 | sourceTree = ""; 129 | }; 130 | OBJ_7 /* Sources */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | OBJ_8 /* AnyCodable */, 134 | ); 135 | name = Sources; 136 | sourceTree = SOURCE_ROOT; 137 | }; 138 | OBJ_8 /* AnyCodable */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | OBJ_9 /* AnyCodable.swift */, 142 | OBJ_10 /* AnyDecodable.swift */, 143 | OBJ_11 /* AnyEncodable.swift */, 144 | ); 145 | name = AnyCodable; 146 | path = Sources/AnyCodable; 147 | sourceTree = SOURCE_ROOT; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXNativeTarget section */ 152 | "AnyCodable::AnyCodable" /* AnyCodable */ = { 153 | isa = PBXNativeTarget; 154 | buildConfigurationList = OBJ_26 /* Build configuration list for PBXNativeTarget "AnyCodable" */; 155 | buildPhases = ( 156 | OBJ_29 /* Sources */, 157 | OBJ_33 /* Frameworks */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = AnyCodable; 164 | productName = AnyCodable; 165 | productReference = "AnyCodable::AnyCodable::Product" /* AnyCodable.framework */; 166 | productType = "com.apple.product-type.framework"; 167 | }; 168 | "AnyCodable::AnyCodableTests" /* AnyCodableTests */ = { 169 | isa = PBXNativeTarget; 170 | buildConfigurationList = OBJ_46 /* Build configuration list for PBXNativeTarget "AnyCodableTests" */; 171 | buildPhases = ( 172 | OBJ_49 /* Sources */, 173 | OBJ_53 /* Frameworks */, 174 | ); 175 | buildRules = ( 176 | ); 177 | dependencies = ( 178 | OBJ_55 /* PBXTargetDependency */, 179 | ); 180 | name = AnyCodableTests; 181 | productName = AnyCodableTests; 182 | productReference = "AnyCodable::AnyCodableTests::Product" /* AnyCodableTests.xctest */; 183 | productType = "com.apple.product-type.bundle.unit-test"; 184 | }; 185 | "AnyCodable::SwiftPMPackageDescription" /* AnyCodablePackageDescription */ = { 186 | isa = PBXNativeTarget; 187 | buildConfigurationList = OBJ_35 /* Build configuration list for PBXNativeTarget "AnyCodablePackageDescription" */; 188 | buildPhases = ( 189 | OBJ_38 /* Sources */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | ); 195 | name = AnyCodablePackageDescription; 196 | productName = AnyCodablePackageDescription; 197 | productType = "com.apple.product-type.framework"; 198 | }; 199 | /* End PBXNativeTarget section */ 200 | 201 | /* Begin PBXProject section */ 202 | OBJ_1 /* Project object */ = { 203 | isa = PBXProject; 204 | attributes = { 205 | LastSwiftMigration = 9999; 206 | LastUpgradeCheck = 9999; 207 | }; 208 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "AnyCodable" */; 209 | compatibilityVersion = "Xcode 3.2"; 210 | developmentRegion = English; 211 | hasScannedForEncodings = 0; 212 | knownRegions = ( 213 | English, 214 | en, 215 | ); 216 | mainGroup = OBJ_5; 217 | productRefGroup = OBJ_17 /* Products */; 218 | projectDirPath = ""; 219 | projectRoot = ""; 220 | targets = ( 221 | "AnyCodable::AnyCodable" /* AnyCodable */, 222 | "AnyCodable::SwiftPMPackageDescription" /* AnyCodablePackageDescription */, 223 | "AnyCodable::AnyCodablePackageTests::ProductTarget" /* AnyCodablePackageTests */, 224 | "AnyCodable::AnyCodableTests" /* AnyCodableTests */, 225 | ); 226 | }; 227 | /* End PBXProject section */ 228 | 229 | /* Begin PBXSourcesBuildPhase section */ 230 | OBJ_29 /* Sources */ = { 231 | isa = PBXSourcesBuildPhase; 232 | buildActionMask = 0; 233 | files = ( 234 | OBJ_30 /* AnyCodable.swift in Sources */, 235 | OBJ_31 /* AnyDecodable.swift in Sources */, 236 | OBJ_32 /* AnyEncodable.swift in Sources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | OBJ_38 /* Sources */ = { 241 | isa = PBXSourcesBuildPhase; 242 | buildActionMask = 0; 243 | files = ( 244 | OBJ_39 /* Package.swift in Sources */, 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | }; 248 | OBJ_49 /* Sources */ = { 249 | isa = PBXSourcesBuildPhase; 250 | buildActionMask = 0; 251 | files = ( 252 | OBJ_50 /* AnyCodableTests.swift in Sources */, 253 | OBJ_51 /* AnyDecodableTests.swift in Sources */, 254 | OBJ_52 /* AnyEncodableTests.swift in Sources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | /* End PBXSourcesBuildPhase section */ 259 | 260 | /* Begin PBXTargetDependency section */ 261 | OBJ_44 /* PBXTargetDependency */ = { 262 | isa = PBXTargetDependency; 263 | target = "AnyCodable::AnyCodableTests" /* AnyCodableTests */; 264 | targetProxy = F8558961228E1831001D1245 /* PBXContainerItemProxy */; 265 | }; 266 | OBJ_55 /* PBXTargetDependency */ = { 267 | isa = PBXTargetDependency; 268 | target = "AnyCodable::AnyCodable" /* AnyCodable */; 269 | targetProxy = F8558960228E1831001D1245 /* PBXContainerItemProxy */; 270 | }; 271 | /* End PBXTargetDependency section */ 272 | 273 | /* Begin XCBuildConfiguration section */ 274 | OBJ_27 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES; 278 | DYLIB_COMPATIBILITY_VERSION = 1; 279 | DYLIB_CURRENT_VERSION = 1; 280 | ENABLE_TESTABILITY = YES; 281 | FRAMEWORK_SEARCH_PATHS = ( 282 | "$(inherited)", 283 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 284 | ); 285 | HEADER_SEARCH_PATHS = "$(inherited)"; 286 | INFOPLIST_FILE = AnyCodable.xcodeproj/AnyCodable_Info.plist; 287 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 288 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 289 | MACOSX_DEPLOYMENT_TARGET = 10.10; 290 | OTHER_CFLAGS = "$(inherited)"; 291 | OTHER_LDFLAGS = "$(inherited)"; 292 | OTHER_SWIFT_FLAGS = "$(inherited)"; 293 | PRODUCT_BUNDLE_IDENTIFIER = AnyCodable; 294 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 295 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 296 | SKIP_INSTALL = YES; 297 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 298 | SWIFT_VERSION = 5.0; 299 | TARGET_NAME = AnyCodable; 300 | TVOS_DEPLOYMENT_TARGET = 9.0; 301 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 302 | }; 303 | name = Debug; 304 | }; 305 | OBJ_28 /* Release */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES; 309 | DYLIB_COMPATIBILITY_VERSION = 1; 310 | DYLIB_CURRENT_VERSION = 1; 311 | ENABLE_TESTABILITY = YES; 312 | FRAMEWORK_SEARCH_PATHS = ( 313 | "$(inherited)", 314 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 315 | ); 316 | HEADER_SEARCH_PATHS = "$(inherited)"; 317 | INFOPLIST_FILE = AnyCodable.xcodeproj/AnyCodable_Info.plist; 318 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 319 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 320 | MACOSX_DEPLOYMENT_TARGET = 10.10; 321 | OTHER_CFLAGS = "$(inherited)"; 322 | OTHER_LDFLAGS = "$(inherited)"; 323 | OTHER_SWIFT_FLAGS = "$(inherited)"; 324 | PRODUCT_BUNDLE_IDENTIFIER = AnyCodable; 325 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 326 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 327 | SKIP_INSTALL = YES; 328 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 329 | SWIFT_VERSION = 5.0; 330 | TARGET_NAME = AnyCodable; 331 | TVOS_DEPLOYMENT_TARGET = 9.0; 332 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 333 | }; 334 | name = Release; 335 | }; 336 | OBJ_3 /* Debug */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | CLANG_ENABLE_OBJC_ARC = YES; 340 | COMBINE_HIDPI_IMAGES = YES; 341 | COPY_PHASE_STRIP = NO; 342 | DEBUG_INFORMATION_FORMAT = dwarf; 343 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 344 | ENABLE_NS_ASSERTIONS = YES; 345 | GCC_OPTIMIZATION_LEVEL = 0; 346 | GCC_PREPROCESSOR_DEFINITIONS = ( 347 | "$(inherited)", 348 | "SWIFT_PACKAGE=1", 349 | "DEBUG=1", 350 | ); 351 | MACOSX_DEPLOYMENT_TARGET = 10.10; 352 | ONLY_ACTIVE_ARCH = YES; 353 | OTHER_SWIFT_FLAGS = "-DXcode"; 354 | PRODUCT_NAME = "$(TARGET_NAME)"; 355 | SDKROOT = macosx; 356 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 357 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; 358 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 359 | USE_HEADERMAP = NO; 360 | }; 361 | name = Debug; 362 | }; 363 | OBJ_36 /* Debug */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES; 367 | LD = /usr/bin/true; 368 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk"; 369 | SWIFT_VERSION = 5.0; 370 | }; 371 | name = Debug; 372 | }; 373 | OBJ_37 /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES; 377 | LD = /usr/bin/true; 378 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk"; 379 | SWIFT_VERSION = 5.0; 380 | }; 381 | name = Release; 382 | }; 383 | OBJ_4 /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | CLANG_ENABLE_OBJC_ARC = YES; 387 | COMBINE_HIDPI_IMAGES = YES; 388 | COPY_PHASE_STRIP = YES; 389 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 390 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 391 | GCC_OPTIMIZATION_LEVEL = s; 392 | GCC_PREPROCESSOR_DEFINITIONS = ( 393 | "$(inherited)", 394 | "SWIFT_PACKAGE=1", 395 | ); 396 | MACOSX_DEPLOYMENT_TARGET = 10.10; 397 | OTHER_SWIFT_FLAGS = "-DXcode"; 398 | PRODUCT_NAME = "$(TARGET_NAME)"; 399 | SDKROOT = macosx; 400 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 401 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE"; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 403 | USE_HEADERMAP = NO; 404 | }; 405 | name = Release; 406 | }; 407 | OBJ_42 /* Debug */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES; 411 | }; 412 | name = Debug; 413 | }; 414 | OBJ_43 /* Release */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES; 418 | }; 419 | name = Release; 420 | }; 421 | OBJ_47 /* Debug */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | CLANG_ENABLE_MODULES = YES; 425 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 426 | FRAMEWORK_SEARCH_PATHS = ( 427 | "$(inherited)", 428 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 429 | ); 430 | HEADER_SEARCH_PATHS = "$(inherited)"; 431 | INFOPLIST_FILE = AnyCodable.xcodeproj/AnyCodableTests_Info.plist; 432 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 433 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; 434 | MACOSX_DEPLOYMENT_TARGET = 10.10; 435 | OTHER_CFLAGS = "$(inherited)"; 436 | OTHER_LDFLAGS = "$(inherited)"; 437 | OTHER_SWIFT_FLAGS = "$(inherited)"; 438 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 439 | SWIFT_VERSION = 5.0; 440 | TARGET_NAME = AnyCodableTests; 441 | TVOS_DEPLOYMENT_TARGET = 9.0; 442 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 443 | }; 444 | name = Debug; 445 | }; 446 | OBJ_48 /* Release */ = { 447 | isa = XCBuildConfiguration; 448 | buildSettings = { 449 | CLANG_ENABLE_MODULES = YES; 450 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 451 | FRAMEWORK_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 454 | ); 455 | HEADER_SEARCH_PATHS = "$(inherited)"; 456 | INFOPLIST_FILE = AnyCodable.xcodeproj/AnyCodableTests_Info.plist; 457 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; 459 | MACOSX_DEPLOYMENT_TARGET = 10.10; 460 | OTHER_CFLAGS = "$(inherited)"; 461 | OTHER_LDFLAGS = "$(inherited)"; 462 | OTHER_SWIFT_FLAGS = "$(inherited)"; 463 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 464 | SWIFT_VERSION = 5.0; 465 | TARGET_NAME = AnyCodableTests; 466 | TVOS_DEPLOYMENT_TARGET = 9.0; 467 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 468 | }; 469 | name = Release; 470 | }; 471 | /* End XCBuildConfiguration section */ 472 | 473 | /* Begin XCConfigurationList section */ 474 | OBJ_2 /* Build configuration list for PBXProject "AnyCodable" */ = { 475 | isa = XCConfigurationList; 476 | buildConfigurations = ( 477 | OBJ_3 /* Debug */, 478 | OBJ_4 /* Release */, 479 | ); 480 | defaultConfigurationIsVisible = 0; 481 | defaultConfigurationName = Release; 482 | }; 483 | OBJ_26 /* Build configuration list for PBXNativeTarget "AnyCodable" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | OBJ_27 /* Debug */, 487 | OBJ_28 /* Release */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | OBJ_35 /* Build configuration list for PBXNativeTarget "AnyCodablePackageDescription" */ = { 493 | isa = XCConfigurationList; 494 | buildConfigurations = ( 495 | OBJ_36 /* Debug */, 496 | OBJ_37 /* Release */, 497 | ); 498 | defaultConfigurationIsVisible = 0; 499 | defaultConfigurationName = Release; 500 | }; 501 | OBJ_41 /* Build configuration list for PBXAggregateTarget "AnyCodablePackageTests" */ = { 502 | isa = XCConfigurationList; 503 | buildConfigurations = ( 504 | OBJ_42 /* Debug */, 505 | OBJ_43 /* Release */, 506 | ); 507 | defaultConfigurationIsVisible = 0; 508 | defaultConfigurationName = Release; 509 | }; 510 | OBJ_46 /* Build configuration list for PBXNativeTarget "AnyCodableTests" */ = { 511 | isa = XCConfigurationList; 512 | buildConfigurations = ( 513 | OBJ_47 /* Debug */, 514 | OBJ_48 /* Release */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | defaultConfigurationName = Release; 518 | }; 519 | /* End XCConfigurationList section */ 520 | }; 521 | rootObject = OBJ_1 /* Project object */; 522 | } 523 | -------------------------------------------------------------------------------- /AnyCodable.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /AnyCodable.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /AnyCodable.xcodeproj/xcshareddata/xcschemes/AnyCodable-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /AnyCodable.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AnyCodable.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2018 Read Evaluate Press, LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "AnyCodable", 8 | platforms: [ 9 | .iOS(.v9), 10 | .macOS(.v10_10), 11 | .tvOS(.v9), 12 | .watchOS(.v2), 13 | ], 14 | products: [ 15 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 16 | .library( 17 | name: "AnyCodable", 18 | targets: ["AnyCodable"] 19 | ), 20 | ], 21 | dependencies: [ 22 | // Dependencies declare other packages that this package depends on. 23 | // .package(url: /* package url */, from: "1.0.0"), 24 | ], 25 | targets: [ 26 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 27 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 28 | .target( 29 | name: "AnyCodable", 30 | dependencies: [] 31 | ), 32 | .testTarget( 33 | name: "AnyCodableTests", 34 | dependencies: ["AnyCodable"] 35 | ), 36 | ] 37 | ) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnyCodable 2 | 3 | [![Build Status][build status badge]][build status] 4 | [![License][license badge]][license] 5 | [![Swift Version][swift version badge]][swift version] 6 | ![Cocoapods platforms][cocoapods platforms badge] 7 | [![Cocoapods compatible][cocoapods badge]][cocoapods] 8 | [![Carthage compatible][carthage badge]][carthage] 9 | 10 | Type-erased wrappers for `Encodable`, `Decodable`, and `Codable` values. 11 | 12 | This functionality is discussed in Chapter 3 of 13 | [Flight School Guide to Swift Codable](https://flight.school/books/codable). 14 | 15 | ## Installation 16 | 17 | ### Swift Package Manager 18 | 19 | Add the AnyCodable package to your target dependencies in `Package.swift`: 20 | 21 | ```swift 22 | import PackageDescription 23 | 24 | let package = Package( 25 | name: "YourProject", 26 | dependencies: [ 27 | .package( 28 | url: "https://github.com/Flight-School/AnyCodable", 29 | from: "0.6.0" 30 | ), 31 | ] 32 | ) 33 | ``` 34 | 35 | Then run the `swift build` command to build your project. 36 | 37 | ### CocoaPods 38 | 39 | You can install `AnyCodable` via CocoaPods 40 | by adding the following line to your `Podfile`: 41 | 42 | ```ruby 43 | pod 'AnyCodable-FlightSchool', '~> 0.6.0' 44 | ``` 45 | 46 | Run the `pod install` command to download the library 47 | and integrate it into your Xcode project. 48 | 49 | > **Note** 50 | > The module name for this library is "AnyCodable" --- 51 | > that is, to use it, you add `import AnyCodable` to the top of your Swift code 52 | > just as you would by any other installation method. 53 | > The pod is called "AnyCodable-FlightSchool" 54 | > because there's an existing pod with the name "AnyCodable". 55 | 56 | ### Carthage 57 | 58 | To use `AnyCodable` in your Xcode project using Carthage, 59 | specify it in `Cartfile`: 60 | 61 | ``` 62 | github "Flight-School/AnyCodable" ~> 0.6.0 63 | ``` 64 | 65 | Then run the `carthage update` command to build the framework, 66 | and drag the built AnyCodable.framework into your Xcode project. 67 | 68 | ## Usage 69 | 70 | ### AnyEncodable 71 | 72 | ```swift 73 | import AnyCodable 74 | 75 | let dictionary: [String: AnyEncodable] = [ 76 | "boolean": true, 77 | "integer": 1, 78 | "double": 3.141592653589793, 79 | "string": "string", 80 | "array": [1, 2, 3], 81 | "nested": [ 82 | "a": "alpha", 83 | "b": "bravo", 84 | "c": "charlie" 85 | ], 86 | "null": nil 87 | ] 88 | 89 | let encoder = JSONEncoder() 90 | let json = try! encoder.encode(dictionary) 91 | ``` 92 | 93 | ### AnyDecodable 94 | 95 | ```swift 96 | let json = """ 97 | { 98 | "boolean": true, 99 | "integer": 1, 100 | "double": 3.141592653589793, 101 | "string": "string", 102 | "array": [1, 2, 3], 103 | "nested": { 104 | "a": "alpha", 105 | "b": "bravo", 106 | "c": "charlie" 107 | }, 108 | "null": null 109 | } 110 | """.data(using: .utf8)! 111 | 112 | let decoder = JSONDecoder() 113 | let dictionary = try! decoder.decode([String: AnyDecodable].self, from: json) 114 | ``` 115 | 116 | ### AnyCodable 117 | 118 | `AnyCodable` can be used to wrap values for encoding and decoding. 119 | 120 | ## License 121 | 122 | MIT 123 | 124 | ## Contact 125 | 126 | Mattt ([@mattt](https://twitter.com/mattt)) 127 | 128 | [build status]: https://github.com/Flight-School/AnyCodable/actions?query=workflow%3ACI 129 | [build status badge]: https://github.com/Flight-School/AnyCodable/workflows/CI/badge.svg 130 | [license]: https://opensource.org/licenses/MIT 131 | [license badge]: https://img.shields.io/cocoapods/l/AnyCodable-FlightSchool.svg 132 | [swift version]: https://swift.org/download/ 133 | [swift version badge]: https://img.shields.io/badge/swift%20version-5.1+-orange.svg 134 | [cocoapods platforms badge]: https://img.shields.io/cocoapods/p/AnyCodable-FlightSchool.svg 135 | [cocoapods]: https://cocoapods.org/pods/AnyCodable-FlightSchool 136 | [cocoapods badge]: https://img.shields.io/cocoapods/v/AnyCodable-FlightSchool.svg 137 | [carthage]: https://github.com/Carthage/Carthage 138 | [carthage badge]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg 139 | -------------------------------------------------------------------------------- /Sources/AnyCodable/AnyCodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | /** 3 | A type-erased `Codable` value. 4 | 5 | The `AnyCodable` type forwards encoding and decoding responsibilities 6 | to an underlying value, hiding its specific underlying type. 7 | 8 | You can encode or decode mixed-type values in dictionaries 9 | and other collections that require `Encodable` or `Decodable` conformance 10 | by declaring their contained type to be `AnyCodable`. 11 | 12 | - SeeAlso: `AnyEncodable` 13 | - SeeAlso: `AnyDecodable` 14 | */ 15 | @frozen public struct AnyCodable: Codable { 16 | public let value: Any 17 | 18 | public init(_ value: T?) { 19 | self.value = value ?? () 20 | } 21 | } 22 | 23 | extension AnyCodable: _AnyEncodable, _AnyDecodable {} 24 | 25 | extension AnyCodable: Equatable { 26 | public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool { 27 | switch (lhs.value, rhs.value) { 28 | case is (Void, Void): 29 | return true 30 | case let (lhs as Bool, rhs as Bool): 31 | return lhs == rhs 32 | case let (lhs as Int, rhs as Int): 33 | return lhs == rhs 34 | case let (lhs as Int8, rhs as Int8): 35 | return lhs == rhs 36 | case let (lhs as Int16, rhs as Int16): 37 | return lhs == rhs 38 | case let (lhs as Int32, rhs as Int32): 39 | return lhs == rhs 40 | case let (lhs as Int64, rhs as Int64): 41 | return lhs == rhs 42 | case let (lhs as UInt, rhs as UInt): 43 | return lhs == rhs 44 | case let (lhs as UInt8, rhs as UInt8): 45 | return lhs == rhs 46 | case let (lhs as UInt16, rhs as UInt16): 47 | return lhs == rhs 48 | case let (lhs as UInt32, rhs as UInt32): 49 | return lhs == rhs 50 | case let (lhs as UInt64, rhs as UInt64): 51 | return lhs == rhs 52 | case let (lhs as Float, rhs as Float): 53 | return lhs == rhs 54 | case let (lhs as Double, rhs as Double): 55 | return lhs == rhs 56 | case let (lhs as String, rhs as String): 57 | return lhs == rhs 58 | case let (lhs as [String: AnyCodable], rhs as [String: AnyCodable]): 59 | return lhs == rhs 60 | case let (lhs as [AnyCodable], rhs as [AnyCodable]): 61 | return lhs == rhs 62 | case let (lhs as [String: Any], rhs as [String: Any]): 63 | return NSDictionary(dictionary: lhs) == NSDictionary(dictionary: rhs) 64 | case let (lhs as [Any], rhs as [Any]): 65 | return NSArray(array: lhs) == NSArray(array: rhs) 66 | case is (NSNull, NSNull): 67 | return true 68 | default: 69 | return false 70 | } 71 | } 72 | } 73 | 74 | extension AnyCodable: CustomStringConvertible { 75 | public var description: String { 76 | switch value { 77 | case is Void: 78 | return String(describing: nil as Any?) 79 | case let value as CustomStringConvertible: 80 | return value.description 81 | default: 82 | return String(describing: value) 83 | } 84 | } 85 | } 86 | 87 | extension AnyCodable: CustomDebugStringConvertible { 88 | public var debugDescription: String { 89 | switch value { 90 | case let value as CustomDebugStringConvertible: 91 | return "AnyCodable(\(value.debugDescription))" 92 | default: 93 | return "AnyCodable(\(description))" 94 | } 95 | } 96 | } 97 | 98 | extension AnyCodable: ExpressibleByNilLiteral {} 99 | extension AnyCodable: ExpressibleByBooleanLiteral {} 100 | extension AnyCodable: ExpressibleByIntegerLiteral {} 101 | extension AnyCodable: ExpressibleByFloatLiteral {} 102 | extension AnyCodable: ExpressibleByStringLiteral {} 103 | extension AnyCodable: ExpressibleByStringInterpolation {} 104 | extension AnyCodable: ExpressibleByArrayLiteral {} 105 | extension AnyCodable: ExpressibleByDictionaryLiteral {} 106 | 107 | 108 | extension AnyCodable: Hashable { 109 | public func hash(into hasher: inout Hasher) { 110 | switch value { 111 | case let value as Bool: 112 | hasher.combine(value) 113 | case let value as Int: 114 | hasher.combine(value) 115 | case let value as Int8: 116 | hasher.combine(value) 117 | case let value as Int16: 118 | hasher.combine(value) 119 | case let value as Int32: 120 | hasher.combine(value) 121 | case let value as Int64: 122 | hasher.combine(value) 123 | case let value as UInt: 124 | hasher.combine(value) 125 | case let value as UInt8: 126 | hasher.combine(value) 127 | case let value as UInt16: 128 | hasher.combine(value) 129 | case let value as UInt32: 130 | hasher.combine(value) 131 | case let value as UInt64: 132 | hasher.combine(value) 133 | case let value as Float: 134 | hasher.combine(value) 135 | case let value as Double: 136 | hasher.combine(value) 137 | case let value as String: 138 | hasher.combine(value) 139 | case let value as [String: AnyCodable]: 140 | hasher.combine(value) 141 | case let value as [AnyCodable]: 142 | hasher.combine(value) 143 | default: 144 | break 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Sources/AnyCodable/AnyDecodable.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | import Foundation 3 | #endif 4 | 5 | /** 6 | A type-erased `Decodable` value. 7 | 8 | The `AnyDecodable` type forwards decoding responsibilities 9 | to an underlying value, hiding its specific underlying type. 10 | 11 | You can decode mixed-type values in dictionaries 12 | and other collections that require `Decodable` conformance 13 | by declaring their contained type to be `AnyDecodable`: 14 | 15 | let json = """ 16 | { 17 | "boolean": true, 18 | "integer": 42, 19 | "double": 3.141592653589793, 20 | "string": "string", 21 | "array": [1, 2, 3], 22 | "nested": { 23 | "a": "alpha", 24 | "b": "bravo", 25 | "c": "charlie" 26 | }, 27 | "null": null 28 | } 29 | """.data(using: .utf8)! 30 | 31 | let decoder = JSONDecoder() 32 | let dictionary = try! decoder.decode([String: AnyDecodable].self, from: json) 33 | */ 34 | @frozen public struct AnyDecodable: Decodable { 35 | public let value: Any 36 | 37 | public init(_ value: T?) { 38 | self.value = value ?? () 39 | } 40 | } 41 | 42 | @usableFromInline 43 | protocol _AnyDecodable { 44 | var value: Any { get } 45 | init(_ value: T?) 46 | } 47 | 48 | extension AnyDecodable: _AnyDecodable {} 49 | 50 | extension _AnyDecodable { 51 | public init(from decoder: Decoder) throws { 52 | let container = try decoder.singleValueContainer() 53 | 54 | if container.decodeNil() { 55 | #if canImport(Foundation) 56 | self.init(NSNull()) 57 | #else 58 | self.init(Optional.none) 59 | #endif 60 | } else if let bool = try? container.decode(Bool.self) { 61 | self.init(bool) 62 | } else if let int = try? container.decode(Int.self) { 63 | self.init(int) 64 | } else if let uint = try? container.decode(UInt.self) { 65 | self.init(uint) 66 | } else if let double = try? container.decode(Double.self) { 67 | self.init(double) 68 | } else if let string = try? container.decode(String.self) { 69 | self.init(string) 70 | } else if let array = try? container.decode([AnyDecodable].self) { 71 | self.init(array.map { $0.value }) 72 | } else if let dictionary = try? container.decode([String: AnyDecodable].self) { 73 | self.init(dictionary.mapValues { $0.value }) 74 | } else { 75 | throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyDecodable value cannot be decoded") 76 | } 77 | } 78 | } 79 | 80 | extension AnyDecodable: Equatable { 81 | public static func == (lhs: AnyDecodable, rhs: AnyDecodable) -> Bool { 82 | switch (lhs.value, rhs.value) { 83 | #if canImport(Foundation) 84 | case is (NSNull, NSNull), is (Void, Void): 85 | return true 86 | #endif 87 | case let (lhs as Bool, rhs as Bool): 88 | return lhs == rhs 89 | case let (lhs as Int, rhs as Int): 90 | return lhs == rhs 91 | case let (lhs as Int8, rhs as Int8): 92 | return lhs == rhs 93 | case let (lhs as Int16, rhs as Int16): 94 | return lhs == rhs 95 | case let (lhs as Int32, rhs as Int32): 96 | return lhs == rhs 97 | case let (lhs as Int64, rhs as Int64): 98 | return lhs == rhs 99 | case let (lhs as UInt, rhs as UInt): 100 | return lhs == rhs 101 | case let (lhs as UInt8, rhs as UInt8): 102 | return lhs == rhs 103 | case let (lhs as UInt16, rhs as UInt16): 104 | return lhs == rhs 105 | case let (lhs as UInt32, rhs as UInt32): 106 | return lhs == rhs 107 | case let (lhs as UInt64, rhs as UInt64): 108 | return lhs == rhs 109 | case let (lhs as Float, rhs as Float): 110 | return lhs == rhs 111 | case let (lhs as Double, rhs as Double): 112 | return lhs == rhs 113 | case let (lhs as String, rhs as String): 114 | return lhs == rhs 115 | case let (lhs as [String: AnyDecodable], rhs as [String: AnyDecodable]): 116 | return lhs == rhs 117 | case let (lhs as [AnyDecodable], rhs as [AnyDecodable]): 118 | return lhs == rhs 119 | default: 120 | return false 121 | } 122 | } 123 | } 124 | 125 | extension AnyDecodable: CustomStringConvertible { 126 | public var description: String { 127 | switch value { 128 | case is Void: 129 | return String(describing: nil as Any?) 130 | case let value as CustomStringConvertible: 131 | return value.description 132 | default: 133 | return String(describing: value) 134 | } 135 | } 136 | } 137 | 138 | extension AnyDecodable: CustomDebugStringConvertible { 139 | public var debugDescription: String { 140 | switch value { 141 | case let value as CustomDebugStringConvertible: 142 | return "AnyDecodable(\(value.debugDescription))" 143 | default: 144 | return "AnyDecodable(\(description))" 145 | } 146 | } 147 | } 148 | 149 | extension AnyDecodable: Hashable { 150 | public func hash(into hasher: inout Hasher) { 151 | switch value { 152 | case let value as Bool: 153 | hasher.combine(value) 154 | case let value as Int: 155 | hasher.combine(value) 156 | case let value as Int8: 157 | hasher.combine(value) 158 | case let value as Int16: 159 | hasher.combine(value) 160 | case let value as Int32: 161 | hasher.combine(value) 162 | case let value as Int64: 163 | hasher.combine(value) 164 | case let value as UInt: 165 | hasher.combine(value) 166 | case let value as UInt8: 167 | hasher.combine(value) 168 | case let value as UInt16: 169 | hasher.combine(value) 170 | case let value as UInt32: 171 | hasher.combine(value) 172 | case let value as UInt64: 173 | hasher.combine(value) 174 | case let value as Float: 175 | hasher.combine(value) 176 | case let value as Double: 177 | hasher.combine(value) 178 | case let value as String: 179 | hasher.combine(value) 180 | case let value as [String: AnyDecodable]: 181 | hasher.combine(value) 182 | case let value as [AnyDecodable]: 183 | hasher.combine(value) 184 | default: 185 | break 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /Sources/AnyCodable/AnyEncodable.swift: -------------------------------------------------------------------------------- 1 | #if canImport(Foundation) 2 | import Foundation 3 | #endif 4 | 5 | /** 6 | A type-erased `Encodable` value. 7 | 8 | The `AnyEncodable` type forwards encoding responsibilities 9 | to an underlying value, hiding its specific underlying type. 10 | 11 | You can encode mixed-type values in dictionaries 12 | and other collections that require `Encodable` conformance 13 | by declaring their contained type to be `AnyEncodable`: 14 | 15 | let dictionary: [String: AnyEncodable] = [ 16 | "boolean": true, 17 | "integer": 42, 18 | "double": 3.141592653589793, 19 | "string": "string", 20 | "array": [1, 2, 3], 21 | "nested": [ 22 | "a": "alpha", 23 | "b": "bravo", 24 | "c": "charlie" 25 | ], 26 | "null": nil 27 | ] 28 | 29 | let encoder = JSONEncoder() 30 | let json = try! encoder.encode(dictionary) 31 | */ 32 | @frozen public struct AnyEncodable: Encodable { 33 | public let value: Any 34 | 35 | public init(_ value: T?) { 36 | self.value = value ?? () 37 | } 38 | } 39 | 40 | @usableFromInline 41 | protocol _AnyEncodable { 42 | var value: Any { get } 43 | init(_ value: T?) 44 | } 45 | 46 | extension AnyEncodable: _AnyEncodable {} 47 | 48 | // MARK: - Encodable 49 | 50 | extension _AnyEncodable { 51 | public func encode(to encoder: Encoder) throws { 52 | var container = encoder.singleValueContainer() 53 | 54 | switch value { 55 | #if canImport(Foundation) 56 | case is NSNull: 57 | try container.encodeNil() 58 | #endif 59 | case is Void: 60 | try container.encodeNil() 61 | case let bool as Bool: 62 | try container.encode(bool) 63 | case let int as Int: 64 | try container.encode(int) 65 | case let int8 as Int8: 66 | try container.encode(int8) 67 | case let int16 as Int16: 68 | try container.encode(int16) 69 | case let int32 as Int32: 70 | try container.encode(int32) 71 | case let int64 as Int64: 72 | try container.encode(int64) 73 | case let uint as UInt: 74 | try container.encode(uint) 75 | case let uint8 as UInt8: 76 | try container.encode(uint8) 77 | case let uint16 as UInt16: 78 | try container.encode(uint16) 79 | case let uint32 as UInt32: 80 | try container.encode(uint32) 81 | case let uint64 as UInt64: 82 | try container.encode(uint64) 83 | case let float as Float: 84 | try container.encode(float) 85 | case let double as Double: 86 | try container.encode(double) 87 | case let string as String: 88 | try container.encode(string) 89 | #if canImport(Foundation) 90 | case let number as NSNumber: 91 | try encode(nsnumber: number, into: &container) 92 | case let date as Date: 93 | try container.encode(date) 94 | case let url as URL: 95 | try container.encode(url) 96 | #endif 97 | case let array as [Any?]: 98 | try container.encode(array.map { AnyEncodable($0) }) 99 | case let dictionary as [String: Any?]: 100 | try container.encode(dictionary.mapValues { AnyEncodable($0) }) 101 | case let encodable as Encodable: 102 | try encodable.encode(to: encoder) 103 | default: 104 | let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyEncodable value cannot be encoded") 105 | throw EncodingError.invalidValue(value, context) 106 | } 107 | } 108 | 109 | #if canImport(Foundation) 110 | private func encode(nsnumber: NSNumber, into container: inout SingleValueEncodingContainer) throws { 111 | switch Character(Unicode.Scalar(UInt8(nsnumber.objCType.pointee))) { 112 | case "B": 113 | try container.encode(nsnumber.boolValue) 114 | case "c": 115 | try container.encode(nsnumber.int8Value) 116 | case "s": 117 | try container.encode(nsnumber.int16Value) 118 | case "i", "l": 119 | try container.encode(nsnumber.int32Value) 120 | case "q": 121 | try container.encode(nsnumber.int64Value) 122 | case "C": 123 | try container.encode(nsnumber.uint8Value) 124 | case "S": 125 | try container.encode(nsnumber.uint16Value) 126 | case "I", "L": 127 | try container.encode(nsnumber.uint32Value) 128 | case "Q": 129 | try container.encode(nsnumber.uint64Value) 130 | case "f": 131 | try container.encode(nsnumber.floatValue) 132 | case "d": 133 | try container.encode(nsnumber.doubleValue) 134 | default: 135 | let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "NSNumber cannot be encoded because its type is not handled") 136 | throw EncodingError.invalidValue(nsnumber, context) 137 | } 138 | } 139 | #endif 140 | } 141 | 142 | extension AnyEncodable: Equatable { 143 | public static func == (lhs: AnyEncodable, rhs: AnyEncodable) -> Bool { 144 | switch (lhs.value, rhs.value) { 145 | case is (Void, Void): 146 | return true 147 | case let (lhs as Bool, rhs as Bool): 148 | return lhs == rhs 149 | case let (lhs as Int, rhs as Int): 150 | return lhs == rhs 151 | case let (lhs as Int8, rhs as Int8): 152 | return lhs == rhs 153 | case let (lhs as Int16, rhs as Int16): 154 | return lhs == rhs 155 | case let (lhs as Int32, rhs as Int32): 156 | return lhs == rhs 157 | case let (lhs as Int64, rhs as Int64): 158 | return lhs == rhs 159 | case let (lhs as UInt, rhs as UInt): 160 | return lhs == rhs 161 | case let (lhs as UInt8, rhs as UInt8): 162 | return lhs == rhs 163 | case let (lhs as UInt16, rhs as UInt16): 164 | return lhs == rhs 165 | case let (lhs as UInt32, rhs as UInt32): 166 | return lhs == rhs 167 | case let (lhs as UInt64, rhs as UInt64): 168 | return lhs == rhs 169 | case let (lhs as Float, rhs as Float): 170 | return lhs == rhs 171 | case let (lhs as Double, rhs as Double): 172 | return lhs == rhs 173 | case let (lhs as String, rhs as String): 174 | return lhs == rhs 175 | case let (lhs as [String: AnyEncodable], rhs as [String: AnyEncodable]): 176 | return lhs == rhs 177 | case let (lhs as [AnyEncodable], rhs as [AnyEncodable]): 178 | return lhs == rhs 179 | default: 180 | return false 181 | } 182 | } 183 | } 184 | 185 | extension AnyEncodable: CustomStringConvertible { 186 | public var description: String { 187 | switch value { 188 | case is Void: 189 | return String(describing: nil as Any?) 190 | case let value as CustomStringConvertible: 191 | return value.description 192 | default: 193 | return String(describing: value) 194 | } 195 | } 196 | } 197 | 198 | extension AnyEncodable: CustomDebugStringConvertible { 199 | public var debugDescription: String { 200 | switch value { 201 | case let value as CustomDebugStringConvertible: 202 | return "AnyEncodable(\(value.debugDescription))" 203 | default: 204 | return "AnyEncodable(\(description))" 205 | } 206 | } 207 | } 208 | 209 | extension AnyEncodable: ExpressibleByNilLiteral {} 210 | extension AnyEncodable: ExpressibleByBooleanLiteral {} 211 | extension AnyEncodable: ExpressibleByIntegerLiteral {} 212 | extension AnyEncodable: ExpressibleByFloatLiteral {} 213 | extension AnyEncodable: ExpressibleByStringLiteral {} 214 | extension AnyEncodable: ExpressibleByStringInterpolation {} 215 | extension AnyEncodable: ExpressibleByArrayLiteral {} 216 | extension AnyEncodable: ExpressibleByDictionaryLiteral {} 217 | 218 | extension _AnyEncodable { 219 | public init(nilLiteral _: ()) { 220 | self.init(nil as Any?) 221 | } 222 | 223 | public init(booleanLiteral value: Bool) { 224 | self.init(value) 225 | } 226 | 227 | public init(integerLiteral value: Int) { 228 | self.init(value) 229 | } 230 | 231 | public init(floatLiteral value: Double) { 232 | self.init(value) 233 | } 234 | 235 | public init(extendedGraphemeClusterLiteral value: String) { 236 | self.init(value) 237 | } 238 | 239 | public init(stringLiteral value: String) { 240 | self.init(value) 241 | } 242 | 243 | public init(arrayLiteral elements: Any...) { 244 | self.init(elements) 245 | } 246 | 247 | public init(dictionaryLiteral elements: (AnyHashable, Any)...) { 248 | self.init([AnyHashable: Any](elements, uniquingKeysWith: { first, _ in first })) 249 | } 250 | } 251 | 252 | extension AnyEncodable: Hashable { 253 | public func hash(into hasher: inout Hasher) { 254 | switch value { 255 | case let value as Bool: 256 | hasher.combine(value) 257 | case let value as Int: 258 | hasher.combine(value) 259 | case let value as Int8: 260 | hasher.combine(value) 261 | case let value as Int16: 262 | hasher.combine(value) 263 | case let value as Int32: 264 | hasher.combine(value) 265 | case let value as Int64: 266 | hasher.combine(value) 267 | case let value as UInt: 268 | hasher.combine(value) 269 | case let value as UInt8: 270 | hasher.combine(value) 271 | case let value as UInt16: 272 | hasher.combine(value) 273 | case let value as UInt32: 274 | hasher.combine(value) 275 | case let value as UInt64: 276 | hasher.combine(value) 277 | case let value as Float: 278 | hasher.combine(value) 279 | case let value as Double: 280 | hasher.combine(value) 281 | case let value as String: 282 | hasher.combine(value) 283 | case let value as [String: AnyEncodable]: 284 | hasher.combine(value) 285 | case let value as [AnyEncodable]: 286 | hasher.combine(value) 287 | default: 288 | break 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /Tests/AnyCodableTests/AnyCodableTests.swift: -------------------------------------------------------------------------------- 1 | @testable import AnyCodable 2 | import XCTest 3 | 4 | 5 | 6 | class AnyCodableTests: XCTestCase { 7 | 8 | struct SomeCodable: Codable { 9 | var string: String 10 | var int: Int 11 | var bool: Bool 12 | var hasUnderscore: String 13 | 14 | enum CodingKeys: String,CodingKey { 15 | case string 16 | case int 17 | case bool 18 | case hasUnderscore = "has_underscore" 19 | } 20 | } 21 | 22 | func testJSONDecoding() throws { 23 | let json = """ 24 | { 25 | "boolean": true, 26 | "integer": 42, 27 | "double": 3.141592653589793, 28 | "string": "string", 29 | "array": [1, 2, 3], 30 | "nested": { 31 | "a": "alpha", 32 | "b": "bravo", 33 | "c": "charlie" 34 | }, 35 | "null": null 36 | } 37 | """.data(using: .utf8)! 38 | 39 | let decoder = JSONDecoder() 40 | let dictionary = try decoder.decode([String: AnyCodable].self, from: json) 41 | 42 | XCTAssertEqual(dictionary["boolean"]?.value as! Bool, true) 43 | XCTAssertEqual(dictionary["integer"]?.value as! Int, 42) 44 | XCTAssertEqual(dictionary["double"]?.value as! Double, 3.141592653589793, accuracy: 0.001) 45 | XCTAssertEqual(dictionary["string"]?.value as! String, "string") 46 | XCTAssertEqual(dictionary["array"]?.value as! [Int], [1, 2, 3]) 47 | XCTAssertEqual(dictionary["nested"]?.value as! [String: String], ["a": "alpha", "b": "bravo", "c": "charlie"]) 48 | XCTAssertEqual(dictionary["null"]?.value as! NSNull, NSNull()) 49 | } 50 | 51 | func testJSONDecodingEquatable() throws { 52 | let json = """ 53 | { 54 | "boolean": true, 55 | "integer": 42, 56 | "double": 3.141592653589793, 57 | "string": "string", 58 | "array": [1, 2, 3], 59 | "nested": { 60 | "a": "alpha", 61 | "b": "bravo", 62 | "c": "charlie" 63 | }, 64 | "null": null 65 | } 66 | """.data(using: .utf8)! 67 | 68 | let decoder = JSONDecoder() 69 | let dictionary1 = try decoder.decode([String: AnyCodable].self, from: json) 70 | let dictionary2 = try decoder.decode([String: AnyCodable].self, from: json) 71 | 72 | XCTAssertEqual(dictionary1["boolean"], dictionary2["boolean"]) 73 | XCTAssertEqual(dictionary1["integer"], dictionary2["integer"]) 74 | XCTAssertEqual(dictionary1["double"], dictionary2["double"]) 75 | XCTAssertEqual(dictionary1["string"], dictionary2["string"]) 76 | XCTAssertEqual(dictionary1["array"], dictionary2["array"]) 77 | XCTAssertEqual(dictionary1["nested"], dictionary2["nested"]) 78 | XCTAssertEqual(dictionary1["null"], dictionary2["null"]) 79 | } 80 | 81 | func testJSONEncoding() throws { 82 | 83 | let someCodable = AnyCodable(SomeCodable(string: "String", int: 100, bool: true, hasUnderscore: "another string")) 84 | 85 | let injectedValue = 1234 86 | let dictionary: [String: AnyCodable] = [ 87 | "boolean": true, 88 | "integer": 42, 89 | "double": 3.141592653589793, 90 | "string": "string", 91 | "stringInterpolation": "string \(injectedValue)", 92 | "array": [1, 2, 3], 93 | "nested": [ 94 | "a": "alpha", 95 | "b": "bravo", 96 | "c": "charlie", 97 | ], 98 | "someCodable": someCodable, 99 | "null": nil 100 | ] 101 | 102 | let encoder = JSONEncoder() 103 | 104 | let json = try encoder.encode(dictionary) 105 | let encodedJSONObject = try JSONSerialization.jsonObject(with: json, options: []) as! NSDictionary 106 | 107 | let expected = """ 108 | { 109 | "boolean": true, 110 | "integer": 42, 111 | "double": 3.141592653589793, 112 | "string": "string", 113 | "stringInterpolation": "string 1234", 114 | "array": [1, 2, 3], 115 | "nested": { 116 | "a": "alpha", 117 | "b": "bravo", 118 | "c": "charlie" 119 | }, 120 | "someCodable": { 121 | "string":"String", 122 | "int":100, 123 | "bool": true, 124 | "has_underscore":"another string" 125 | }, 126 | "null": null 127 | } 128 | """.data(using: .utf8)! 129 | let expectedJSONObject = try JSONSerialization.jsonObject(with: expected, options: []) as! NSDictionary 130 | 131 | XCTAssertEqual(encodedJSONObject, expectedJSONObject) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Tests/AnyCodableTests/AnyDecodableTests.swift: -------------------------------------------------------------------------------- 1 | @testable import AnyCodable 2 | import XCTest 3 | 4 | class AnyDecodableTests: XCTestCase { 5 | func testJSONDecoding() throws { 6 | let json = """ 7 | { 8 | "boolean": true, 9 | "integer": 42, 10 | "double": 3.141592653589793, 11 | "string": "string", 12 | "array": [1, 2, 3], 13 | "nested": { 14 | "a": "alpha", 15 | "b": "bravo", 16 | "c": "charlie" 17 | }, 18 | "null": null 19 | } 20 | """.data(using: .utf8)! 21 | 22 | let decoder = JSONDecoder() 23 | let dictionary = try decoder.decode([String: AnyDecodable].self, from: json) 24 | 25 | XCTAssertEqual(dictionary["boolean"]?.value as! Bool, true) 26 | XCTAssertEqual(dictionary["integer"]?.value as! Int, 42) 27 | XCTAssertEqual(dictionary["double"]?.value as! Double, 3.141592653589793, accuracy: 0.001) 28 | XCTAssertEqual(dictionary["string"]?.value as! String, "string") 29 | XCTAssertEqual(dictionary["array"]?.value as! [Int], [1, 2, 3]) 30 | XCTAssertEqual(dictionary["nested"]?.value as! [String: String], ["a": "alpha", "b": "bravo", "c": "charlie"]) 31 | XCTAssertEqual(dictionary["null"]?.value as! NSNull, NSNull()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/AnyCodableTests/AnyEncodableTests.swift: -------------------------------------------------------------------------------- 1 | @testable import AnyCodable 2 | import XCTest 3 | 4 | class AnyEncodableTests: XCTestCase { 5 | 6 | struct SomeEncodable: Encodable { 7 | var string: String 8 | var int: Int 9 | var bool: Bool 10 | var hasUnderscore: String 11 | 12 | enum CodingKeys: String,CodingKey { 13 | case string 14 | case int 15 | case bool 16 | case hasUnderscore = "has_underscore" 17 | } 18 | } 19 | 20 | func testJSONEncoding() throws { 21 | 22 | let someEncodable = AnyEncodable(SomeEncodable(string: "String", int: 100, bool: true, hasUnderscore: "another string")) 23 | 24 | let dictionary: [String: AnyEncodable] = [ 25 | "boolean": true, 26 | "integer": 42, 27 | "double": 3.141592653589793, 28 | "string": "string", 29 | "array": [1, 2, 3], 30 | "nested": [ 31 | "a": "alpha", 32 | "b": "bravo", 33 | "c": "charlie", 34 | ], 35 | "someCodable": someEncodable, 36 | "null": nil 37 | ] 38 | 39 | let encoder = JSONEncoder() 40 | 41 | let json = try encoder.encode(dictionary) 42 | let encodedJSONObject = try JSONSerialization.jsonObject(with: json, options: []) as! NSDictionary 43 | 44 | let expected = """ 45 | { 46 | "boolean": true, 47 | "integer": 42, 48 | "double": 3.141592653589793, 49 | "string": "string", 50 | "array": [1, 2, 3], 51 | "nested": { 52 | "a": "alpha", 53 | "b": "bravo", 54 | "c": "charlie" 55 | }, 56 | "someCodable": { 57 | "string":"String", 58 | "int":100, 59 | "bool": true, 60 | "has_underscore":"another string" 61 | }, 62 | "null": null 63 | } 64 | """.data(using: .utf8)! 65 | let expectedJSONObject = try JSONSerialization.jsonObject(with: expected, options: []) as! NSDictionary 66 | 67 | XCTAssertEqual(encodedJSONObject, expectedJSONObject) 68 | } 69 | 70 | func testEncodeNSNumber() throws { 71 | let dictionary: [String: NSNumber] = [ 72 | "boolean": true, 73 | "char": -127, 74 | "int": -32767, 75 | "short": -32767, 76 | "long": -2147483647, 77 | "longlong": -9223372036854775807, 78 | "uchar": 255, 79 | "uint": 65535, 80 | "ushort": 65535, 81 | "ulong": 4294967295, 82 | "ulonglong": 18446744073709615, 83 | "double": 3.141592653589793, 84 | ] 85 | 86 | let encoder = JSONEncoder() 87 | 88 | let json = try encoder.encode(AnyEncodable(dictionary)) 89 | let encodedJSONObject = try JSONSerialization.jsonObject(with: json, options: []) as! NSDictionary 90 | 91 | let expected = """ 92 | { 93 | "boolean": true, 94 | "char": -127, 95 | "int": -32767, 96 | "short": -32767, 97 | "long": -2147483647, 98 | "longlong": -9223372036854775807, 99 | "uchar": 255, 100 | "uint": 65535, 101 | "ushort": 65535, 102 | "ulong": 4294967295, 103 | "ulonglong": 18446744073709615, 104 | "double": 3.141592653589793, 105 | } 106 | """.data(using: .utf8)! 107 | let expectedJSONObject = try JSONSerialization.jsonObject(with: expected, options: []) as! NSDictionary 108 | 109 | XCTAssertEqual(encodedJSONObject, expectedJSONObject) 110 | XCTAssert(encodedJSONObject["boolean"] is Bool) 111 | 112 | XCTAssert(encodedJSONObject["char"] is Int8) 113 | XCTAssert(encodedJSONObject["int"] is Int16) 114 | XCTAssert(encodedJSONObject["short"] is Int32) 115 | XCTAssert(encodedJSONObject["long"] is Int32) 116 | XCTAssert(encodedJSONObject["longlong"] is Int64) 117 | 118 | XCTAssert(encodedJSONObject["uchar"] is UInt8) 119 | XCTAssert(encodedJSONObject["uint"] is UInt16) 120 | XCTAssert(encodedJSONObject["ushort"] is UInt32) 121 | XCTAssert(encodedJSONObject["ulong"] is UInt32) 122 | XCTAssert(encodedJSONObject["ulonglong"] is UInt64) 123 | 124 | XCTAssert(encodedJSONObject["double"] is Double) 125 | } 126 | 127 | func testStringInterpolationEncoding() throws { 128 | let dictionary: [String: AnyEncodable] = [ 129 | "boolean": "\(true)", 130 | "integer": "\(42)", 131 | "double": "\(3.141592653589793)", 132 | "string": "\("string")", 133 | "array": "\([1, 2, 3])", 134 | ] 135 | 136 | let encoder = JSONEncoder() 137 | 138 | let json = try encoder.encode(dictionary) 139 | let encodedJSONObject = try JSONSerialization.jsonObject(with: json, options: []) as! NSDictionary 140 | 141 | let expected = """ 142 | { 143 | "boolean": "true", 144 | "integer": "42", 145 | "double": "3.141592653589793", 146 | "string": "string", 147 | "array": "[1, 2, 3]", 148 | } 149 | """.data(using: .utf8)! 150 | let expectedJSONObject = try JSONSerialization.jsonObject(with: expected, options: []) as! NSDictionary 151 | 152 | XCTAssertEqual(encodedJSONObject, expectedJSONObject) 153 | } 154 | } 155 | --------------------------------------------------------------------------------