├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Fastlane ├── Fastfile └── Pluginfile ├── Gemfile ├── Gemfile.lock ├── GenericJSON.podspec ├── GenericJSON.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── GenericJSON.xcscheme ├── GenericJSON ├── Info.plist ├── Initialization.swift ├── JSON.swift ├── Merging.swift └── Querying.swift ├── GenericJSONTests ├── CodingTests.swift ├── EqualityTests.swift ├── Info.plist ├── InitializationTests.swift ├── MergingTests.swift └── QueryingTests.swift ├── LICENSE ├── Makefile ├── Package.swift ├── Playground.playground ├── Contents.swift └── contents.xcplayground └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | .bundle 3 | Fastlane/README.md 4 | Fastlane/report.xml 5 | .DS_Store 6 | xcuserdata 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode10 2 | language: objective-c 3 | before_install: 4 | - gem update --system 5 | - gem install bundler 6 | xcode_project: GenericJSON.xcodeproj 7 | xcode_scheme: GenericJSON 8 | after_success: 9 | - bundle exec pod lib lint 10 | - swift test 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Details about this file’s format at . The change log is parsed automatically when minting releases through Fastlane, see `Fastlane/Fastfile`. 2 | 3 | ## [Unreleased] 4 | 5 | ## [2.0.2] - 2022-10-13 6 | 7 | - Fix warning about Swift version [iwill] 8 | 9 | ## [2.0.1] - 2019-11-14 10 | 11 | - Conform `JSON` to `Hashable` [cjmconie] 12 | 13 | ## [2.0.0] - 2019-07-04 14 | 15 | - Fix initialization from `NSNumber` booleans [cjmconie] 16 | - Change `Float` number representation to `Double` [cjmconie] 17 | 18 | ## [1.2.1] - 2019-04-23 19 | 20 | - Trivial release to change versioning tags 21 | 22 | ## [1.2.0] - 2019-02-13 23 | 24 | - Allow initialization from nil and NSNull values [cjmconie] 25 | 26 | ## [1.1.4] - 2019-01-31 27 | 28 | - Remove redundant “public” keyword from extensions [rudedogdhc] 29 | 30 | ## [1.1.3] - 2019-01-16 31 | 32 | - Set explicit iOS Deployment Target [rudedogdhc] 33 | 34 | ## [1.1.2] - 2019-01-06 35 | 36 | - Update podspec to always point to the latest tagged version [zoul] 37 | 38 | ## [1.1.1] - 2019-01-06 39 | 40 | - First version with a changelog :) 41 | - Add basic Swift Package Manager support [zoul] 42 | - Switch to a platform-neutral build target [zoul] 43 | - Add ability to query using a key path string [cjmconie] 44 | - Add first version of JSON merging [cjmconie & zoul] 45 | - Add support for macOS when distributed through CocoaPods [roznet] 46 | -------------------------------------------------------------------------------- /Fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | desc "Make a new release: Bump version number, update changelog and commit & tag the changes" 2 | lane :release do |options| 3 | 4 | # Make sure we don't commit any work-in-progress with the release 5 | ensure_git_status_clean 6 | 7 | # Read the release type from command-line arguments, default to patch 8 | release_type = options[:type] ? options[:type] : "patch" 9 | 10 | # Bump version number 11 | increment_version_number(bump_type: release_type) 12 | version_number = get_version_number(target: "GenericJSON") 13 | 14 | # Update changelog with the version number and release date 15 | stamp_changelog(section_identifier: version_number) 16 | git_add(path: 'CHANGELOG.md') 17 | 18 | # Update CocoaPods version 19 | version_bump_podspec(path: "GenericJSON.podspec", bump_type: release_type) 20 | git_add(path: 'GenericJSON.podspec') 21 | 22 | # Commit and tag the release 23 | commit_version_bump( 24 | message: "Release #{version_number}", 25 | xcodeproj: "GenericJSON.xcodeproj", 26 | force: true) 27 | add_git_tag(tag: version_number) 28 | end 29 | 30 | desc "Push podspec to CocoaPods trunk" 31 | lane :trunk do |options| 32 | pod_push 33 | end 34 | -------------------------------------------------------------------------------- /Fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-changelog' 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem "cocoapods", ">=1.6.0.beta" 4 | gem 'fastlane' 5 | 6 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 7 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.1) 5 | activesupport (4.2.11.1) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | addressable (2.7.0) 11 | public_suffix (>= 2.0.2, < 5.0) 12 | algoliasearch (1.27.1) 13 | httpclient (~> 2.8, >= 2.8.3) 14 | json (>= 1.5.1) 15 | atomos (0.1.3) 16 | babosa (1.0.3) 17 | claide (1.0.3) 18 | cocoapods (1.8.4) 19 | activesupport (>= 4.0.2, < 5) 20 | claide (>= 1.0.2, < 2.0) 21 | cocoapods-core (= 1.8.4) 22 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 23 | cocoapods-downloader (>= 1.2.2, < 2.0) 24 | cocoapods-plugins (>= 1.0.0, < 2.0) 25 | cocoapods-search (>= 1.0.0, < 2.0) 26 | cocoapods-stats (>= 1.0.0, < 2.0) 27 | cocoapods-trunk (>= 1.4.0, < 2.0) 28 | cocoapods-try (>= 1.1.0, < 2.0) 29 | colored2 (~> 3.1) 30 | escape (~> 0.0.4) 31 | fourflusher (>= 2.3.0, < 3.0) 32 | gh_inspector (~> 1.0) 33 | molinillo (~> 0.6.6) 34 | nap (~> 1.0) 35 | ruby-macho (~> 1.4) 36 | xcodeproj (>= 1.11.1, < 2.0) 37 | cocoapods-core (1.8.4) 38 | activesupport (>= 4.0.2, < 6) 39 | algoliasearch (~> 1.0) 40 | concurrent-ruby (~> 1.1) 41 | fuzzy_match (~> 2.0.4) 42 | nap (~> 1.0) 43 | cocoapods-deintegrate (1.0.4) 44 | cocoapods-downloader (1.2.2) 45 | cocoapods-plugins (1.0.0) 46 | nap 47 | cocoapods-search (1.0.0) 48 | cocoapods-stats (1.1.0) 49 | cocoapods-trunk (1.4.1) 50 | nap (>= 0.8, < 2.0) 51 | netrc (~> 0.11) 52 | cocoapods-try (1.1.0) 53 | colored (1.2) 54 | colored2 (3.1.2) 55 | commander-fastlane (4.4.6) 56 | highline (~> 1.7.2) 57 | concurrent-ruby (1.1.5) 58 | declarative (0.0.10) 59 | declarative-option (0.1.0) 60 | digest-crc (0.4.1) 61 | domain_name (0.5.20190701) 62 | unf (>= 0.0.5, < 1.0.0) 63 | dotenv (2.7.5) 64 | emoji_regex (1.0.1) 65 | escape (0.0.4) 66 | excon (0.68.0) 67 | faraday (0.17.0) 68 | multipart-post (>= 1.2, < 3) 69 | faraday-cookie_jar (0.0.6) 70 | faraday (>= 0.7.4) 71 | http-cookie (~> 1.0.0) 72 | faraday_middleware (0.13.1) 73 | faraday (>= 0.7.4, < 1.0) 74 | fastimage (2.1.7) 75 | fastlane (2.135.2) 76 | CFPropertyList (>= 2.3, < 4.0.0) 77 | addressable (>= 2.3, < 3.0.0) 78 | babosa (>= 1.0.2, < 2.0.0) 79 | bundler (>= 1.12.0, < 3.0.0) 80 | colored 81 | commander-fastlane (>= 4.4.6, < 5.0.0) 82 | dotenv (>= 2.1.1, < 3.0.0) 83 | emoji_regex (>= 0.1, < 2.0) 84 | excon (>= 0.45.0, < 1.0.0) 85 | faraday (~> 0.17) 86 | faraday-cookie_jar (~> 0.0.6) 87 | faraday_middleware (~> 0.13.1) 88 | fastimage (>= 2.1.0, < 3.0.0) 89 | gh_inspector (>= 1.1.2, < 2.0.0) 90 | google-api-client (>= 0.21.2, < 0.24.0) 91 | google-cloud-storage (>= 1.15.0, < 2.0.0) 92 | highline (>= 1.7.2, < 2.0.0) 93 | json (< 3.0.0) 94 | jwt (~> 2.1.0) 95 | mini_magick (>= 4.9.4, < 5.0.0) 96 | multi_xml (~> 0.5) 97 | multipart-post (~> 2.0.0) 98 | plist (>= 3.1.0, < 4.0.0) 99 | public_suffix (~> 2.0.0) 100 | rubyzip (>= 1.3.0, < 2.0.0) 101 | security (= 0.1.3) 102 | simctl (~> 1.6.3) 103 | slack-notifier (>= 2.0.0, < 3.0.0) 104 | terminal-notifier (>= 2.0.0, < 3.0.0) 105 | terminal-table (>= 1.4.5, < 2.0.0) 106 | tty-screen (>= 0.6.3, < 1.0.0) 107 | tty-spinner (>= 0.8.0, < 1.0.0) 108 | word_wrap (~> 1.0.0) 109 | xcodeproj (>= 1.8.1, < 2.0.0) 110 | xcpretty (~> 0.3.0) 111 | xcpretty-travis-formatter (>= 0.0.3) 112 | fastlane-plugin-changelog (0.15.0) 113 | fourflusher (2.3.1) 114 | fuzzy_match (2.0.4) 115 | gh_inspector (1.1.3) 116 | google-api-client (0.23.9) 117 | addressable (~> 2.5, >= 2.5.1) 118 | googleauth (>= 0.5, < 0.7.0) 119 | httpclient (>= 2.8.1, < 3.0) 120 | mime-types (~> 3.0) 121 | representable (~> 3.0) 122 | retriable (>= 2.0, < 4.0) 123 | signet (~> 0.9) 124 | google-cloud-core (1.3.2) 125 | google-cloud-env (~> 1.0) 126 | google-cloud-env (1.2.1) 127 | faraday (~> 0.11) 128 | google-cloud-storage (1.16.0) 129 | digest-crc (~> 0.4) 130 | google-api-client (~> 0.23) 131 | google-cloud-core (~> 1.2) 132 | googleauth (>= 0.6.2, < 0.10.0) 133 | googleauth (0.6.7) 134 | faraday (~> 0.12) 135 | jwt (>= 1.4, < 3.0) 136 | memoist (~> 0.16) 137 | multi_json (~> 1.11) 138 | os (>= 0.9, < 2.0) 139 | signet (~> 0.7) 140 | highline (1.7.10) 141 | http-cookie (1.0.3) 142 | domain_name (~> 0.5) 143 | httpclient (2.8.3) 144 | i18n (0.9.5) 145 | concurrent-ruby (~> 1.0) 146 | json (2.2.0) 147 | jwt (2.1.0) 148 | memoist (0.16.1) 149 | mime-types (3.3) 150 | mime-types-data (~> 3.2015) 151 | mime-types-data (3.2019.1009) 152 | mini_magick (4.9.5) 153 | minitest (5.13.0) 154 | molinillo (0.6.6) 155 | multi_json (1.14.1) 156 | multi_xml (0.6.0) 157 | multipart-post (2.0.0) 158 | nanaimo (0.2.6) 159 | nap (1.1.0) 160 | naturally (2.2.0) 161 | netrc (0.11.0) 162 | os (1.0.1) 163 | plist (3.5.0) 164 | public_suffix (2.0.5) 165 | representable (3.0.4) 166 | declarative (< 0.1.0) 167 | declarative-option (< 0.2.0) 168 | uber (< 0.2.0) 169 | retriable (3.1.2) 170 | rouge (2.0.7) 171 | ruby-macho (1.4.0) 172 | rubyzip (1.3.0) 173 | security (0.1.3) 174 | signet (0.11.0) 175 | addressable (~> 2.3) 176 | faraday (~> 0.9) 177 | jwt (>= 1.5, < 3.0) 178 | multi_json (~> 1.10) 179 | simctl (1.6.6) 180 | CFPropertyList 181 | naturally 182 | slack-notifier (2.3.2) 183 | terminal-notifier (2.0.0) 184 | terminal-table (1.8.0) 185 | unicode-display_width (~> 1.1, >= 1.1.1) 186 | thread_safe (0.3.6) 187 | tty-cursor (0.7.0) 188 | tty-screen (0.7.0) 189 | tty-spinner (0.9.1) 190 | tty-cursor (~> 0.7) 191 | tzinfo (1.2.5) 192 | thread_safe (~> 0.1) 193 | uber (0.1.0) 194 | unf (0.1.4) 195 | unf_ext 196 | unf_ext (0.0.7.6) 197 | unicode-display_width (1.6.0) 198 | word_wrap (1.0.0) 199 | xcodeproj (1.13.0) 200 | CFPropertyList (>= 2.3.3, < 4.0) 201 | atomos (~> 0.1.3) 202 | claide (>= 1.0.2, < 2.0) 203 | colored2 (~> 3.1) 204 | nanaimo (~> 0.2.6) 205 | xcpretty (0.3.0) 206 | rouge (~> 2.0.7) 207 | xcpretty-travis-formatter (1.0.0) 208 | xcpretty (~> 0.2, >= 0.0.7) 209 | 210 | PLATFORMS 211 | ruby 212 | 213 | DEPENDENCIES 214 | cocoapods (>= 1.6.0.beta) 215 | fastlane 216 | fastlane-plugin-changelog 217 | 218 | BUNDLED WITH 219 | 2.0.1 220 | -------------------------------------------------------------------------------- /GenericJSON.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'GenericJSON' 3 | s.ios.deployment_target = '10.0' 4 | s.osx.deployment_target = '10.14' 5 | s.version = '2.0.2' 6 | s.license = { :type => 'MIT' } 7 | s.homepage = 'https://github.com/zoul/generic-json-swift' 8 | s.authors = 'Tomáš Znamenáček' 9 | s.summary = 'A simple Swift library for working with generic JSON structures.' 10 | s.source = { :git => 'https://github.com/zoul/generic-json-swift.git', :tag => s.version } 11 | s.source_files = 'GenericJSON/*.swift' 12 | s.swift_version = "5.0" 13 | end 14 | 15 | -------------------------------------------------------------------------------- /GenericJSON.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0D25131F20E7322F0079E170 /* Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D25131E20E7322F0079E170 /* Querying.swift */; }; 11 | 0D25132320E732CA0079E170 /* InitializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDB78691F505F77007BC950 /* InitializationTests.swift */; }; 12 | 0D7AB1BF21A3F6BC006333EF /* Merging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7AB1BE21A3F6BC006333EF /* Merging.swift */; }; 13 | 0D7AB1C121A3F6EF006333EF /* MergingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7AB1C021A3F6EF006333EF /* MergingTests.swift */; }; 14 | 0D8B1C771F5011010071F6FC /* GenericJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D8B1C6D1F5011010071F6FC /* GenericJSON.framework */; }; 15 | 0D8B1C7C1F5011010071F6FC /* CodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8B1C7B1F5011010071F6FC /* CodingTests.swift */; }; 16 | 0D8B1C881F5011130071F6FC /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8B1C871F5011130071F6FC /* JSON.swift */; }; 17 | 0D8B1C8A1F50113F0071F6FC /* Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8B1C891F50113F0071F6FC /* Initialization.swift */; }; 18 | 0D8B1C8E1F5013DB0071F6FC /* EqualityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8B1C8D1F5013DB0071F6FC /* EqualityTests.swift */; }; 19 | FC5111AD2180B8E200D335C5 /* QueryingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5111AC2180B8E100D335C5 /* QueryingTests.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 0D8B1C781F5011010071F6FC /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 0D8B1C641F5011010071F6FC /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 0D8B1C6C1F5011010071F6FC; 28 | remoteInfo = GenericJSON; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 0D25131E20E7322F0079E170 /* Querying.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Querying.swift; sourceTree = ""; }; 34 | 0D7AB1BE21A3F6BC006333EF /* Merging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Merging.swift; sourceTree = ""; }; 35 | 0D7AB1C021A3F6EF006333EF /* MergingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergingTests.swift; sourceTree = ""; }; 36 | 0D8B1C6D1F5011010071F6FC /* GenericJSON.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GenericJSON.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 0D8B1C711F5011010071F6FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 0D8B1C761F5011010071F6FC /* GenericJSONTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GenericJSONTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 0D8B1C7B1F5011010071F6FC /* CodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodingTests.swift; sourceTree = ""; }; 40 | 0D8B1C7D1F5011010071F6FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 0D8B1C871F5011130071F6FC /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; 42 | 0D8B1C891F50113F0071F6FC /* Initialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Initialization.swift; sourceTree = ""; }; 43 | 0D8B1C8D1F5013DB0071F6FC /* EqualityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EqualityTests.swift; sourceTree = ""; }; 44 | 0DB7DD181F50285E00539475 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | 0DB7DD1D1F5028E600539475 /* Playground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Playground.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 46 | 0DDB78691F505F77007BC950 /* InitializationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializationTests.swift; sourceTree = ""; }; 47 | FC5111AC2180B8E100D335C5 /* QueryingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingTests.swift; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 0D8B1C691F5011010071F6FC /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | 0D8B1C731F5011010071F6FC /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | 0D8B1C771F5011010071F6FC /* GenericJSON.framework in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 0D8B1C631F5011010071F6FC = { 70 | isa = PBXGroup; 71 | children = ( 72 | 0DB7DD1D1F5028E600539475 /* Playground.playground */, 73 | 0D8B1C6F1F5011010071F6FC /* GenericJSON */, 74 | 0D8B1C7A1F5011010071F6FC /* GenericJSONTests */, 75 | 0DB7DD161F50285E00539475 /* GenericJSON */, 76 | 0D8B1C6E1F5011010071F6FC /* Products */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | 0D8B1C6E1F5011010071F6FC /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 0D8B1C6D1F5011010071F6FC /* GenericJSON.framework */, 84 | 0D8B1C761F5011010071F6FC /* GenericJSONTests.xctest */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 0D8B1C6F1F5011010071F6FC /* GenericJSON */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 0D8B1C871F5011130071F6FC /* JSON.swift */, 93 | 0D8B1C891F50113F0071F6FC /* Initialization.swift */, 94 | 0D25131E20E7322F0079E170 /* Querying.swift */, 95 | 0D7AB1BE21A3F6BC006333EF /* Merging.swift */, 96 | 0D8B1C711F5011010071F6FC /* Info.plist */, 97 | ); 98 | path = GenericJSON; 99 | sourceTree = ""; 100 | }; 101 | 0D8B1C7A1F5011010071F6FC /* GenericJSONTests */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | FC5111AC2180B8E100D335C5 /* QueryingTests.swift */, 105 | 0D8B1C7B1F5011010071F6FC /* CodingTests.swift */, 106 | 0DDB78691F505F77007BC950 /* InitializationTests.swift */, 107 | 0D8B1C8D1F5013DB0071F6FC /* EqualityTests.swift */, 108 | 0D7AB1C021A3F6EF006333EF /* MergingTests.swift */, 109 | 0D8B1C7D1F5011010071F6FC /* Info.plist */, 110 | ); 111 | path = GenericJSONTests; 112 | sourceTree = ""; 113 | }; 114 | 0DB7DD161F50285E00539475 /* GenericJSON */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 0DB7DD181F50285E00539475 /* Info.plist */, 118 | ); 119 | path = GenericJSON; 120 | sourceTree = ""; 121 | }; 122 | /* End PBXGroup section */ 123 | 124 | /* Begin PBXHeadersBuildPhase section */ 125 | 0D8B1C6A1F5011010071F6FC /* Headers */ = { 126 | isa = PBXHeadersBuildPhase; 127 | buildActionMask = 2147483647; 128 | files = ( 129 | ); 130 | runOnlyForDeploymentPostprocessing = 0; 131 | }; 132 | /* End PBXHeadersBuildPhase section */ 133 | 134 | /* Begin PBXNativeTarget section */ 135 | 0D8B1C6C1F5011010071F6FC /* GenericJSON */ = { 136 | isa = PBXNativeTarget; 137 | buildConfigurationList = 0D8B1C811F5011010071F6FC /* Build configuration list for PBXNativeTarget "GenericJSON" */; 138 | buildPhases = ( 139 | 0D8B1C681F5011010071F6FC /* Sources */, 140 | 0D8B1C691F5011010071F6FC /* Frameworks */, 141 | 0D8B1C6A1F5011010071F6FC /* Headers */, 142 | 0D8B1C6B1F5011010071F6FC /* Resources */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = GenericJSON; 149 | productName = GenericJSON; 150 | productReference = 0D8B1C6D1F5011010071F6FC /* GenericJSON.framework */; 151 | productType = "com.apple.product-type.framework"; 152 | }; 153 | 0D8B1C751F5011010071F6FC /* GenericJSONTests */ = { 154 | isa = PBXNativeTarget; 155 | buildConfigurationList = 0D8B1C841F5011010071F6FC /* Build configuration list for PBXNativeTarget "GenericJSONTests" */; 156 | buildPhases = ( 157 | 0D8B1C721F5011010071F6FC /* Sources */, 158 | 0D8B1C731F5011010071F6FC /* Frameworks */, 159 | 0D8B1C741F5011010071F6FC /* Resources */, 160 | ); 161 | buildRules = ( 162 | ); 163 | dependencies = ( 164 | 0D8B1C791F5011010071F6FC /* PBXTargetDependency */, 165 | ); 166 | name = GenericJSONTests; 167 | productName = GenericJSONTests; 168 | productReference = 0D8B1C761F5011010071F6FC /* GenericJSONTests.xctest */; 169 | productType = "com.apple.product-type.bundle.unit-test"; 170 | }; 171 | /* End PBXNativeTarget section */ 172 | 173 | /* Begin PBXProject section */ 174 | 0D8B1C641F5011010071F6FC /* Project object */ = { 175 | isa = PBXProject; 176 | attributes = { 177 | LastSwiftUpdateCheck = 0900; 178 | LastUpgradeCheck = 0900; 179 | ORGANIZATIONNAME = "Tomáš Znamenáček"; 180 | TargetAttributes = { 181 | 0D8B1C6C1F5011010071F6FC = { 182 | CreatedOnToolsVersion = 9.0; 183 | LastSwiftMigration = 0900; 184 | ProvisioningStyle = Automatic; 185 | }; 186 | 0D8B1C751F5011010071F6FC = { 187 | CreatedOnToolsVersion = 9.0; 188 | ProvisioningStyle = Automatic; 189 | }; 190 | }; 191 | }; 192 | buildConfigurationList = 0D8B1C671F5011010071F6FC /* Build configuration list for PBXProject "GenericJSON" */; 193 | compatibilityVersion = "Xcode 8.0"; 194 | developmentRegion = en; 195 | hasScannedForEncodings = 0; 196 | knownRegions = ( 197 | en, 198 | ); 199 | mainGroup = 0D8B1C631F5011010071F6FC; 200 | productRefGroup = 0D8B1C6E1F5011010071F6FC /* Products */; 201 | projectDirPath = ""; 202 | projectRoot = ""; 203 | targets = ( 204 | 0D8B1C6C1F5011010071F6FC /* GenericJSON */, 205 | 0D8B1C751F5011010071F6FC /* GenericJSONTests */, 206 | ); 207 | }; 208 | /* End PBXProject section */ 209 | 210 | /* Begin PBXResourcesBuildPhase section */ 211 | 0D8B1C6B1F5011010071F6FC /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | 0D8B1C741F5011010071F6FC /* Resources */ = { 219 | isa = PBXResourcesBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXResourcesBuildPhase section */ 226 | 227 | /* Begin PBXSourcesBuildPhase section */ 228 | 0D8B1C681F5011010071F6FC /* Sources */ = { 229 | isa = PBXSourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 0D25131F20E7322F0079E170 /* Querying.swift in Sources */, 233 | 0D7AB1BF21A3F6BC006333EF /* Merging.swift in Sources */, 234 | 0D8B1C881F5011130071F6FC /* JSON.swift in Sources */, 235 | 0D8B1C8A1F50113F0071F6FC /* Initialization.swift in Sources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | 0D8B1C721F5011010071F6FC /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | FC5111AD2180B8E200D335C5 /* QueryingTests.swift in Sources */, 244 | 0D8B1C7C1F5011010071F6FC /* CodingTests.swift in Sources */, 245 | 0D25132320E732CA0079E170 /* InitializationTests.swift in Sources */, 246 | 0D8B1C8E1F5013DB0071F6FC /* EqualityTests.swift in Sources */, 247 | 0D7AB1C121A3F6EF006333EF /* MergingTests.swift in Sources */, 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | /* End PBXSourcesBuildPhase section */ 252 | 253 | /* Begin PBXTargetDependency section */ 254 | 0D8B1C791F5011010071F6FC /* PBXTargetDependency */ = { 255 | isa = PBXTargetDependency; 256 | target = 0D8B1C6C1F5011010071F6FC /* GenericJSON */; 257 | targetProxy = 0D8B1C781F5011010071F6FC /* PBXContainerItemProxy */; 258 | }; 259 | /* End PBXTargetDependency section */ 260 | 261 | /* Begin XCBuildConfiguration section */ 262 | 0D8B1C7F1F5011010071F6FC /* Debug */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_ANALYZER_NONNULL = YES; 267 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 268 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 269 | CLANG_CXX_LIBRARY = "libc++"; 270 | CLANG_ENABLE_MODULES = NO; 271 | CLANG_ENABLE_OBJC_ARC = YES; 272 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 273 | CLANG_WARN_BOOL_CONVERSION = YES; 274 | CLANG_WARN_COMMA = YES; 275 | CLANG_WARN_CONSTANT_CONVERSION = YES; 276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 277 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 278 | CLANG_WARN_EMPTY_BODY = YES; 279 | CLANG_WARN_ENUM_CONVERSION = YES; 280 | CLANG_WARN_INFINITE_RECURSION = YES; 281 | CLANG_WARN_INT_CONVERSION = YES; 282 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 284 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 285 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 286 | CLANG_WARN_STRICT_PROTOTYPES = YES; 287 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 288 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 289 | CLANG_WARN_UNREACHABLE_CODE = YES; 290 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 291 | CODE_SIGN_IDENTITY = "-"; 292 | COPY_PHASE_STRIP = NO; 293 | CURRENT_PROJECT_VERSION = 1; 294 | DEBUG_INFORMATION_FORMAT = dwarf; 295 | ENABLE_STRICT_OBJC_MSGSEND = YES; 296 | ENABLE_TESTABILITY = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu11; 298 | GCC_DYNAMIC_NO_PIC = NO; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_OPTIMIZATION_LEVEL = 0; 301 | GCC_PREPROCESSOR_DEFINITIONS = ( 302 | "DEBUG=1", 303 | "$(inherited)", 304 | ); 305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 307 | GCC_WARN_UNDECLARED_SELECTOR = YES; 308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 309 | GCC_WARN_UNUSED_FUNCTION = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 312 | MACOSX_DEPLOYMENT_TARGET = 10.12; 313 | MTL_ENABLE_DEBUG_INFO = YES; 314 | ONLY_ACTIVE_ARCH = YES; 315 | SDKROOT = macosx; 316 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 317 | SWIFT_INSTALL_OBJC_HEADER = NO; 318 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 319 | SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; 320 | SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; 321 | VERSIONING_SYSTEM = "apple-generic"; 322 | VERSION_INFO_PREFIX = ""; 323 | }; 324 | name = Debug; 325 | }; 326 | 0D8B1C801F5011010071F6FC /* Release */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ALWAYS_SEARCH_USER_PATHS = NO; 330 | CLANG_ANALYZER_NONNULL = YES; 331 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 333 | CLANG_CXX_LIBRARY = "libc++"; 334 | CLANG_ENABLE_MODULES = NO; 335 | CLANG_ENABLE_OBJC_ARC = YES; 336 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 337 | CLANG_WARN_BOOL_CONVERSION = YES; 338 | CLANG_WARN_COMMA = YES; 339 | CLANG_WARN_CONSTANT_CONVERSION = YES; 340 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 341 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 342 | CLANG_WARN_EMPTY_BODY = YES; 343 | CLANG_WARN_ENUM_CONVERSION = YES; 344 | CLANG_WARN_INFINITE_RECURSION = YES; 345 | CLANG_WARN_INT_CONVERSION = YES; 346 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 347 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 348 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 349 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 350 | CLANG_WARN_STRICT_PROTOTYPES = YES; 351 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 352 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 353 | CLANG_WARN_UNREACHABLE_CODE = YES; 354 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 355 | CODE_SIGN_IDENTITY = "-"; 356 | COPY_PHASE_STRIP = NO; 357 | CURRENT_PROJECT_VERSION = 1; 358 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 359 | ENABLE_NS_ASSERTIONS = NO; 360 | ENABLE_STRICT_OBJC_MSGSEND = YES; 361 | GCC_C_LANGUAGE_STANDARD = gnu11; 362 | GCC_NO_COMMON_BLOCKS = YES; 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 370 | MACOSX_DEPLOYMENT_TARGET = 10.12; 371 | MTL_ENABLE_DEBUG_INFO = NO; 372 | SDKROOT = macosx; 373 | SWIFT_INSTALL_OBJC_HEADER = NO; 374 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 375 | SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; 376 | SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; 377 | VERSIONING_SYSTEM = "apple-generic"; 378 | VERSION_INFO_PREFIX = ""; 379 | }; 380 | name = Release; 381 | }; 382 | 0D8B1C821F5011010071F6FC /* Debug */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | APPLICATION_EXTENSION_API_ONLY = YES; 386 | CODE_SIGN_IDENTITY = ""; 387 | CODE_SIGN_STYLE = Automatic; 388 | COMBINE_HIDPI_IMAGES = YES; 389 | DYLIB_COMPATIBILITY_VERSION = 1; 390 | DYLIB_CURRENT_VERSION = 1; 391 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 392 | FRAMEWORK_VERSION = A; 393 | INFOPLIST_FILE = GenericJSON/Info.plist; 394 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 395 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 396 | PRODUCT_BUNDLE_IDENTIFIER = com.github.zoul.GenericJSON; 397 | PRODUCT_NAME = GenericJSON; 398 | SKIP_INSTALL = YES; 399 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchos watchsimulator appletvos appletvsimulator macosx"; 400 | SWIFT_VERSION = 4.0; 401 | }; 402 | name = Debug; 403 | }; 404 | 0D8B1C831F5011010071F6FC /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | APPLICATION_EXTENSION_API_ONLY = YES; 408 | CODE_SIGN_IDENTITY = ""; 409 | CODE_SIGN_STYLE = Automatic; 410 | COMBINE_HIDPI_IMAGES = YES; 411 | DYLIB_COMPATIBILITY_VERSION = 1; 412 | DYLIB_CURRENT_VERSION = 1; 413 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 414 | FRAMEWORK_VERSION = A; 415 | INFOPLIST_FILE = GenericJSON/Info.plist; 416 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = com.github.zoul.GenericJSON; 419 | PRODUCT_NAME = GenericJSON; 420 | SKIP_INSTALL = YES; 421 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchos watchsimulator appletvos appletvsimulator macosx"; 422 | SWIFT_VERSION = 4.0; 423 | }; 424 | name = Release; 425 | }; 426 | 0D8B1C851F5011010071F6FC /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 430 | CODE_SIGN_STYLE = Automatic; 431 | COMBINE_HIDPI_IMAGES = YES; 432 | INFOPLIST_FILE = GenericJSONTests/Info.plist; 433 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 434 | PRODUCT_BUNDLE_IDENTIFIER = com.github.zoul.GenericJSONTests; 435 | PRODUCT_NAME = "$(TARGET_NAME)"; 436 | SWIFT_VERSION = 4.0; 437 | }; 438 | name = Debug; 439 | }; 440 | 0D8B1C861F5011010071F6FC /* Release */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 444 | CODE_SIGN_STYLE = Automatic; 445 | COMBINE_HIDPI_IMAGES = YES; 446 | INFOPLIST_FILE = GenericJSONTests/Info.plist; 447 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 448 | PRODUCT_BUNDLE_IDENTIFIER = com.github.zoul.GenericJSONTests; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | SWIFT_VERSION = 4.0; 451 | }; 452 | name = Release; 453 | }; 454 | /* End XCBuildConfiguration section */ 455 | 456 | /* Begin XCConfigurationList section */ 457 | 0D8B1C671F5011010071F6FC /* Build configuration list for PBXProject "GenericJSON" */ = { 458 | isa = XCConfigurationList; 459 | buildConfigurations = ( 460 | 0D8B1C7F1F5011010071F6FC /* Debug */, 461 | 0D8B1C801F5011010071F6FC /* Release */, 462 | ); 463 | defaultConfigurationIsVisible = 0; 464 | defaultConfigurationName = Release; 465 | }; 466 | 0D8B1C811F5011010071F6FC /* Build configuration list for PBXNativeTarget "GenericJSON" */ = { 467 | isa = XCConfigurationList; 468 | buildConfigurations = ( 469 | 0D8B1C821F5011010071F6FC /* Debug */, 470 | 0D8B1C831F5011010071F6FC /* Release */, 471 | ); 472 | defaultConfigurationIsVisible = 0; 473 | defaultConfigurationName = Release; 474 | }; 475 | 0D8B1C841F5011010071F6FC /* Build configuration list for PBXNativeTarget "GenericJSONTests" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | 0D8B1C851F5011010071F6FC /* Debug */, 479 | 0D8B1C861F5011010071F6FC /* Release */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | defaultConfigurationName = Release; 483 | }; 484 | /* End XCConfigurationList section */ 485 | }; 486 | rootObject = 0D8B1C641F5011010071F6FC /* Project object */; 487 | } 488 | -------------------------------------------------------------------------------- /GenericJSON.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GenericJSON.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GenericJSON.xcodeproj/xcshareddata/xcschemes/GenericJSON.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /GenericJSON/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 2.0.1 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /GenericJSON/Initialization.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private struct InitializationError: Error {} 4 | 5 | extension JSON { 6 | 7 | /// Create a JSON value from anything. 8 | /// 9 | /// Argument has to be a valid JSON structure: A `Double`, `Int`, `String`, 10 | /// `Bool`, an `Array` of those types or a `Dictionary` of those types. 11 | /// 12 | /// You can also pass `nil` or `NSNull`, both will be treated as `.null`. 13 | public init(_ value: Any) throws { 14 | switch value { 15 | case _ as NSNull: 16 | self = .null 17 | case let opt as Optional where opt == nil: 18 | self = .null 19 | case let num as NSNumber: 20 | if num.isBool { 21 | self = .bool(num.boolValue) 22 | } else { 23 | self = .number(num.doubleValue) 24 | } 25 | case let str as String: 26 | self = .string(str) 27 | case let bool as Bool: 28 | self = .bool(bool) 29 | case let array as [Any]: 30 | self = .array(try array.map(JSON.init)) 31 | case let dict as [String:Any]: 32 | self = .object(try dict.mapValues(JSON.init)) 33 | default: 34 | throw InitializationError() 35 | } 36 | } 37 | } 38 | 39 | extension JSON { 40 | 41 | /// Create a JSON value from an `Encodable`. This will give you access to the “raw” 42 | /// encoded JSON value the `Encodable` is serialized into. 43 | public init(encodable: T) throws { 44 | let encoded = try JSONEncoder().encode(encodable) 45 | self = try JSONDecoder().decode(JSON.self, from: encoded) 46 | } 47 | } 48 | 49 | extension JSON: ExpressibleByBooleanLiteral { 50 | 51 | public init(booleanLiteral value: Bool) { 52 | self = .bool(value) 53 | } 54 | } 55 | 56 | extension JSON: ExpressibleByNilLiteral { 57 | 58 | public init(nilLiteral: ()) { 59 | self = .null 60 | } 61 | } 62 | 63 | extension JSON: ExpressibleByArrayLiteral { 64 | 65 | public init(arrayLiteral elements: JSON...) { 66 | self = .array(elements) 67 | } 68 | } 69 | 70 | extension JSON: ExpressibleByDictionaryLiteral { 71 | 72 | public init(dictionaryLiteral elements: (String, JSON)...) { 73 | var object: [String:JSON] = [:] 74 | for (k, v) in elements { 75 | object[k] = v 76 | } 77 | self = .object(object) 78 | } 79 | } 80 | 81 | extension JSON: ExpressibleByFloatLiteral { 82 | 83 | public init(floatLiteral value: Double) { 84 | self = .number(value) 85 | } 86 | } 87 | 88 | extension JSON: ExpressibleByIntegerLiteral { 89 | 90 | public init(integerLiteral value: Int) { 91 | self = .number(Double(value)) 92 | } 93 | } 94 | 95 | extension JSON: ExpressibleByStringLiteral { 96 | 97 | public init(stringLiteral value: String) { 98 | self = .string(value) 99 | } 100 | } 101 | 102 | // MARK: - NSNumber 103 | 104 | extension NSNumber { 105 | 106 | /// Boolean value indicating whether this `NSNumber` wraps a boolean. 107 | /// 108 | /// For example, when using `NSJSONSerialization` Bool values are converted into `NSNumber` instances. 109 | /// 110 | /// - seealso: https://stackoverflow.com/a/49641315/3589408 111 | fileprivate var isBool: Bool { 112 | let objCType = String(cString: self.objCType) 113 | if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType) { 114 | return true 115 | } else { 116 | return false 117 | } 118 | } 119 | } 120 | 121 | private let trueNumber = NSNumber(value: true) 122 | private let falseNumber = NSNumber(value: false) 123 | private let trueObjCType = String(cString: trueNumber.objCType) 124 | private let falseObjCType = String(cString: falseNumber.objCType) 125 | -------------------------------------------------------------------------------- /GenericJSON/JSON.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A JSON value representation. This is a bit more useful than the naïve `[String:Any]` type 4 | /// for JSON values, since it makes sure only valid JSON values are present & supports `Equatable` 5 | /// and `Codable`, so that you can compare values for equality and code and decode them into data 6 | /// or strings. 7 | @dynamicMemberLookup public enum JSON: Equatable { 8 | case string(String) 9 | case number(Double) 10 | case object([String:JSON]) 11 | case array([JSON]) 12 | case bool(Bool) 13 | case null 14 | } 15 | 16 | extension JSON: Codable { 17 | 18 | public func encode(to encoder: Encoder) throws { 19 | 20 | var container = encoder.singleValueContainer() 21 | 22 | switch self { 23 | case let .array(array): 24 | try container.encode(array) 25 | case let .object(object): 26 | try container.encode(object) 27 | case let .string(string): 28 | try container.encode(string) 29 | case let .number(number): 30 | try container.encode(number) 31 | case let .bool(bool): 32 | try container.encode(bool) 33 | case .null: 34 | try container.encodeNil() 35 | } 36 | } 37 | 38 | public init(from decoder: Decoder) throws { 39 | 40 | let container = try decoder.singleValueContainer() 41 | 42 | if let object = try? container.decode([String: JSON].self) { 43 | self = .object(object) 44 | } else if let array = try? container.decode([JSON].self) { 45 | self = .array(array) 46 | } else if let string = try? container.decode(String.self) { 47 | self = .string(string) 48 | } else if let bool = try? container.decode(Bool.self) { 49 | self = .bool(bool) 50 | } else if let number = try? container.decode(Double.self) { 51 | self = .number(number) 52 | } else if container.decodeNil() { 53 | self = .null 54 | } else { 55 | throw DecodingError.dataCorrupted( 56 | .init(codingPath: decoder.codingPath, debugDescription: "Invalid JSON value.") 57 | ) 58 | } 59 | } 60 | } 61 | 62 | extension JSON: CustomDebugStringConvertible { 63 | 64 | public var debugDescription: String { 65 | switch self { 66 | case .string(let str): 67 | return str.debugDescription 68 | case .number(let num): 69 | return num.debugDescription 70 | case .bool(let bool): 71 | return bool.description 72 | case .null: 73 | return "null" 74 | default: 75 | let encoder = JSONEncoder() 76 | encoder.outputFormatting = [.prettyPrinted] 77 | return try! String(data: encoder.encode(self), encoding: .utf8)! 78 | } 79 | } 80 | } 81 | 82 | extension JSON: Hashable {} 83 | -------------------------------------------------------------------------------- /GenericJSON/Merging.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension JSON { 4 | 5 | /// Return a new JSON value by merging two other ones 6 | /// 7 | /// If we call the current JSON value `old` and the incoming JSON value 8 | /// `new`, the precise merging rules are: 9 | /// 10 | /// 1. If `old` or `new` are anything but an object, return `new`. 11 | /// 2. If both `old` and `new` are objects, create a merged object like this: 12 | /// 1. Add keys from `old` not present in `new` (“no change” case). 13 | /// 2. Add keys from `new` not present in `old` (“create” case). 14 | /// 3. For keys present in both `old` and `new`, apply merge recursively to their values (“update” case). 15 | public func merging(with new: JSON) -> JSON { 16 | 17 | // If old or new are anything but an object, return new. 18 | guard case .object(let lhs) = self, case .object(let rhs) = new else { 19 | return new 20 | } 21 | 22 | var merged: [String: JSON] = [:] 23 | 24 | // Add keys from old not present in new (“no change” case). 25 | for (key, val) in lhs where rhs[key] == nil { 26 | merged[key] = val 27 | } 28 | 29 | // Add keys from new not present in old (“create” case). 30 | for (key, val) in rhs where lhs[key] == nil { 31 | merged[key] = val 32 | } 33 | 34 | // For keys present in both old and new, apply merge recursively to their values. 35 | for key in lhs.keys where rhs[key] != nil { 36 | merged[key] = lhs[key]?.merging(with: rhs[key]!) 37 | } 38 | 39 | return JSON.object(merged) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /GenericJSON/Querying.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension JSON { 4 | 5 | /// Return the string value if this is a `.string`, otherwise `nil` 6 | var stringValue: String? { 7 | if case .string(let value) = self { 8 | return value 9 | } 10 | return nil 11 | } 12 | 13 | /// Return the double value if this is a `.number`, otherwise `nil` 14 | var doubleValue: Double? { 15 | if case .number(let value) = self { 16 | return value 17 | } 18 | return nil 19 | } 20 | 21 | /// Return the bool value if this is a `.bool`, otherwise `nil` 22 | var boolValue: Bool? { 23 | if case .bool(let value) = self { 24 | return value 25 | } 26 | return nil 27 | } 28 | 29 | /// Return the object value if this is an `.object`, otherwise `nil` 30 | var objectValue: [String: JSON]? { 31 | if case .object(let value) = self { 32 | return value 33 | } 34 | return nil 35 | } 36 | 37 | /// Return the array value if this is an `.array`, otherwise `nil` 38 | var arrayValue: [JSON]? { 39 | if case .array(let value) = self { 40 | return value 41 | } 42 | return nil 43 | } 44 | 45 | /// Return `true` iff this is `.null` 46 | var isNull: Bool { 47 | if case .null = self { 48 | return true 49 | } 50 | return false 51 | } 52 | 53 | /// If this is an `.array`, return item at index 54 | /// 55 | /// If this is not an `.array` or the index is out of bounds, returns `nil`. 56 | subscript(index: Int) -> JSON? { 57 | if case .array(let arr) = self, arr.indices.contains(index) { 58 | return arr[index] 59 | } 60 | return nil 61 | } 62 | 63 | /// If this is an `.object`, return item at key 64 | subscript(key: String) -> JSON? { 65 | if case .object(let dict) = self { 66 | return dict[key] 67 | } 68 | return nil 69 | } 70 | 71 | /// Dynamic member lookup sugar for string subscripts 72 | /// 73 | /// This lets you write `json.foo` instead of `json["foo"]`. 74 | subscript(dynamicMember member: String) -> JSON? { 75 | return self[member] 76 | } 77 | 78 | /// Return the JSON type at the keypath if this is an `.object`, otherwise `nil` 79 | /// 80 | /// This lets you write `json[keyPath: "foo.bar.jar"]`. 81 | subscript(keyPath keyPath: String) -> JSON? { 82 | return queryKeyPath(keyPath.components(separatedBy: ".")) 83 | } 84 | 85 | func queryKeyPath(_ path: T) -> JSON? where T: Collection, T.Element == String { 86 | 87 | // Only object values may be subscripted 88 | guard case .object(let object) = self else { 89 | return nil 90 | } 91 | 92 | // Is the path non-empty? 93 | guard let head = path.first else { 94 | return nil 95 | } 96 | 97 | // Do we have a value at the required key? 98 | guard let value = object[head] else { 99 | return nil 100 | } 101 | 102 | let tail = path.dropFirst() 103 | 104 | return tail.isEmpty ? value : value.queryKeyPath(tail) 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /GenericJSONTests/CodingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import GenericJSON 3 | 4 | class CodingTests: XCTestCase { 5 | 6 | @available(OSX 10.13, *) 7 | func testEncoding() throws { 8 | let json: JSON = [ 9 | "num": 1, 10 | "str": "baz", 11 | "bool": true, 12 | "null": nil, 13 | "array": [], 14 | "obj": [:], 15 | ] 16 | let encoder = JSONEncoder() 17 | encoder.outputFormatting = .sortedKeys 18 | let encoded = try encoder.encode(json) 19 | let str = String(data: encoded, encoding: .utf8)! 20 | XCTAssertEqual(str, """ 21 | {"array":[],"bool":true,"null":null,"num":1,"obj":{},"str":"baz"} 22 | """) 23 | } 24 | 25 | // ???: DOESNOT throw errors 26 | // func testFragmentEncoding() { 27 | // let fragments: [JSON] = ["foo", 1, true, nil] 28 | // for f in fragments { 29 | // XCTAssertThrowsError(try JSONEncoder().encode(f)) 30 | // } 31 | // } 32 | 33 | func testDecoding() throws { 34 | let input = """ 35 | {"array":[1],"num":1,"bool":true,"obj":{},"null":null,"str":"baz"} 36 | """ 37 | let json = try! JSONDecoder().decode(JSON.self, from: input.data(using: .utf8)!) 38 | XCTAssertEqual(json, [ 39 | "num": 1, 40 | "str": "baz", 41 | "bool": true, 42 | "null": nil, 43 | "array": [1], 44 | "obj": [:], 45 | ]) 46 | } 47 | 48 | func testDecodingBool() throws { 49 | XCTAssertEqual(try JSONDecoder().decode(JSON.self, from: "{\"b\":true}".data(using: .utf8)!), ["b":true]) 50 | XCTAssertEqual(try JSONDecoder().decode(JSON.self, from: "{\"n\":1}".data(using: .utf8)!), ["n":1]) 51 | } 52 | 53 | func testEmptyCollectionDecoding() throws { 54 | XCTAssertEqual(try JSONDecoder().decode(JSON.self, from: "[]".data(using: .utf8)!), []) 55 | XCTAssertEqual(try JSONDecoder().decode(JSON.self, from: "{}".data(using: .utf8)!), [:]) 56 | } 57 | 58 | func testDebugDescriptions() { 59 | let fragments: [JSON] = ["foo", 1, true, nil] 60 | let descriptions = fragments.map { $0.debugDescription } 61 | XCTAssertEqual(descriptions, ["\"foo\"", "1.0", "true", "null"]) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /GenericJSONTests/EqualityTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import GenericJSON 3 | 4 | class EqualityTests: XCTestCase { 5 | 6 | func testEquality() { 7 | XCTAssertEqual([] as JSON, [] as JSON) 8 | XCTAssertEqual(nil as JSON, nil as JSON) 9 | XCTAssertEqual(1 as JSON, 1 as JSON) 10 | XCTAssertEqual(1 as JSON, 1.0 as JSON) 11 | XCTAssertEqual("foo" as JSON, "foo" as JSON) 12 | XCTAssertEqual(["foo": ["bar"]] as JSON, ["foo": ["bar"]] as JSON) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /GenericJSONTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 2.0.1 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /GenericJSONTests/InitializationTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import GenericJSON 3 | 4 | class InitializationTests: XCTestCase { 5 | 6 | func testLiteralInitialization() { 7 | XCTAssertEqual(nil as JSON, .null) 8 | XCTAssertEqual(true as JSON, .bool(true)) 9 | XCTAssertEqual([1, 2] as JSON, .array([.number(1), .number(2)])) 10 | XCTAssertEqual(["x": 1] as JSON, .object(["x": .number(1)])) 11 | XCTAssertEqual(3.4028236e+38 as JSON, .number(3.4028236e+38)) 12 | XCTAssertEqual("foo" as JSON, .string("foo")) 13 | } 14 | 15 | func testValueInitialization() throws { 16 | let num = 1 17 | let bool = true 18 | let str = "foo" 19 | let json = try JSON([ 20 | "a": [num, bool], 21 | "b": [str, [str], [str: bool]], 22 | ]) 23 | XCTAssertEqual(json, [ 24 | "a": [1, true], 25 | "b": ["foo", ["foo"], ["foo": true]], 26 | ]) 27 | } 28 | 29 | func testUnknownTypeInitialization() { 30 | XCTAssertThrowsError(try JSON(["foo": Date()])) 31 | } 32 | 33 | func testNilInitializationFromAny() throws { 34 | XCTAssertEqual(try JSON(Optional.none as Any), .null) 35 | XCTAssertEqual(try JSON(Optional.none as Any), .null) 36 | XCTAssertThrowsError(try JSON(Optional.some(Date()) as Any)) 37 | } 38 | 39 | func testNSNullInitialization() throws { 40 | XCTAssertEqual(try JSON(NSNull()), .null) 41 | } 42 | 43 | func testInitializationFromCodable() throws { 44 | 45 | struct Foo: Codable { 46 | var a: String = "foo" 47 | var b: Bool = true 48 | } 49 | 50 | let json = try JSON(encodable: Foo()) 51 | XCTAssertEqual(json, [ 52 | "a": "foo", 53 | "b": true, 54 | ]) 55 | } 56 | 57 | func testInitializationFromJSONSerilization() throws { 58 | 59 | let jsonData = """ 60 | { 61 | "array": [ 62 | 1, 63 | 2, 64 | 3 65 | ], 66 | "boolean": true, 67 | "null": null, 68 | "number": 1, 69 | "greatest_int": \(Int.max), 70 | "greatest_double": \(Double.greatestFiniteMagnitude), 71 | "object": { 72 | "a": "b" 73 | }, 74 | "string": "Hello World" 75 | } 76 | """.data(using: .utf8)! 77 | 78 | let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) 79 | let json = try JSON(jsonObject) 80 | 81 | XCTAssertEqual(json["array"]!, JSON.array([1, 2, 3])) 82 | XCTAssertEqual(json["boolean"]!, JSON.bool(true)) 83 | XCTAssertEqual(json["number"]!, JSON.number(1)) 84 | XCTAssertEqual(json["greatest_int"]!, JSON.number(Double(Int.max))) 85 | XCTAssertEqual(json["greatest_double"]!, JSON.number(Double.greatestFiniteMagnitude)) 86 | XCTAssertEqual(json["null"]!, JSON.null) 87 | XCTAssertEqual(json["object"]!, JSON.object(["a": "b"])) 88 | XCTAssertEqual(json["string"]!, JSON.string("Hello World")) 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /GenericJSONTests/MergingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import GenericJSON 3 | 4 | class MergingTests: XCTestCase { 5 | 6 | func testMerging() { 7 | let old: JSON = ["x": "y"] 8 | XCTAssertEqual(old.merging(with: [:]), old) // no change 9 | XCTAssertEqual(old.merging(with: ["a": "b"]), ["x": "y", "a": "b"]) // create 10 | XCTAssertEqual(old.merging(with: ["x": 1]), ["x": 1]) // update 11 | XCTAssertEqual(old.merging(with: ["x": nil]), ["x": nil]) // “delete” 12 | } 13 | 14 | func testMergingPrimitives() { 15 | XCTAssertEqual(JSON.number(2).merging(with: "foo"), "foo") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GenericJSONTests/QueryingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import GenericJSON 3 | 4 | class QueryingTests: XCTestCase { 5 | 6 | func testStringValue() { 7 | XCTAssertEqual(JSON.string("foo").stringValue, "foo") 8 | XCTAssertEqual(JSON.number(42).stringValue, nil) 9 | XCTAssertEqual(JSON.null.stringValue, nil) 10 | } 11 | 12 | func testFloatValue() { 13 | XCTAssertEqual(JSON.number(42).doubleValue, 42) 14 | XCTAssertEqual(JSON.string("foo").doubleValue, nil) 15 | XCTAssertEqual(JSON.null.doubleValue, nil) 16 | } 17 | 18 | func testBoolValue() { 19 | XCTAssertEqual(JSON.bool(true).boolValue, true) 20 | XCTAssertEqual(JSON.string("foo").boolValue, nil) 21 | XCTAssertEqual(JSON.null.boolValue, nil) 22 | } 23 | 24 | func testObjectValue() { 25 | XCTAssertEqual(JSON.object(["foo": "bar"]).objectValue, ["foo": JSON.string("bar")]) 26 | XCTAssertEqual(JSON.string("foo").objectValue, nil) 27 | XCTAssertEqual(JSON.null.objectValue, nil) 28 | } 29 | 30 | func testArrayValue() { 31 | XCTAssertEqual(JSON.array(["foo", "bar"]).arrayValue, [JSON.string("foo"), JSON.string("bar")]) 32 | XCTAssertEqual(JSON.string("foo").arrayValue, nil) 33 | XCTAssertEqual(JSON.null.arrayValue, nil) 34 | } 35 | 36 | func testNullValue() { 37 | XCTAssertEqual(JSON.null.isNull, true) 38 | XCTAssertEqual(JSON.string("foo").isNull, false) 39 | } 40 | 41 | func testArraySubscripting() { 42 | let json: JSON = ["foo", "bar"] 43 | XCTAssertEqual(json[0], JSON.string("foo")) 44 | XCTAssertEqual(json[-1], nil) 45 | XCTAssertEqual(json[2], nil) 46 | XCTAssertEqual(json["foo"], nil) 47 | } 48 | 49 | func testStringSubscripting() { 50 | let json: JSON = ["foo": "bar"] 51 | XCTAssertEqual(json["foo"], JSON.string("bar")) 52 | XCTAssertEqual(json[0], nil) 53 | XCTAssertEqual(json["nonesuch"], nil) 54 | } 55 | 56 | func testStringSubscriptingSugar() { 57 | let json: JSON = ["foo": "bar"] 58 | XCTAssertEqual(json.foo, JSON.string("bar")) 59 | XCTAssertEqual(json.nonesuch, nil) 60 | } 61 | 62 | func testKeyPath() { 63 | let json: JSON = [ 64 | "string": "foo bar", 65 | "boolean": true, 66 | "number": 123, 67 | "object": [ 68 | "str": "col", 69 | "arr": [1, 2, 3], 70 | "obj": [ 71 | "x": "rah", 72 | "y": "tar", 73 | "z": "yaz" 74 | ] 75 | ] 76 | ] 77 | XCTAssertEqual(json[keyPath: "string"], "foo bar") 78 | XCTAssertEqual(json[keyPath: "boolean"], true) 79 | XCTAssertEqual(json[keyPath: "number"], 123) 80 | XCTAssertEqual(json[keyPath: "object.str"], "col") 81 | XCTAssertEqual(json[keyPath: "object.arr"], [1, 2, 3]) 82 | XCTAssertEqual(json[keyPath: "object.obj.y"], "tar") 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tomáš Znamenáček 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: release trunk 2 | release: 3 | bundle exec fastlane release 4 | trunk: 5 | bundle exec fastlane trunk 6 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "GenericJSON", 6 | products: [ 7 | .library( 8 | name: "GenericJSON", 9 | targets: ["GenericJSON"]), 10 | ], 11 | dependencies: [], 12 | targets: [ 13 | .target( 14 | name: "GenericJSON", 15 | dependencies: [], 16 | path: "GenericJSON" 17 | ), 18 | .testTarget( 19 | name: "GenericJSONTests", 20 | dependencies: ["GenericJSON"], 21 | path: "GenericJSONTests" 22 | ) 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /Playground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import GenericJSON 3 | 4 | let json: JSON = [ 5 | "foo": "bar", 6 | "bar": 1, 7 | "empty": nil, 8 | ] 9 | 10 | let str = try String(data: try JSONEncoder().encode(json), encoding: .utf8)! 11 | -------------------------------------------------------------------------------- /Playground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generic JSON 2 | 3 | [![Build Status](https://travis-ci.org/zoul/generic-json-swift.svg?branch=master)](https://travis-ci.org/zoul/generic-json-swift) 4 | 5 | Generic JSON makes it easy to deal with freeform JSON strings without creating a separate, well-typed structure. 6 | 7 | ## Codable and freeform JSON 8 | 9 | Swift 4 introduced a new JSON encoding and decoding machinery represented by the `Codable` protocol. The feature is very nice and very type-safe, meaning it’s no longer possible to just willy-nilly decode a JSON string pulling random untyped data from it. Which is good™ most of the time – but what should you do when you _do_ want to just willy-nilly encode or decode a JSON string without introducing a separate, well-typed structure for it? For example: 10 | 11 | ```swift 12 | // error: heterogeneous collection literal could only be inferred to '[String : Any]'; 13 | // add explicit type annotation if this is intentional 14 | let json = [ 15 | "foo": "foo", 16 | "bar": 1, 17 | ] 18 | 19 | // Okay then: 20 | let json: [String:Any] = [ 21 | "foo": "foo", 22 | "bar": 1, 23 | ] 24 | 25 | // But: fatal error: Dictionary does not conform to Encodable because Any does not conform to Encodable. 26 | let encoded = try JSONEncoder().encode(json) 27 | ``` 28 | 29 | So this doesn’t work very well. Also, the `json` value can’t be checked for equality with another, although arbitrary JSON values _should_ support equality. Enter `JSON`. 30 | 31 | ## Usage 32 | 33 | ### Create a `JSON` structure 34 | 35 | ```swift 36 | let json: JSON = [ 37 | "foo": "foo", 38 | "bar": 1, 39 | ] 40 | 41 | // "{"bar":1,"foo":"foo"}" 42 | let str = try String(data: try JSONEncoder().encode(json), encoding: .utf8)! 43 | let hopefullyTrue = (json == json) // true! 44 | ``` 45 | 46 | ### Convert `Encodable` objects into a generic JSON structure 47 | 48 | ```swift 49 | struct Player: Codable { 50 | let name: String 51 | let swings: Bool 52 | } 53 | 54 | let val = try JSON(encodable: Player(name: "Miles", swings: true)) 55 | val == [ 56 | "name": "Miles", 57 | "swings": true, 58 | ] // true 59 | ``` 60 | 61 | ### Query Values 62 | 63 | Consider the following `JSON` structure: 64 | 65 | ```swift 66 | let json: JSON = [ 67 | "num": 1, 68 | "str": "baz", 69 | "bool": true, 70 | "obj": [ 71 | "foo": "jar", 72 | "bar": 1, 73 | ] 74 | ] 75 | ``` 76 | 77 | Querying values can be done using optional property accessors, subscripting or dynamic member subscripting: 78 | 79 | ```swift 80 | // Property accessors 81 | if let str = json.objectValue?["str"]?.stringValue { … } 82 | if let foo = json.objectValue?["obj"]?.objectValue?["foo"]?.stringValue { … } 83 | 84 | // Subscripting 85 | if let str = json["str"]?.stringValue { … } 86 | if let foo = json["obj"]?["foo"]?.stringValue { … } 87 | 88 | // Dynamic member subscripting 89 | if let str = json.str?.stringValue { … } 90 | if let foo = json.obj?.foo?.stringValue { … } 91 | ``` 92 | 93 | You may even drill through nested structures using a dot-separated key path: 94 | 95 | ```swift 96 | let val = json[keyPath: "obj.foo"] // "jar" 97 | ``` 98 | --------------------------------------------------------------------------------