├── .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 | [](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 |
--------------------------------------------------------------------------------