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