├── .github
└── FUNDING.yml
├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── contents.xcworkspacedata
│ └── xcshareddata
│ └── xcschemes
│ └── ZippyJSON.xcscheme
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── ZippyJSON
│ └── ZippyJSONDecoder.swift
├── Tests
└── ZippyJSONTests
│ ├── AppleJSONDecoderTests.swift
│ ├── CodableMultifile.swift
│ ├── CodableTests.swift
│ ├── Info.plist
│ ├── JSONTests.swift
│ ├── Models
│ ├── apache_builds.json
│ ├── apache_builds.swift
│ ├── canada.json
│ ├── canada.swift
│ ├── entities.json
│ ├── entities.swift
│ ├── github_events.json
│ ├── github_events.swift
│ ├── instruments.json
│ ├── json_to_swift.rb
│ ├── marine_ik.json
│ ├── mesh.json
│ ├── mesh.swift
│ ├── numbers.json
│ ├── random.json
│ ├── random.swift
│ ├── twitter.json
│ ├── twitter.swift
│ ├── twitter2.json
│ ├── twitterCamel.swift
│ ├── twitterescaped.json
│ └── user.swift
│ ├── StdlibUnittest.swift
│ ├── StickyEncodingTests
│ ├── BinaryDecoderNegativeTests.swift
│ ├── BinaryEncoderNegativeTests.swift
│ ├── BinaryEncodingKeyedContainerNegativeTests.swift
│ ├── BinaryEncodingKeyedContainerTests.swift
│ ├── BinaryEncodingSingleValueContainerNegativeTests.swift
│ ├── BinaryEncodingStructuredTypeTests.swift
│ ├── BinaryEncodingUnkeyedContainerNegativeTests.swift
│ ├── BinaryEncodingUnkeyedContainerTests.swift
│ ├── DocumentationExampleTests.swift
│ ├── EncodeDecodeTester.swift
│ └── TestFixtures
│ │ ├── BasicType.swift
│ │ ├── CodableType.swift
│ │ ├── ComplexSubclassClass.swift
│ │ ├── ComplexType.swift
│ │ └── EquatableType.swift
│ ├── Tests.swift
│ ├── XCTestManifests.swift
│ └── ZippyJSONDecoderTests.swift
├── ZippyJSON.podspec
└── misc
├── chart.svg
├── snakify.rb
├── template.swift
└── template_generation.rb
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [michaeleisel]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 | /.build
22 | /Packages
23 | *.xcodeproj
24 |
25 | # Bundler
26 | .bundle
27 |
28 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
29 | # Carthage/Checkouts
30 |
31 | Carthage/Build
32 |
33 | gen/
34 |
35 | # We recommend against adding the Pods directory to your .gitignore. However
36 | # you should judge for yourself, the pros and cons are mentioned at:
37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
38 | #
39 | # Note: if you ignore the Pods directory, make sure to uncomment
40 | # `pod install` in .travis.yml
41 | #
42 | # Pods/
43 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/ZippyJSON.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
54 |
55 |
61 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Tests were used from the swift project which is licensed under the Apache 2.0 license, see https://github.com/apple/swift/blob/7b654b372f/LICENSE.txt
2 | Tests were used from IkigaJSON which is licensed under the MIT license, see https://github.com/Ikiga/IkigaJSON/blob/master/LICENSE
3 | Tests were used from the sticky-encoding project which is licensed under the Apache 2.0 license, see https://github.com/stickytools/sticky-encoding/blob/ba9ec3ffef7/LICENSE
4 |
5 | For everything else:
6 |
7 | Copyright (c) 2019 Michael Eisel
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy
10 | of this software and associated documentation files (the "Software"), to deal
11 | in the Software without restriction, including without limitation the rights
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | copies of the Software, and to permit persons to whom the Software is
14 | furnished to do so, subject to the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be included in
17 | all copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "jjliso8601dateformatter",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/michaeleisel/JJLISO8601DateFormatter",
7 | "state" : {
8 | "revision" : "ed1d996123688bade6e895aa49595f0d862900e7",
9 | "version" : "0.1.7"
10 | }
11 | },
12 | {
13 | "identity" : "zippyjsoncfamily",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/michaeleisel/ZippyJSONCFamily",
16 | "state" : {
17 | "revision" : "8abdd7a5e943afe68e7b03fdaa63b21c042a3893",
18 | "version" : "1.2.9"
19 | }
20 | }
21 | ],
22 | "version" : 2
23 | }
24 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.7
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "ZippyJSON",
7 | platforms: [
8 | .iOS(.v11),
9 | .tvOS(.v11),
10 | .macOS(.v10_13),
11 | ],
12 | products: [
13 | .library(
14 | name: "ZippyJSON",
15 | targets: ["ZippyJSON"]),
16 | ],
17 | dependencies: [
18 | .package(url: "https://github.com/michaeleisel/JJLISO8601DateFormatter", from: "0.1.7"),
19 | .package(url: "https://github.com/michaeleisel/ZippyJSONCFamily", exact: "1.2.14"),
20 | ],
21 | targets: [
22 | .target(
23 | name: "ZippyJSON",
24 | dependencies: ["ZippyJSONCFamily", "JJLISO8601DateFormatter"]),
25 | .testTarget(
26 | name: "ZippyJSONTests",
27 | dependencies: ["ZippyJSON"],
28 | resources: [
29 | .copy("Models/apache_builds.json"),
30 | .copy("Models/canada.json"),
31 | .copy("Models/entities.json"),
32 | .copy("Models/github_events.json"),
33 | .copy("Models/marine_ik.json"),
34 | .copy("Models/mesh.json"),
35 | .copy("Models/numbers.json"),
36 | .copy("Models/random.json"),
37 | .copy("Models/twitter.json"),
38 | .copy("Models/twitter2.json"),
39 | .copy("Models/twitterescaped.json")
40 | ])
41 | ]
42 | )
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ZippyJSON
2 | ## A much faster version of JSONDecoder
3 | 
4 | [](https://cocoapods.com)
5 | [](https://swift.org/package-manager/)
6 |
7 | ### Note: JSONDecoder is faster than ZippyJSON for iOS 17+. The rest of this document describes the performance difference pre-iOS 17.
8 |
9 | ## Benchmarks
10 |
11 |
12 |
13 | These benchmarks were done on a Macbook Pro. The results are very similar on the iPhone (ZippyJSON is 3x+ faster for all 3 files on both platforms).
14 |
15 | ## Usage
16 |
17 | Just replace `JSONDecoder` with `ZippyJSONDecoder` wherever you want to use it. So instead of `let decoder = JSONDecoder()`, do `let decoder = ZippyJSONDecoder()`, and everything will just work. This is because `ZippyJSONDecoder` has the exact same API as `JSONDecoder` (i.e. it's drop-in). Also, don't forget to add `import ZippyJSON` in files where you use it.
18 |
19 | #### *NOTE: when measuring the speed of ZippyJSON, make sure you're building for release*
20 |
21 | ## Why is it so much faster?
22 |
23 | - Apple's version first converts the JSON into an `NSDictionary` using `NSJSONSerialization` and then afterwards makes things Swifty. The creation of that intermediate dictionary is expensive.
24 | - ZippyJSON is built largely in C++ (but still with a Swift interface wrapped around it). For the initial parsing (you might call it tokenizing), it uses [simdjson](https://github.com/lemire/simdjson), a very fast library that makes good use of vectorization. Apple, on the other hand, uses entirely [Swift](https://github.com/apple/swift/blob/master/stdlib/public/Darwin/Foundation/JSONEncoder.swift) (aside from the use of `NSJSONSerialization`) which is generally slower.
25 | - There are many specific optimizations in there as well. For example, date parsing for ISO-8601 dates is 10x faster due to using JJLISO8601DateFormatter instead of Apple's date formatter.
26 |
27 | So, it's largely due to Apple trying to be elegant and operate at a higher level.
28 |
29 | ## When should you use this library?
30 |
31 | At first, default to using `JSONDecoder`. It's very battle-tested, and for plenty of use cases is just fine. Then, once you start looking for new things to optimize, take a look at how long your JSON parsing is taking. After all, JSON parsing can be a bottleneck for getting data to the user. As a rule of thumb, divide its current time taken by 4 to approximate the time taken with ZippyJSON. If that difference is significant to you (and even milliseconds can impact a user experience!), then consider using ZippyJSON.
32 |
33 | ## Future improvements
34 |
35 | There are still many places in the code that are ripe for optimization. Feel free to submit a ticket if you have a specific case where you need more performant JSON parsing, and where ZippyJSON is not already 4x faster than Apple's. JSONEncoder and NSJSONSerialization are also promising for optimzation, please chime in if you need one of these improved.
36 |
37 | ## Installation
38 |
39 | ### Cocoapods
40 |
41 | ZippyJSON is available through [CocoaPods](https://cocoapods.org) (SPM support is in the works). To install
42 | it, simply add the following line to your Podfile:
43 |
44 | ```ruby
45 | pod 'ZippyJSON'
46 | ```
47 |
48 | You can also make it `pod 'ZippyJSON', :inhibit_warnings => true` if you want to suppress all warnings.
49 |
50 | ### SwiftPM (iOS only)
51 |
52 | Add the package in the SwiftPM packages area with repository URL https://github.com/michaeleisel/ZippyJSON
53 |
54 | ## Author
55 |
56 | Michael Eisel, michael.eisel@gmail.com
57 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/CodableMultifile.swift:
--------------------------------------------------------------------------------
1 | // RUN: %empty-directory(%t)
2 | // RUN: cp %s %t/main.swift
3 | // RUN: %target-build-swift %t/main.swift %S/Inputs/CodableMultifileOther.swift -module-name main -o %t/main
4 | // RUN: %target-codesign %t/main
5 | // RUN: %target-run %t/main
6 | // REQUIRES: executable_test
7 |
8 | // FIXME: This test could run on Linux too, if we could either use
9 | // corelibs-foundation, or implement a mock Encoder for testing.
10 |
11 | // REQUIRES: objc_interop
12 |
13 | /*import StdlibUnittest
14 | import Foundation
15 |
16 | var CodableMultifileTestSuite = TestSuite("CodableMultifile")
17 |
18 | // The first test doesn't synthesize encode(to:) at all.
19 | CodableMultifileTestSuite.test("1") {
20 | let derived = DerivedFirst()
21 |
22 | // Make sure the vtable offset matches between this translation unit and
23 | // the other file, which requires us to force the encode(to:) member when
24 | // type checking this translation unit.
25 | expectEqual(false, derived.derivedMember)
26 | }
27 |
28 | // The second test synthesizes init(from:) before encode(to:).
29 |
30 | // We define a wrapper so that the virtual method BaseSecond.encode(to:) method
31 | // is called from outside its module. If we use the conformance of BaseSecond
32 | // to Codable, we don't expose the bug because the virtual method call is
33 | // encapsulated in the conformance, which is emitted in the same translation unit
34 | // as BaseSecond.
35 | struct WrapperSecond : Encodable {
36 | let base: BaseSecond
37 |
38 | func encode(to encoder: Encoder) throws {
39 | try base.encode(to: encoder)
40 | }
41 | }
42 |
43 | CodableMultifileTestSuite.test("2") {
44 | // Make sure we synthesize the init(from:) member before encode(to:) in
45 | // this translation unit.
46 | _ = BaseSecond.init(from:)
47 |
48 | let base = WrapperSecond(base: BaseSecond())
49 | let encoder = JSONEncoder()
50 |
51 | expectEqual(
52 | "{\"baseMember\":2}",
53 | String(data: try! encoder.encode(base), encoding: .utf8)!)
54 | }
55 |
56 | // The third test synthesizes encode(to:) before init(from:).
57 |
58 | // See above.
59 | struct WrapperThird : Encodable {
60 | let base: BaseThird
61 |
62 | func encode(to encoder: Encoder) throws {
63 | try base.encode(to: encoder)
64 | }
65 | }
66 |
67 | CodableMultifileTestSuite.test("3") {
68 | // Make sure we synthesize the encode(to:) member before init(from:) in
69 | // this translation unit.
70 | _ = BaseThird.encode(to:)
71 |
72 | let base = WrapperThird(base: BaseThird())
73 | let encoder = JSONEncoder()
74 |
75 | expectEqual(
76 | "{\"baseMember\":3}",
77 | String(data: try! encoder.encode(base), encoding: .utf8)!)
78 | }
79 |
80 | runAllTests()*/
81 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/CodableTests.swift:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
2 | // Licensed under Apache License v2.0 with Runtime Library Exception
3 | //
4 | // See https://swift.org/LICENSE.txt for license information
5 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
6 | //
7 | //===----------------------------------------------------------------------===//
8 | //
9 | // RUN: %target-run-simple-swift
10 | // REQUIRES: executable_test
11 | // REQUIRES: objc_interop
12 | // REQUIRES: rdar49026133
13 |
14 | import Foundation
15 | import CoreGraphics
16 | import ZippyJSON
17 |
18 | import XCTest
19 | class TestCodableSuper : XCTestCase { }
20 |
21 | // MARK: - Helper Functions
22 | @available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *)
23 | func makePersonNameComponents(namePrefix: String? = nil,
24 | givenName: String? = nil,
25 | middleName: String? = nil,
26 | familyName: String? = nil,
27 | nameSuffix: String? = nil,
28 | nickname: String? = nil) -> PersonNameComponents {
29 | var result = PersonNameComponents()
30 | result.namePrefix = namePrefix
31 | result.givenName = givenName
32 | result.middleName = middleName
33 | result.familyName = familyName
34 | result.nameSuffix = nameSuffix
35 | result.nickname = nickname
36 | return result
37 | }
38 |
39 | func testDescription(_ value: T) -> String {
40 | if let debugDescribable = value as? CustomDebugStringConvertible {
41 | return debugDescribable.debugDescription
42 | } else if let describable = value as? CustomStringConvertible {
43 | return describable.description
44 | } else {
45 | return "\(value)"
46 | }
47 | }
48 |
49 | func performEncodeAndDecode(of value: T, encode: (T) throws -> Data, decode: (T.Type, Data) throws -> T, lineNumber: Int) -> T {
50 |
51 | let data: Data
52 | do {
53 | data = try encode(value)
54 | } catch {
55 | fatalError("\(#file):\(lineNumber): Unable to encode \(T.self) <\(testDescription(value))>: \(error)")
56 | }
57 |
58 | do {
59 | return try decode(T.self, data)
60 | } catch {
61 | fatalError("\(#file):\(lineNumber): Unable to decode \(T.self) <\(testDescription(value))>: \(error)")
62 | }
63 | }
64 |
65 | func expectRoundTripEquality(of value: T, encode: (T) throws -> Data, decode: (T.Type, Data) throws -> T, lineNumber: Int) where T : Equatable {
66 |
67 | let decoded = performEncodeAndDecode(of: value, encode: encode, decode: decode, lineNumber: lineNumber)
68 |
69 | expectEqual(value, decoded, "\(#file):\(lineNumber): Decoded \(T.self) <\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
70 | }
71 |
72 | func expectRoundTripEqualityThroughJSON(for value: T, lineNumber: Int) where T : Equatable {
73 | let inf = "INF", negInf = "-INF", nan = "NaN"
74 | let encode = { (_ value: T) throws -> Data in
75 | let encoder = JSONEncoder()
76 | encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: inf,
77 | negativeInfinity: negInf,
78 | nan: nan)
79 | return try encoder.encode(value)
80 | }
81 |
82 | let decode = { (_ type: T.Type, _ data: Data) throws -> T in
83 | let decoder = ZippyJSONDecoder()
84 | decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: inf,
85 | negativeInfinity: negInf,
86 | nan: nan)
87 | return try decoder.decode(type, from: data)
88 | }
89 |
90 | expectRoundTripEquality(of: value, encode: encode, decode: decode, lineNumber: lineNumber)
91 | }
92 |
93 | func expectRoundTripEqualityThroughPlist(for value: T, lineNumber: Int) where T : Equatable {
94 | // no-op, just a remnant of an apple test
95 | }
96 |
97 | // MARK: - Helper Types
98 | // A wrapper around a UUID that will allow it to be encoded at the top level of an encoder.
99 | struct UUIDCodingWrapper : Codable, Equatable {
100 | let value: UUID
101 |
102 | init(_ value: UUID) {
103 | self.value = value
104 | }
105 |
106 | static func ==(_ lhs: UUIDCodingWrapper, _ rhs: UUIDCodingWrapper) -> Bool {
107 | return lhs.value == rhs.value
108 | }
109 | }
110 |
111 | // MARK: - Tests
112 | class TestCodable : TestCodableSuper {
113 | // MARK: - AffineTransform
114 | #if os(macOS)
115 | lazy var affineTransformValues: [Int : AffineTransform] = [
116 | #line : AffineTransform.identity,
117 | #line : AffineTransform(),
118 | #line : AffineTransform(translationByX: 2.0, byY: 2.0),
119 | #line : AffineTransform(scale: 2.0),
120 | #line : AffineTransform(rotationByDegrees: .pi / 2),
121 |
122 | #line : AffineTransform(m11: 1.0, m12: 2.5, m21: 66.2, m22: 40.2, tX: -5.5, tY: 3.7),
123 | #line : AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33),
124 | #line : AffineTransform(m11: 4.5, m12: 1.1, m21: 0.025, m22: 0.077, tX: -0.55, tY: 33.2),
125 | #line : AffineTransform(m11: 7.0, m12: -2.3, m21: 6.7, m22: 0.25, tX: 0.556, tY: 0.99),
126 | #line : AffineTransform(m11: 0.498, m12: -0.284, m21: -0.742, m22: 0.3248, tX: 12, tY: 44)
127 | ]
128 |
129 | func test_AffineTransform_JSON() {
130 | for (testLine, transform) in affineTransformValues {
131 | expectRoundTripEqualityThroughJSON(for: transform, lineNumber: testLine)
132 | }
133 | }
134 |
135 | func test_AffineTransform_Plist() {
136 | for (testLine, transform) in affineTransformValues {
137 | expectRoundTripEqualityThroughPlist(for: transform, lineNumber: testLine)
138 | }
139 | }
140 | #endif
141 |
142 | // MARK: - Calendar
143 | lazy var calendarValues: [Int : Calendar] = [
144 | #line : Calendar(identifier: .gregorian),
145 | #line : Calendar(identifier: .buddhist),
146 | #line : Calendar(identifier: .chinese),
147 | #line : Calendar(identifier: .coptic),
148 | #line : Calendar(identifier: .ethiopicAmeteMihret),
149 | #line : Calendar(identifier: .ethiopicAmeteAlem),
150 | #line : Calendar(identifier: .hebrew),
151 | #line : Calendar(identifier: .iso8601),
152 | #line : Calendar(identifier: .indian),
153 | #line : Calendar(identifier: .islamic),
154 | #line : Calendar(identifier: .islamicCivil),
155 | #line : Calendar(identifier: .japanese),
156 | #line : Calendar(identifier: .persian),
157 | #line : Calendar(identifier: .republicOfChina),
158 | ]
159 |
160 | func test_Calendar_JSON() {
161 | for (testLine, calendar) in calendarValues {
162 | expectRoundTripEqualityThroughJSON(for: calendar, lineNumber: testLine)
163 | }
164 | }
165 |
166 | func test_Calendar_Plist() {
167 | for (testLine, calendar) in calendarValues {
168 | expectRoundTripEqualityThroughPlist(for: calendar, lineNumber: testLine)
169 | }
170 | }
171 |
172 | // MARK: - CharacterSet
173 | lazy var characterSetValues: [Int : CharacterSet] = [
174 | #line : CharacterSet.controlCharacters,
175 | #line : CharacterSet.whitespaces,
176 | #line : CharacterSet.whitespacesAndNewlines,
177 | #line : CharacterSet.decimalDigits,
178 | #line : CharacterSet.letters,
179 | #line : CharacterSet.lowercaseLetters,
180 | #line : CharacterSet.uppercaseLetters,
181 | #line : CharacterSet.nonBaseCharacters,
182 | #line : CharacterSet.alphanumerics,
183 | #line : CharacterSet.decomposables,
184 | #line : CharacterSet.illegalCharacters,
185 | #line : CharacterSet.punctuationCharacters,
186 | #line : CharacterSet.capitalizedLetters,
187 | #line : CharacterSet.symbols,
188 | #line : CharacterSet.newlines
189 | ]
190 |
191 | func test_CharacterSet_JSON() {
192 | for (testLine, characterSet) in characterSetValues {
193 | expectRoundTripEqualityThroughJSON(for: characterSet, lineNumber: testLine)
194 | }
195 | }
196 |
197 | func test_CharacterSet_Plist() {
198 | for (testLine, characterSet) in characterSetValues {
199 | expectRoundTripEqualityThroughPlist(for: characterSet, lineNumber: testLine)
200 | }
201 | }
202 |
203 | // MARK: - CGAffineTransform
204 | lazy var cg_affineTransformValues: [Int : CGAffineTransform] = {
205 | var values = [
206 | #line : CGAffineTransform.identity,
207 | #line : CGAffineTransform(),
208 | #line : CGAffineTransform(translationX: 2.0, y: 2.0),
209 | #line : CGAffineTransform(scaleX: 2.0, y: 2.0),
210 | #line : CGAffineTransform(a: 1.0, b: 2.5, c: 66.2, d: 40.2, tx: -5.5, ty: 3.7),
211 | #line : CGAffineTransform(a: -55.66, b: 22.7, c: 1.5, d: 0.0, tx: -22, ty: -33),
212 | #line : CGAffineTransform(a: 4.5, b: 1.1, c: 0.025, d: 0.077, tx: -0.55, ty: 33.2),
213 | #line : CGAffineTransform(a: 7.0, b: -2.3, c: 6.7, d: 0.25, tx: 0.556, ty: 0.99),
214 | #line : CGAffineTransform(a: 0.498, b: -0.284, c: -0.742, d: 0.3248, tx: 12, ty: 44)
215 | ]
216 |
217 | if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
218 | values[#line] = CGAffineTransform(rotationAngle: .pi / 2)
219 | }
220 |
221 | return values
222 | }()
223 |
224 | func test_CGAffineTransform_JSON() {
225 | for (testLine, transform) in cg_affineTransformValues {
226 | expectRoundTripEqualityThroughJSON(for: transform, lineNumber: testLine)
227 | }
228 | }
229 |
230 | func test_CGAffineTransform_Plist() {
231 | for (testLine, transform) in cg_affineTransformValues {
232 | expectRoundTripEqualityThroughPlist(for: transform, lineNumber: testLine)
233 | }
234 | }
235 |
236 | // MARK: - CGPoint
237 | lazy var cg_pointValues: [Int : CGPoint] = {
238 | var values = [
239 | #line : CGPoint.zero,
240 | #line : CGPoint(x: 10, y: 20)
241 | ]
242 |
243 | if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
244 | // Limit on magnitude in JSON. See rdar://problem/12717407
245 | values[#line] = CGPoint(x: CGFloat.greatestFiniteMagnitude,
246 | y: CGFloat.greatestFiniteMagnitude)
247 | }
248 |
249 | return values
250 | }()
251 |
252 | func test_CGPoint_JSON() {
253 | for (testLine, point) in cg_pointValues {
254 | expectRoundTripEqualityThroughJSON(for: point, lineNumber: testLine)
255 | }
256 | }
257 |
258 | func test_CGPoint_Plist() {
259 | for (testLine, point) in cg_pointValues {
260 | expectRoundTripEqualityThroughPlist(for: point, lineNumber: testLine)
261 | }
262 | }
263 |
264 | // MARK: - CGSize
265 | lazy var cg_sizeValues: [Int : CGSize] = {
266 | var values = [
267 | #line : CGSize.zero,
268 | #line : CGSize(width: 30, height: 40)
269 | ]
270 |
271 | if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
272 | // Limit on magnitude in JSON. See rdar://problem/12717407
273 | values[#line] = CGSize(width: CGFloat.greatestFiniteMagnitude,
274 | height: CGFloat.greatestFiniteMagnitude)
275 | }
276 |
277 | return values
278 | }()
279 |
280 | func test_CGSize_JSON() {
281 | for (testLine, size) in cg_sizeValues {
282 | expectRoundTripEqualityThroughJSON(for: size, lineNumber: testLine)
283 | }
284 | }
285 |
286 | func test_CGSize_Plist() {
287 | for (testLine, size) in cg_sizeValues {
288 | expectRoundTripEqualityThroughPlist(for: size, lineNumber: testLine)
289 | }
290 | }
291 |
292 | // MARK: - CGRect
293 | lazy var cg_rectValues: [Int : CGRect] = {
294 | var values = [
295 | #line : CGRect.zero,
296 | #line : CGRect.null,
297 | #line : CGRect(x: 10, y: 20, width: 30, height: 40)
298 | ]
299 |
300 | if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
301 | // Limit on magnitude in JSON. See rdar://problem/12717407
302 | values[#line] = CGRect.infinite
303 | }
304 |
305 | return values
306 | }()
307 |
308 | func test_CGRect_JSON() {
309 | for (testLine, rect) in cg_rectValues {
310 | expectRoundTripEqualityThroughJSON(for: rect, lineNumber: testLine)
311 | }
312 | }
313 |
314 | func test_CGRect_Plist() {
315 | for (testLine, rect) in cg_rectValues {
316 | expectRoundTripEqualityThroughPlist(for: rect, lineNumber: testLine)
317 | }
318 | }
319 |
320 | // MARK: - CGVector
321 | lazy var cg_vectorValues: [Int : CGVector] = {
322 | var values = [
323 | #line : CGVector.zero,
324 | #line : CGVector(dx: 0.0, dy: -9.81)
325 | ]
326 |
327 | if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
328 | // Limit on magnitude in JSON. See rdar://problem/12717407
329 | values[#line] = CGVector(dx: CGFloat.greatestFiniteMagnitude,
330 | dy: CGFloat.greatestFiniteMagnitude)
331 | }
332 |
333 | return values
334 | }()
335 |
336 | func test_CGVector_JSON() {
337 | for (testLine, vector) in cg_vectorValues {
338 | expectRoundTripEqualityThroughJSON(for: vector, lineNumber: testLine)
339 | }
340 | }
341 |
342 | func test_CGVector_Plist() {
343 | for (testLine, vector) in cg_vectorValues {
344 | expectRoundTripEqualityThroughPlist(for: vector, lineNumber: testLine)
345 | }
346 | }
347 |
348 | // MARK: - ClosedRange
349 | func test_ClosedRange_JSON() {
350 | let value = 0...Int.max
351 | let decoded = performEncodeAndDecode(of: value, encode: { try JSONEncoder().encode($0) }, decode: { try ZippyJSONDecoder().decode($0, from: $1) }, lineNumber: #line)
352 | expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded ClosedRange upperBound <\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
353 | expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded ClosedRange lowerBound <\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
354 | }
355 |
356 | func test_ClosedRange_Plist() {
357 | let value = 0...Int.max
358 | let decoded = performEncodeAndDecode(of: value, encode: { try PropertyListEncoder().encode($0) }, decode: { try PropertyListDecoder().decode($0, from: $1) }, lineNumber: #line)
359 | expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded ClosedRange upperBound <\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
360 | expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded ClosedRange lowerBound <\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
361 | }
362 |
363 | // MARK: - ContiguousArray
364 | lazy var contiguousArrayValues: [Int : ContiguousArray] = [
365 | #line : [],
366 | #line : ["foo"],
367 | #line : ["foo", "bar"],
368 | #line : ["foo", "bar", "baz"],
369 | ]
370 |
371 | func test_ContiguousArray_JSON() {
372 | for (testLine, contiguousArray) in contiguousArrayValues {
373 | expectRoundTripEqualityThroughJSON(for: contiguousArray, lineNumber: testLine)
374 | }
375 | }
376 |
377 | func test_ContiguousArray_Plist() {
378 | for (testLine, contiguousArray) in contiguousArrayValues {
379 | expectRoundTripEqualityThroughPlist(for: contiguousArray, lineNumber: testLine)
380 | }
381 | }
382 |
383 | // MARK: - DateComponents
384 | lazy var dateComponents: Set = [
385 | .era, .year, .month, .day, .hour, .minute, .second, .nanosecond,
386 | .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear,
387 | .yearForWeekOfYear, .timeZone, .calendar
388 | ]
389 |
390 | func test_DateComponents_JSON() {
391 | let calendar = Calendar(identifier: .gregorian)
392 | let components = calendar.dateComponents(dateComponents, from: Date())
393 | expectRoundTripEqualityThroughJSON(for: components, lineNumber: #line - 1)
394 | }
395 |
396 | func test_DateComponents_Plist() {
397 | let calendar = Calendar(identifier: .gregorian)
398 | let components = calendar.dateComponents(dateComponents, from: Date())
399 | expectRoundTripEqualityThroughPlist(for: components, lineNumber: #line - 1)
400 | }
401 |
402 | // MARK: - DateInterval
403 | @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
404 | lazy var dateIntervalValues: [Int : DateInterval] = [
405 | #line : DateInterval(),
406 | #line : DateInterval(start: Date.distantPast, end: Date()),
407 | #line : DateInterval(start: Date(), end: Date.distantFuture),
408 | #line : DateInterval(start: Date.distantPast, end: Date.distantFuture)
409 | ]
410 |
411 | @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
412 | func test_DateInterval_JSON() {
413 | for (testLine, interval) in dateIntervalValues {
414 | expectRoundTripEqualityThroughJSON(for: interval, lineNumber: testLine)
415 | }
416 | }
417 |
418 | @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
419 | func test_DateInterval_Plist() {
420 | for (testLine, interval) in dateIntervalValues {
421 | expectRoundTripEqualityThroughPlist(for: interval, lineNumber: testLine)
422 | }
423 | }
424 |
425 | // MARK: - Decimal
426 | lazy var decimalValues: [Int : Decimal] = [
427 | #line : Decimal.leastFiniteMagnitude,
428 | #line : Decimal.greatestFiniteMagnitude,
429 | #line : Decimal.leastNormalMagnitude,
430 | #line : Decimal.leastNonzeroMagnitude,
431 | #line : 0,
432 | #line : 1,
433 | #line : 12.34,
434 | // #line : Decimal(),
435 |
436 | #line : Decimal.pi,
437 | ]
438 |
439 | func test_Decimal_JSON() {
440 | for (testLine, decimal) in decimalValues {
441 | // Decimal encodes as a number in JSON and cannot be encoded at the top level.
442 | expectRoundTripEqualityThroughJSON(for: TopLevelWrapper(decimal), lineNumber: testLine)
443 | }
444 | }
445 |
446 | func test_Decimal_Plist() {
447 | for (testLine, decimal) in decimalValues {
448 | expectRoundTripEqualityThroughPlist(for: decimal, lineNumber: testLine)
449 | }
450 | }
451 |
452 | // MARK: - IndexPath
453 | lazy var indexPathValues: [Int : IndexPath] = [
454 | #line : IndexPath(), // empty
455 | #line : IndexPath(index: 0), // single
456 | #line : IndexPath(indexes: [1, 2]), // pair
457 | #line : IndexPath(indexes: [3, 4, 5, 6, 7, 8]), // array
458 | ]
459 |
460 | func test_IndexPath_JSON() {
461 | for (testLine, indexPath) in indexPathValues {
462 | expectRoundTripEqualityThroughJSON(for: indexPath, lineNumber: testLine)
463 | }
464 | }
465 |
466 | func test_IndexPath_Plist() {
467 | for (testLine, indexPath) in indexPathValues {
468 | expectRoundTripEqualityThroughPlist(for: indexPath, lineNumber: testLine)
469 | }
470 | }
471 |
472 | // MARK: - IndexSet
473 | lazy var indexSetValues: [Int : IndexSet] = [
474 | #line : IndexSet(),
475 | #line : IndexSet(integer: 42),
476 | ]
477 | lazy var indexSetMaxValues: [Int : IndexSet] = [
478 | #line : IndexSet(integersIn: 0 ..< Int.max)
479 | ]
480 |
481 | func test_IndexSet_JSON() {
482 | for (testLine, indexSet) in indexSetValues {
483 | expectRoundTripEqualityThroughJSON(for: indexSet, lineNumber: testLine)
484 | }
485 | if #available(macOS 10.10, iOS 8, *) {
486 | // Mac OS X 10.9 and iOS 7 weren't able to round-trip Int.max in JSON.
487 | for (testLine, indexSet) in indexSetMaxValues {
488 | expectRoundTripEqualityThroughJSON(for: indexSet, lineNumber: testLine)
489 | }
490 | }
491 | }
492 |
493 | func test_IndexSet_Plist() {
494 | for (testLine, indexSet) in indexSetValues {
495 | expectRoundTripEqualityThroughPlist(for: indexSet, lineNumber: testLine)
496 | }
497 | for (testLine, indexSet) in indexSetMaxValues {
498 | expectRoundTripEqualityThroughPlist(for: indexSet, lineNumber: testLine)
499 | }
500 | }
501 |
502 | // MARK: - Locale
503 | lazy var localeValues: [Int : Locale] = [
504 | #line : Locale(identifier: ""),
505 | #line : Locale(identifier: "en"),
506 | #line : Locale(identifier: "en_US"),
507 | #line : Locale(identifier: "en_US_POSIX"),
508 | #line : Locale(identifier: "uk"),
509 | #line : Locale(identifier: "fr_FR"),
510 | #line : Locale(identifier: "fr_BE"),
511 | #line : Locale(identifier: "zh-Hant-HK")
512 | ]
513 |
514 | func test_Locale_JSON() {
515 | for (testLine, locale) in localeValues {
516 | expectRoundTripEqualityThroughJSON(for: locale, lineNumber: testLine)
517 | }
518 | }
519 |
520 | func test_Locale_Plist() {
521 | for (testLine, locale) in localeValues {
522 | expectRoundTripEqualityThroughPlist(for: locale, lineNumber: testLine)
523 | }
524 | }
525 |
526 | // MARK: - NSRange
527 | lazy var nsrangeValues: [Int : NSRange] = [
528 | #line : NSRange(),
529 | #line : NSRange(location: 5, length: 20),
530 | ]
531 | lazy var nsrangeMaxValues: [Int : NSRange] = [
532 | #line : NSRange(location: 0, length: Int.max),
533 | #line : NSRange(location: NSNotFound, length: 0),
534 | ]
535 |
536 | func test_NSRange_JSON() {
537 | for (testLine, range) in nsrangeValues {
538 | expectRoundTripEqualityThroughJSON(for: range, lineNumber: testLine)
539 | }
540 | if #available(macOS 10.10, iOS 8, *) {
541 | // Mac OS X 10.9 and iOS 7 weren't able to round-trip Int.max in JSON.
542 | for (testLine, range) in nsrangeMaxValues {
543 | expectRoundTripEqualityThroughJSON(for: range, lineNumber: testLine)
544 | }
545 | }
546 | }
547 |
548 | func test_NSRange_Plist() {
549 | for (testLine, range) in nsrangeValues {
550 | expectRoundTripEqualityThroughPlist(for: range, lineNumber: testLine)
551 | }
552 | for (testLine, range) in nsrangeMaxValues {
553 | expectRoundTripEqualityThroughPlist(for: range, lineNumber: testLine)
554 | }
555 | }
556 |
557 | // MARK: - PartialRangeFrom
558 | func test_PartialRangeFrom_JSON() {
559 | let value = 0...
560 | let decoded = performEncodeAndDecode(of: value, encode: { try JSONEncoder().encode($0) }, decode: { try ZippyJSONDecoder().decode($0, from: $1) }, lineNumber: #line)
561 | expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded PartialRangeFrom <\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
562 | }
563 |
564 | func test_PartialRangeFrom_Plist() {
565 | let value = 0...
566 | let decoded = performEncodeAndDecode(of: value, encode: { try PropertyListEncoder().encode($0) }, decode: { try PropertyListDecoder().decode($0, from: $1) }, lineNumber: #line)
567 | expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded PartialRangeFrom <\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
568 | }
569 |
570 | // MARK: - PartialRangeThrough
571 | func test_PartialRangeThrough_JSON() {
572 | let value = ...Int.max
573 | let decoded = performEncodeAndDecode(of: value, encode: { try JSONEncoder().encode($0) }, decode: { try ZippyJSONDecoder().decode($0, from: $1) }, lineNumber: #line)
574 | expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded PartialRangeThrough <\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
575 | }
576 |
577 | func test_PartialRangeThrough_Plist() {
578 | let value = ...Int.max
579 | let decoded = performEncodeAndDecode(of: value, encode: { try PropertyListEncoder().encode($0) }, decode: { try PropertyListDecoder().decode($0, from: $1) }, lineNumber: #line)
580 | expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded PartialRangeThrough <\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
581 | }
582 |
583 | // MARK: - PartialRangeUpTo
584 | func test_PartialRangeUpTo_JSON() {
585 | let value = .. not equal to original <\(testDescription(value))>")
588 | }
589 |
590 | func test_PartialRangeUpTo_Plist() {
591 | let value = .. not equal to original <\(testDescription(value))>")
594 | }
595 |
596 | // MARK: - PersonNameComponents
597 | @available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *)
598 | lazy var personNameComponentsValues: [Int : PersonNameComponents] = [
599 | #line : makePersonNameComponents(givenName: "John", familyName: "Appleseed"),
600 | #line : makePersonNameComponents(givenName: "John", familyName: "Appleseed", nickname: "Johnny"),
601 | #line : makePersonNameComponents(namePrefix: "Dr.", givenName: "Jane", middleName: "A.", familyName: "Appleseed", nameSuffix: "Esq.", nickname: "Janie")
602 | ]
603 |
604 | @available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *)
605 | func test_PersonNameComponents_JSON() {
606 | for (testLine, components) in personNameComponentsValues {
607 | expectRoundTripEqualityThroughJSON(for: components, lineNumber: testLine)
608 | }
609 | }
610 |
611 | @available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *)
612 | func test_PersonNameComponents_Plist() {
613 | for (testLine, components) in personNameComponentsValues {
614 | expectRoundTripEqualityThroughPlist(for: components, lineNumber: testLine)
615 | }
616 | }
617 |
618 | // MARK: - Range
619 | func test_Range_JSON() {
620 | let value = 0.. not equal to original <\(testDescription(value))>")
623 | expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded Range lowerBound<\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
624 | }
625 |
626 | func test_Range_Plist() {
627 | let value = 0.. not equal to original <\(testDescription(value))>")
630 | expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded Range lowerBound<\(testDescription(decoded))> not equal to original <\(testDescription(value))>")
631 | }
632 |
633 | // MARK: - TimeZone
634 | lazy var timeZoneValues: [Int : TimeZone] = [
635 | #line : TimeZone(identifier: "America/Los_Angeles")!,
636 | #line : TimeZone(identifier: "UTC")!,
637 | #line : TimeZone.current
638 | ]
639 |
640 | func test_TimeZone_JSON() {
641 | for (testLine, timeZone) in timeZoneValues {
642 | expectRoundTripEqualityThroughJSON(for: timeZone, lineNumber: testLine)
643 | }
644 | }
645 |
646 | func test_TimeZone_Plist() {
647 | for (testLine, timeZone) in timeZoneValues {
648 | expectRoundTripEqualityThroughPlist(for: timeZone, lineNumber: testLine)
649 | }
650 | }
651 |
652 | // MARK: - URL
653 | lazy var urlValues: [Int : URL] = {
654 | var values: [Int : URL] = [
655 | #line : URL(fileURLWithPath: NSTemporaryDirectory()),
656 | #line : URL(fileURLWithPath: "/"),
657 | #line : URL(string: "http://swift.org")!,
658 | #line : URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!
659 | ]
660 |
661 | if #available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *) {
662 | values[#line] = URL(fileURLWithPath: "bin/sh", relativeTo: URL(fileURLWithPath: "/"))
663 | }
664 |
665 | return values
666 | }()
667 |
668 | func test_URL_JSON() {
669 | for (testLine, url) in urlValues {
670 | // URLs encode as single strings in JSON. They lose their baseURL this way.
671 | // For relative URLs, we don't expect them to be equal to the original.
672 | if url.baseURL == nil {
673 | // This is an absolute URL; we can expect equality.
674 | expectRoundTripEqualityThroughJSON(for: TopLevelWrapper(url), lineNumber: testLine)
675 | } else {
676 | // This is a relative URL. Make it absolute first.
677 | let absoluteURL = URL(string: url.absoluteString)!
678 | expectRoundTripEqualityThroughJSON(for: TopLevelWrapper(absoluteURL), lineNumber: testLine)
679 | }
680 | }
681 | }
682 |
683 | func test_URL_Plist() {
684 | for (testLine, url) in urlValues {
685 | expectRoundTripEqualityThroughPlist(for: url, lineNumber: testLine)
686 | }
687 | }
688 |
689 | // MARK: - URLComponents
690 | lazy var urlComponentsValues: [Int : URLComponents] = [
691 | #line : URLComponents(),
692 |
693 | #line : URLComponents(string: "http://swift.org")!,
694 | #line : URLComponents(string: "http://swift.org:80")!,
695 | #line : URLComponents(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi¶m2=hello")!,
696 | #line : URLComponents(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!,
697 |
698 | #line : URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: false)!,
699 | #line : URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: false)!,
700 | #line : URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi¶m2=hello")!, resolvingAgainstBaseURL: false)!,
701 | #line : URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: false)!,
702 | #line : URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: false)!,
703 | #line : URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: false)!,
704 | #line : URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: false)!,
705 |
706 | #line : URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: true)!,
707 | #line : URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: true)!,
708 | #line : URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi¶m2=hello")!, resolvingAgainstBaseURL: true)!,
709 | #line : URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: true)!,
710 | #line : URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: true)!,
711 | #line : URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: true)!,
712 | #line : URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: true)!,
713 |
714 | #line : {
715 | var components = URLComponents()
716 | components.scheme = "https"
717 | return components
718 | }(),
719 |
720 | #line : {
721 | var components = URLComponents()
722 | components.user = "johnny"
723 | return components
724 | }(),
725 |
726 | #line : {
727 | var components = URLComponents()
728 | components.password = "apples"
729 | return components
730 | }(),
731 |
732 | #line : {
733 | var components = URLComponents()
734 | components.host = "0.0.0.0"
735 | return components
736 | }(),
737 |
738 | #line : {
739 | var components = URLComponents()
740 | components.port = 8080
741 | return components
742 | }(),
743 |
744 | #line : {
745 | var components = URLComponents()
746 | components.path = ".."
747 | return components
748 | }(),
749 |
750 | #line : {
751 | var components = URLComponents()
752 | components.query = "param1=hi¶m2=there"
753 | return components
754 | }(),
755 |
756 | #line : {
757 | var components = URLComponents()
758 | components.fragment = "anchor"
759 | return components
760 | }(),
761 |
762 | #line : {
763 | var components = URLComponents()
764 | components.scheme = "ftp"
765 | components.user = "johnny"
766 | components.password = "apples"
767 | components.host = "0.0.0.0"
768 | components.port = 4242
769 | components.path = "/some/file"
770 | components.query = "utf8=✅"
771 | components.fragment = "anchor"
772 | return components
773 | }()
774 | ]
775 |
776 | func test_URLComponents_JSON() {
777 | for (testLine, components) in urlComponentsValues {
778 | expectRoundTripEqualityThroughJSON(for: components, lineNumber: testLine)
779 | }
780 | }
781 |
782 | func test_URLComponents_Plist() {
783 | for (testLine, components) in urlComponentsValues {
784 | expectRoundTripEqualityThroughPlist(for: components, lineNumber: testLine)
785 | }
786 | }
787 |
788 | // MARK: - UUID
789 | lazy var uuidValues: [Int : UUID] = [
790 | #line : UUID(),
791 | #line : UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!,
792 | #line : UUID(uuidString: "e621e1f8-c36c-495a-93fc-0c247a3e6e5f")!,
793 | #line : UUID(uuid: uuid_t(0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f))
794 | ]
795 |
796 | func test_UUID_JSON() {
797 | for (testLine, uuid) in uuidValues {
798 | // We have to wrap the UUID since we cannot have a top-level string.
799 | expectRoundTripEqualityThroughJSON(for: UUIDCodingWrapper(uuid), lineNumber: testLine)
800 | }
801 | }
802 |
803 | func test_UUID_Plist() {
804 | for (testLine, uuid) in uuidValues {
805 | // We have to wrap the UUID since we cannot have a top-level string.
806 | expectRoundTripEqualityThroughPlist(for: UUIDCodingWrapper(uuid), lineNumber: testLine)
807 | }
808 | }
809 | }
810 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/JSONTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import Foundation
3 | import ZippyJSON
4 |
5 | var newParser: ZippyJSONDecoder {
6 | return ZippyJSONDecoder()
7 | }
8 |
9 | //var parser: JSONDecoder {
10 | // return JSONDecoder()
11 | //}
12 |
13 | var newEncoder: JSONEncoder {
14 | return JSONEncoder()
15 | }
16 |
17 | final class JSONTests: XCTestCase {
18 | func testMissingCommaInObject() {
19 | let json = """
20 | {
21 | "yes": "✅",
22 | "bug": "🐛",
23 | "awesome": [true, false, false, false,true]
24 | "flag": "🇳🇱"
25 | }
26 | """.data(using: .utf8)!
27 |
28 | struct Test: Codable {
29 | let yes: String
30 | let bug: String
31 | let awesome: [Bool]
32 | let flag: String
33 | }
34 |
35 | XCTAssertThrowsError(try newParser.decode(Test.self, from: json))
36 | }
37 |
38 | private func measureTime(run block: () throws -> ()) rethrows -> TimeInterval {
39 | let date = Date()
40 | try block()
41 | return Date().timeIntervalSince(date)
42 | }
43 |
44 | func testMissingCommaInArray() {
45 | let json = """
46 | {
47 | "yes": "✅",
48 | "bug": "🐛",
49 | "awesome": [true false, false, false,true],
50 | "flag": "🇳🇱"
51 | }
52 | """.data(using: .utf8)!
53 |
54 | struct Test: Codable {
55 | let yes: String
56 | let bug: String
57 | let awesome: [Bool]
58 | let flag: String
59 | }
60 |
61 | XCTAssertThrowsError(try newParser.decode(Test.self, from: json))
62 | }
63 |
64 | func testMissingEndOfArray() {
65 | let json = """
66 | {
67 | "yes": "✅",
68 | "bug": "🐛",
69 | "awesome": [true, false, false, false,true
70 | """.data(using: .utf8)!
71 |
72 | struct Test: Codable {
73 | let yes: String
74 | let bug: String
75 | let awesome: [Bool]
76 | let flag: String
77 | }
78 |
79 | XCTAssertThrowsError(try newParser.decode(Test.self, from: json))
80 | }
81 |
82 | func testMissingEndOfObject() {
83 | let json = """
84 | {
85 | "yes": "✅",
86 | "bug": "🐛",
87 | "awesome": [true, false, false, false,true],
88 | "flag": "🇳🇱"
89 | """.data(using: .utf8)!
90 |
91 | struct Test: Codable {
92 | let yes: String
93 | let bug: String
94 | let awesome: [Bool]
95 | let flag: String
96 | }
97 |
98 | XCTAssertThrowsError(try newParser.decode(Test.self, from: json))
99 | }
100 |
101 | func testKeyDecoding() throws {
102 | let parser = newParser
103 | parser.keyDecodingStrategy = .convertFromSnakeCase
104 |
105 | struct Test: Codable {
106 | let userName: String
107 | let eMail: String
108 | }
109 |
110 | let json0 = """
111 | {
112 | "userName": "Joannis",
113 | "e_mail": "joannis@orlandos.nl"
114 | }
115 | """.data(using: .utf8)!
116 |
117 | let json1 = """
118 | {
119 | "user_name": "Joannis",
120 | "e_mail": "joannis@orlandos.nl"
121 | }
122 | """.data(using: .utf8)!
123 |
124 | do {
125 | let user = try parser.decode(Test.self, from: json0)
126 | XCTAssertEqual(user.userName, "Joannis")
127 | XCTAssertEqual(user.eMail, "joannis@orlandos.nl")
128 | } catch {
129 | XCTFail()
130 | }
131 |
132 | do {
133 | let user = try parser.decode(Test.self, from: json1)
134 | XCTAssertEqual(user.userName, "Joannis")
135 | XCTAssertEqual(user.eMail, "joannis@orlandos.nl")
136 | } catch {
137 | XCTFail()
138 | }
139 | }
140 |
141 | func testEmojis() throws {
142 | let json = """
143 | {
144 | "yes": "✅",
145 | "bug": "🐛",
146 | "flag": "🇳🇱"
147 | }
148 | """.data(using: .utf8)!
149 |
150 | struct Test: Decodable {
151 | let yes: String
152 | let bug: String
153 | let flag: String
154 | }
155 |
156 | let test = try newParser.decode(Test.self, from: json)
157 | XCTAssertEqual(test.yes, "✅")
158 | XCTAssertEqual(test.bug, "🐛")
159 | XCTAssertEqual(test.flag, "🇳🇱")
160 | }
161 |
162 | func testObject() throws {
163 | let json = """
164 | {
165 | "id": "0",
166 | "username": "Joannis",
167 | "role": "admin",
168 | "awesome": true,
169 | "superAwesome": false
170 | }
171 | """.data(using: .utf8)!
172 |
173 | struct User: Decodable {
174 | let id: String
175 | let username: String
176 | let role: String
177 | let awesome: Bool
178 | let superAwesome: Bool
179 | }
180 |
181 | let user = try! newParser.decode(User.self, from: json)
182 |
183 | XCTAssertEqual(user.id, "0")
184 | XCTAssertEqual(user.username, "Joannis")
185 | XCTAssertEqual(user.role, "admin")
186 | XCTAssertTrue(user.awesome)
187 | XCTAssertFalse(user.superAwesome)
188 | }
189 |
190 | func testArray() throws {
191 | let json = """
192 | {
193 | "id": "0",
194 | "username": "Joannis",
195 | "roles": ["admin", null, "member", "moderator"],
196 | "awesome": true,
197 | "superAwesome": false
198 | }
199 | """.data(using: .utf8)!
200 |
201 | struct User: Decodable {
202 | let id: String
203 | let username: String
204 | let roles: [String?]
205 | let awesome: Bool
206 | let superAwesome: Bool
207 | }
208 |
209 | let user = try! newParser.decode(User.self, from: json)
210 |
211 | XCTAssertEqual(user.id, "0")
212 | XCTAssertEqual(user.username, "Joannis")
213 | XCTAssertEqual(user.roles.count, 4)
214 | XCTAssertEqual(user.roles[0], "admin")
215 | XCTAssertEqual(user.roles[1], nil)
216 | XCTAssertEqual(user.roles[2], "member")
217 | XCTAssertEqual(user.roles[3], "moderator")
218 | XCTAssertTrue(user.awesome)
219 | XCTAssertFalse(user.superAwesome)
220 | }
221 |
222 | @available(OSX 10.12, *)
223 | func testISO8601DateStrategy() throws {
224 | let decoder = newParser
225 | decoder.dateDecodingStrategy = .iso8601
226 |
227 | let date = Date()
228 | let string = ISO8601DateFormatter().string(from: date)
229 |
230 | let json = """
231 | {
232 | "createdAt": "\(string)"
233 | }
234 | """.data(using: .utf8)!
235 |
236 | struct Test: Decodable {
237 | let createdAt: Date
238 | }
239 |
240 | let test = try decoder.decode(Test.self, from: json)
241 |
242 | // Because of Double rounding errors, this is necessary
243 | XCTAssertEqual(Int(test.createdAt.timeIntervalSince1970), Int(date.timeIntervalSince1970))
244 | }
245 |
246 | @available(OSX 10.12, *)
247 | func testEpochSecDateStrategy() throws {
248 | let decoder = newParser
249 | decoder.dateDecodingStrategy = .secondsSince1970
250 |
251 | let date = Date()
252 |
253 | let json = """
254 | {
255 | "createdAt": \(Int(date.timeIntervalSince1970))
256 | }
257 | """.data(using: .utf8)!
258 |
259 | struct Test: Decodable {
260 | let createdAt: Date
261 | }
262 |
263 | let test = try decoder.decode(Test.self, from: json)
264 |
265 | // Because of Double rounding errors, this is necessary
266 | XCTAssertEqual(Int(test.createdAt.timeIntervalSince1970), Int(date.timeIntervalSince1970))
267 | }
268 |
269 | @available(OSX 10.12, *)
270 | func testEpochMSDateStrategy() throws {
271 | let decoder = newParser
272 | decoder.dateDecodingStrategy = .millisecondsSince1970
273 |
274 | let date = Date()
275 |
276 | let json = """
277 | {
278 | "createdAt": \(Int(date.timeIntervalSince1970 * 1000))
279 | }
280 | """.data(using: .utf8)!
281 |
282 | struct Test: Decodable {
283 | let createdAt: Date
284 | }
285 |
286 | let test = try decoder.decode(Test.self, from: json)
287 |
288 | // Because of Double rounding errors, this is necessary
289 | XCTAssertEqual(Int(test.createdAt.timeIntervalSince1970), Int(date.timeIntervalSince1970))
290 | }
291 |
292 | func testEscaping() throws {
293 | let json = """
294 | {
295 | "id": "0",
296 | "username": "Joannis\\tis\\nawesome\\\\\\"",
297 | "roles": ["admin", null, "member", "moderator"],
298 | "awesome": true,
299 | "superAwesome": false
300 | }
301 | """.data(using: .utf8)!
302 |
303 | struct User: Decodable {
304 | let id: String
305 | let username: String
306 | let roles: [String?]
307 | let awesome: Bool
308 | let superAwesome: Bool
309 | }
310 |
311 | let user = try! newParser.decode(User.self, from: json)
312 |
313 | XCTAssertEqual(user.id, "0")
314 | XCTAssertEqual(user.username, "Joannis\tis\nawesome\\\"")
315 | XCTAssertEqual(user.roles.count, 4)
316 | XCTAssertEqual(user.roles[0], "admin")
317 | XCTAssertEqual(user.roles[1], nil)
318 | XCTAssertEqual(user.roles[2], "member")
319 | XCTAssertEqual(user.roles[3], "moderator")
320 | XCTAssertTrue(user.awesome)
321 | XCTAssertFalse(user.superAwesome)
322 | }
323 |
324 | func testNumerical() throws {
325 | let json = """
326 | {
327 | "piD": 3.14,
328 | "piF": 0.314e1,
329 | "piFm": 314e-2,
330 | "piFp": 0.0314e+2,
331 | "u8": 255,
332 | "u8zero": 0,
333 | "i8": -127,
334 | "imax": \(Int32.max),
335 | "imin": \(Int32.min)
336 | }
337 | """.data(using: .utf8)!
338 |
339 | struct Stuff: Decodable {
340 | let piD: Double
341 | let piF: Float
342 | let piFm: Float
343 | let piFp: Float
344 | let u8: UInt8
345 | let u8zero: UInt8
346 | let i8: Int8
347 | let imax: Int32
348 | let imin: Int32
349 | }
350 |
351 | let stuff = try newParser.decode(Stuff.self, from: json)
352 |
353 | XCTAssertEqual(stuff.piD, 3.14)
354 | XCTAssertEqual(stuff.piF, 3.14)
355 | XCTAssertEqual(stuff.piFm, 3.14)
356 | XCTAssertEqual(stuff.piFp, 3.14)
357 | XCTAssertEqual(stuff.u8, 255)
358 | XCTAssertEqual(stuff.u8zero, 0)
359 | XCTAssertEqual(stuff.i8, -127)
360 | XCTAssertEqual(stuff.imax, .max)
361 | XCTAssertEqual(stuff.imin, .min)
362 | }
363 |
364 | func testCodablePerformance() throws {
365 | let data = """
366 | {
367 | "awesome": true,
368 | "superAwesome": false
369 | }
370 | """.data(using: .utf8)!
371 |
372 | struct User: Decodable {
373 | let awesome: Bool
374 | let superAwesome: Bool
375 | }
376 |
377 | for _ in 0..<1 {
378 | _ = try! newParser.decode(User.self, from: data)
379 | }
380 | }
381 |
382 | func testInvalidJSONCase() {
383 | let object = """
384 | {
385 | "hoi" "",
386 | }
387 | """
388 |
389 | struct Test: Codable {
390 | var hoi: String
391 | }
392 |
393 | let decoder = ZippyJSONDecoder()
394 | XCTAssertThrowsError(try decoder.decode(Test.self, from: object.data(using: .utf8)!))
395 | }
396 |
397 | func testDataDecoding() throws {
398 | struct Datas: Codable {
399 | let data: Data
400 | }
401 |
402 | let decoder = ZippyJSONDecoder()
403 |
404 | decoder.dataDecodingStrategy = .deferredToData
405 | var datas = try decoder.decode(Datas.self, from: "{\"data\":[1,2,3]}".data(using: .utf8)!)
406 | XCTAssertEqual(datas.data, Data(bytes: [1,2,3]))
407 |
408 | decoder.dataDecodingStrategy = .custom({ _ in
409 | return Data(bytes: [1, 2, 3])
410 | })
411 | datas = try decoder.decode(Datas.self, from: "{\"data\":true}".data(using: .utf8)!)
412 | XCTAssertEqual(datas.data, Data(bytes: [1,2,3]))
413 |
414 | let data = Data(bytes: [0x01, 0x02, 0x03, 0x04, 0x05])
415 | decoder.dataDecodingStrategy = .base64
416 | datas = try decoder.decode(Datas.self, from: "{\"data\":\"\(data.base64EncodedString())\"}".data(using: .utf8)!)
417 | XCTAssertEqual(datas.data, data)
418 | }
419 |
420 | func testNestedArrayInObjectAccess() {
421 |
422 | }
423 |
424 | func testArrayAccess() {
425 |
426 | }
427 |
428 | func testNestedObjectInArrayAccess() {
429 |
430 | }
431 |
432 | func testNestedArrayInArrayAccess() {
433 |
434 | }
435 |
436 | static var allTests = [
437 | ("testObject", testObject),
438 | ("testArray", testArray),
439 | ("testEscaping", testEscaping),
440 | ("testNumerical", testNumerical),
441 | ("testCodablePerformance", testCodablePerformance),
442 | ]
443 | }
444 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/apache_builds.swift:
--------------------------------------------------------------------------------
1 | // This file was generated from JSON Schema using quicktype, do not modify it directly.
2 | // To parse the JSON, add this file to your project and do:
3 | //
4 | // let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
5 |
6 | //
7 | // Hashable or Equatable:
8 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
9 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
10 | // synthesized for types that have collections (such as arrays or dictionaries).
11 |
12 | import Foundation
13 |
14 | // MARK: - Welcome
15 | public struct ApacheBuilds: Codable, Equatable {
16 | let assignedLabels: [OverallLoad]
17 | let mode: String
18 | let nodeDescription: String
19 | let nodeName: String
20 | let numExecutors: Int
21 | let welcomeDescription: String
22 | let jobs: [Job]
23 | let overallLoad: OverallLoad
24 | let primaryView: View
25 | let quietingDown: Bool
26 | let slaveAgentPort: Int
27 | let unlabeledLoad: OverallLoad
28 | let useCrumbs: Bool
29 | let useSecurity: Bool
30 | let views: [View]
31 |
32 | enum CodingKeys: String, CodingKey {
33 | case assignedLabels = "assignedLabels"
34 | case mode = "mode"
35 | case nodeDescription = "nodeDescription"
36 | case nodeName = "nodeName"
37 | case numExecutors = "numExecutors"
38 | case welcomeDescription = "description"
39 | case jobs = "jobs"
40 | case overallLoad = "overallLoad"
41 | case primaryView = "primaryView"
42 | case quietingDown = "quietingDown"
43 | case slaveAgentPort = "slaveAgentPort"
44 | case unlabeledLoad = "unlabeledLoad"
45 | case useCrumbs = "useCrumbs"
46 | case useSecurity = "useSecurity"
47 | case views = "views"
48 | }
49 | }
50 |
51 | //
52 | // Hashable or Equatable:
53 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
54 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
55 | // synthesized for types that have collections (such as arrays or dictionaries).
56 |
57 | // MARK: - OverallLoad
58 | struct OverallLoad: Codable, Equatable {
59 | }
60 |
61 | //
62 | // Hashable or Equatable:
63 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
64 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
65 | // synthesized for types that have collections (such as arrays or dictionaries).
66 |
67 | // MARK: - Job
68 | struct Job: Codable, Equatable {
69 | let name: String
70 | let url: String
71 | let color: String
72 |
73 | enum CodingKeys: String, CodingKey {
74 | case name = "name"
75 | case url = "url"
76 | case color = "color"
77 | }
78 | }
79 |
80 | //
81 | // Hashable or Equatable:
82 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
83 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
84 | // synthesized for types that have collections (such as arrays or dictionaries).
85 |
86 | // MARK: - View
87 | struct View: Codable, Equatable {
88 | let name: String
89 | let url: String
90 |
91 | enum CodingKeys: String, CodingKey {
92 | case name = "name"
93 | case url = "url"
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/canada.swift:
--------------------------------------------------------------------------------
1 | struct canada: Codable, Equatable {
2 | let `type`: String
3 | let `features`: [features]
4 | }
5 |
6 | struct features: Codable, Equatable {
7 | let `type`: String
8 | let `properties`: properties
9 | let `geometry`: geometry
10 | }
11 |
12 | struct geometry: Codable, Equatable {
13 | let `type`: String
14 | let `coordinates`: [[[Double]]]
15 | }
16 |
17 | struct properties: Codable, Equatable {
18 | let `name`: String
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/entities.json:
--------------------------------------------------------------------------------
1 | {
2 | "hashtags": [],
3 | "symbols": [],
4 | "urls": [],
5 | "user_mentions": [],
6 | "media": [
7 | {
8 | "id": 505864942575034400,
9 | "id_str": "505864942575034369",
10 | "indices": [
11 | 13,
12 | 35
13 | ],
14 | "media_url": "http://pbs.twimg.com/media/BwUxfC6CIAEr-Ye.jpg",
15 | "media_url_https": "https://pbs.twimg.com/media/BwUxfC6CIAEr-Ye.jpg",
16 | "url": "http://t.co/PkCJAcSuYK",
17 | "display_url": "pic.twitter.com/PkCJAcSuYK",
18 | "expanded_url": "http://twitter.com/KATANA77/status/505864943636197376/photo/1",
19 | "type": "photo",
20 | "sizes": {
21 | "medium": {
22 | "w": 600,
23 | "h": 338,
24 | "resize": "fit"
25 | },
26 | "small": {
27 | "w": 340,
28 | "h": 192,
29 | "resize": "fit"
30 | },
31 | "thumb": {
32 | "w": 150,
33 | "h": 150,
34 | "resize": "crop"
35 | },
36 | "large": {
37 | "w": 765,
38 | "h": 432,
39 | "resize": "fit"
40 | }
41 | }
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/entities.swift:
--------------------------------------------------------------------------------
1 | struct entities {
2 | let `hashtags`: [[String]?]
3 | let `symbols`: [[String]?]
4 | let `urls`: [[String]?]
5 | let `user_mentions`: [[String]?]
6 | let `media`: [media]
7 | }
8 |
9 | struct media {
10 | let `id`: Int
11 | let `id_str`: String
12 | let `indices`: [Int]
13 | let `media_url`: String
14 | let `media_url_https`: String
15 | let `url`: String
16 | let `display_url`: String
17 | let `expanded_url`: String
18 | let `type`: String
19 | let `sizes`: sizes
20 | }
21 |
22 | struct sizes {
23 | let `medium`: size
24 | let `small`: size
25 | let `thumb`: size
26 | let `large`: size
27 | }
28 |
29 | struct size {
30 | let `w`: Int
31 | let `h`: Int
32 | let `resize`: String
33 | }
34 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/github_events.swift:
--------------------------------------------------------------------------------
1 | // This file was generated from JSON Schema using quicktype, do not modify it directly.
2 | // To parse the JSON, add this file to your project and do:
3 | //
4 | // let ghWelcome = try? newJSONDecoder().decode(ghWelcome.self, from: jsonData)
5 |
6 | //
7 | // Hashable or Equatable:
8 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
9 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
10 | // synthesized for types that have collections (such as arrays or dictionaries).
11 |
12 | import Foundation
13 |
14 | // MARK: - ghWelcomeElement
15 | struct ghWelcomeElement: Codable, Equatable {
16 | let type: String
17 | let createdAt: Date
18 | let actor: ghActor
19 | let repo: ghRepo
20 | let welcomePublic: Bool
21 | let payload: ghPayload
22 | let id: String
23 | let org: ghActor?
24 |
25 | enum CodingKeys: String, CodingKey {
26 | case type = "type"
27 | case createdAt = "created_at"
28 | case actor = "actor"
29 | case repo = "repo"
30 | case welcomePublic = "public"
31 | case payload = "payload"
32 | case id = "id"
33 | case org = "org"
34 | }
35 | }
36 |
37 | //
38 | // Hashable or Equatable:
39 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
40 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
41 | // synthesized for types that have collections (such as arrays or dictionaries).
42 |
43 | // MARK: - ghActor
44 | struct ghActor: Codable, Equatable {
45 | let gravatarid: String
46 | let login: String
47 | let avatarurl: String
48 | let url: String
49 | let id: Int
50 |
51 | enum CodingKeys: String, CodingKey {
52 | case gravatarid = "gravatar_id"
53 | case login = "login"
54 | case avatarurl = "avatar_url"
55 | case url = "url"
56 | case id = "id"
57 | }
58 | }
59 |
60 | //
61 | // Hashable or Equatable:
62 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
63 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
64 | // synthesized for types that have collections (such as arrays or dictionaries).
65 |
66 | // MARK: - ghPayload
67 | struct ghPayload: Codable, Equatable {
68 | let commits: [ghCommit]?
69 | let distinctSize: Int?
70 | let ref: String?
71 | let pushid: Int?
72 | let head: String?
73 | let before: String?
74 | let size: Int?
75 | let payloadDescription: String?
76 | let masterBranch: String?
77 | let refType: String?
78 | let forkee: ghForkee?
79 | let action: String?
80 | let issue: ghIssue?
81 | let comment: ghComment?
82 | let pages: [ghPage]?
83 |
84 | enum CodingKeys: String, CodingKey {
85 | case commits = "commits"
86 | case distinctSize = "distinct_size"
87 | case ref = "ref"
88 | case pushid = "push_id"
89 | case head = "head"
90 | case before = "before"
91 | case size = "size"
92 | case payloadDescription = "description"
93 | case masterBranch = "master_branch"
94 | case refType = "ref_type"
95 | case forkee = "forkee"
96 | case action = "action"
97 | case issue = "issue"
98 | case comment = "comment"
99 | case pages = "pages"
100 | }
101 | }
102 |
103 | //
104 | // Hashable or Equatable:
105 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
106 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
107 | // synthesized for types that have collections (such as arrays or dictionaries).
108 |
109 | // MARK: - ghComment
110 | struct ghComment: Codable, Equatable {
111 | let user: ghUser
112 | let url: String
113 | let issueurl: String
114 | let createdAt: Date
115 | let body: String
116 | let updatedAt: Date
117 | let id: Int
118 |
119 | enum CodingKeys: String, CodingKey {
120 | case user = "user"
121 | case url = "url"
122 | case issueurl = "issue_url"
123 | case createdAt = "created_at"
124 | case body = "body"
125 | case updatedAt = "updated_at"
126 | case id = "id"
127 | }
128 | }
129 |
130 | //
131 | // Hashable or Equatable:
132 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
133 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
134 | // synthesized for types that have collections (such as arrays or dictionaries).
135 |
136 | // MARK: - ghUser
137 | struct ghUser: Codable, Equatable {
138 | let url: String
139 | let gistsurl: String
140 | let gravatarid: String
141 | let type: String
142 | let avatarurl: String
143 | let subscriptionsurl: String
144 | let organizationsurl: String
145 | let receivedEventsurl: String
146 | let reposurl: String
147 | let login: String
148 | let id: Int
149 | let starredurl: String
150 | let eventsurl: String
151 | let followersurl: String
152 | let followingurl: String
153 |
154 | enum CodingKeys: String, CodingKey {
155 | case url = "url"
156 | case gistsurl = "gists_url"
157 | case gravatarid = "gravatar_id"
158 | case type = "type"
159 | case avatarurl = "avatar_url"
160 | case subscriptionsurl = "subscriptions_url"
161 | case organizationsurl = "organizations_url"
162 | case receivedEventsurl = "received_events_url"
163 | case reposurl = "repos_url"
164 | case login = "login"
165 | case id = "id"
166 | case starredurl = "starred_url"
167 | case eventsurl = "events_url"
168 | case followersurl = "followers_url"
169 | case followingurl = "following_url"
170 | }
171 | }
172 |
173 | //
174 | // Hashable or Equatable:
175 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
176 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
177 | // synthesized for types that have collections (such as arrays or dictionaries).
178 |
179 | // MARK: - ghCommit
180 | struct ghCommit: Codable, Equatable {
181 | let url: String
182 | let message: String
183 | let distinct: Bool
184 | let sha: String
185 | let author: ghAuthor
186 |
187 | enum CodingKeys: String, CodingKey {
188 | case url = "url"
189 | case message = "message"
190 | case distinct = "distinct"
191 | case sha = "sha"
192 | case author = "author"
193 | }
194 | }
195 |
196 | //
197 | // Hashable or Equatable:
198 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
199 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
200 | // synthesized for types that have collections (such as arrays or dictionaries).
201 |
202 | // MARK: - ghAuthor
203 | struct ghAuthor: Codable, Equatable {
204 | let email: String
205 | let name: String
206 |
207 | enum CodingKeys: String, CodingKey {
208 | case email = "email"
209 | case name = "name"
210 | }
211 | }
212 |
213 | //
214 | // Hashable or Equatable:
215 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
216 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
217 | // synthesized for types that have collections (such as arrays or dictionaries).
218 |
219 | // MARK: - ghForkee
220 | struct ghForkee: Codable, Equatable {
221 | let forkeeDescription: String
222 | let fork: Bool
223 | let url: String
224 | let language: String
225 | let stargazersurl: String
226 | let cloneurl: String
227 | let tagsurl: String
228 | let fullName: String
229 | let mergesurl: String
230 | let forks: Int
231 | let forkeePrivate: Bool
232 | let gitRefsurl: String
233 | let archiveurl: String
234 | let collaboratorsurl: String
235 | let owner: ghUser
236 | let languagesurl: String
237 | let treesurl: String
238 | let labelsurl: String
239 | let htmlurl: String
240 | let pushedAt: Date
241 | let createdAt: Date
242 | let hasIssues: Bool
243 | let forksurl: String
244 | let branchesurl: String
245 | let commitsurl: String
246 | let notificationsurl: String
247 | let openIssues: Int
248 | let contentsurl: String
249 | let blobsurl: String
250 | let issuesurl: String
251 | let compareurl: String
252 | let issueEventsurl: String
253 | let name: String
254 | let updatedAt: Date
255 | let statusesurl: String
256 | let forksCount: Int
257 | let assigneesurl: String
258 | let sshurl: String
259 | let forkeePublic: Bool
260 | let hasWiki: Bool
261 | let subscribersurl: String
262 | // let mirrorurl: JSONNull?
263 | let watchersCount: Int
264 | let id: Int
265 | let hasDownloads: Bool
266 | let gitCommitsurl: String
267 | let downloadsurl: String
268 | let pullsurl: String
269 | let homepage: String?
270 | let issueCommenturl: String
271 | let hooksurl: String
272 | let subscriptionurl: String
273 | let milestonesurl: String
274 | let svnurl: String
275 | let eventsurl: String
276 | let gitTagsurl: String
277 | let teamsurl: String
278 | let commentsurl: String
279 | let openIssuesCount: Int
280 | let keysurl: String
281 | let giturl: String
282 | let contributorsurl: String
283 | let size: Int
284 | let watchers: Int
285 |
286 | enum CodingKeys: String, CodingKey {
287 | case forkeeDescription = "description"
288 | case fork = "fork"
289 | case url = "url"
290 | case language = "language"
291 | case stargazersurl = "stargazers_url"
292 | case cloneurl = "clone_url"
293 | case tagsurl = "tags_url"
294 | case fullName = "full_name"
295 | case mergesurl = "merges_url"
296 | case forks = "forks"
297 | case forkeePrivate = "private"
298 | case gitRefsurl = "git_refs_url"
299 | case archiveurl = "archive_url"
300 | case collaboratorsurl = "collaborators_url"
301 | case owner = "owner"
302 | case languagesurl = "languages_url"
303 | case treesurl = "trees_url"
304 | case labelsurl = "labels_url"
305 | case htmlurl = "html_url"
306 | case pushedAt = "pushed_at"
307 | case createdAt = "created_at"
308 | case hasIssues = "has_issues"
309 | case forksurl = "forks_url"
310 | case branchesurl = "branches_url"
311 | case commitsurl = "commits_url"
312 | case notificationsurl = "notifications_url"
313 | case openIssues = "open_issues"
314 | case contentsurl = "contents_url"
315 | case blobsurl = "blobs_url"
316 | case issuesurl = "issues_url"
317 | case compareurl = "compare_url"
318 | case issueEventsurl = "issue_events_url"
319 | case name = "name"
320 | case updatedAt = "updated_at"
321 | case statusesurl = "statuses_url"
322 | case forksCount = "forks_count"
323 | case assigneesurl = "assignees_url"
324 | case sshurl = "ssh_url"
325 | case forkeePublic = "public"
326 | case hasWiki = "has_wiki"
327 | case subscribersurl = "subscribers_url"
328 | // case mirrorurl = "mirror_url"
329 | case watchersCount = "watchers_count"
330 | case id = "id"
331 | case hasDownloads = "has_downloads"
332 | case gitCommitsurl = "git_commits_url"
333 | case downloadsurl = "downloads_url"
334 | case pullsurl = "pulls_url"
335 | case homepage = "homepage"
336 | case issueCommenturl = "issue_comment_url"
337 | case hooksurl = "hooks_url"
338 | case subscriptionurl = "subscription_url"
339 | case milestonesurl = "milestones_url"
340 | case svnurl = "svn_url"
341 | case eventsurl = "events_url"
342 | case gitTagsurl = "git_tags_url"
343 | case teamsurl = "teams_url"
344 | case commentsurl = "comments_url"
345 | case openIssuesCount = "open_issues_count"
346 | case keysurl = "keys_url"
347 | case giturl = "git_url"
348 | case contributorsurl = "contributors_url"
349 | case size = "size"
350 | case watchers = "watchers"
351 | }
352 | }
353 |
354 | //
355 | // Hashable or Equatable:
356 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
357 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
358 | // synthesized for types that have collections (such as arrays or dictionaries).
359 |
360 | // MARK: - ghIssue
361 | struct ghIssue: Codable, Equatable {
362 | let user: ghUser
363 | let url: String
364 | let labels: [Int]
365 | let htmlurl: String
366 | let labelsurl: String
367 | let pullRequest: ghPullRequest
368 | let createdAt: Date
369 | let closedAt: Date?
370 | // let milestone: JSONNull?
371 | let title: String
372 | let body: String
373 | let updatedAt: Date
374 | let number: Int
375 | let state: String
376 | let assignee: ghUser?
377 | let id: Int
378 | let eventsurl: String
379 | let commentsurl: String
380 | let comments: Int
381 |
382 | enum CodingKeys: String, CodingKey {
383 | case user = "user"
384 | case url = "url"
385 | case labels = "labels"
386 | case htmlurl = "html_url"
387 | case labelsurl = "labels_url"
388 | case pullRequest = "pull_request"
389 | case createdAt = "created_at"
390 | case closedAt = "closed_at"
391 | // case milestone = "milestone"
392 | case title = "title"
393 | case body = "body"
394 | case updatedAt = "updated_at"
395 | case number = "number"
396 | case state = "state"
397 | case assignee = "assignee"
398 | case id = "id"
399 | case eventsurl = "events_url"
400 | case commentsurl = "comments_url"
401 | case comments = "comments"
402 | }
403 | }
404 |
405 | //
406 | // Hashable or Equatable:
407 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
408 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
409 | // synthesized for types that have collections (such as arrays or dictionaries).
410 |
411 | // MARK: - ghPullRequest
412 | struct ghPullRequest: Codable, Equatable {
413 | /*let htmlurl: JSONNull?
414 | let patchurl: JSONNull?
415 | let diffurl: JSONNull?
416 |
417 | enum CodingKeys: String, CodingKey {
418 | case htmlurl = "html_url"
419 | case patchurl = "patch_url"
420 | case diffurl = "diff_url"
421 | }*/
422 | }
423 |
424 | //
425 | // Hashable or Equatable:
426 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
427 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
428 | // synthesized for types that have collections (such as arrays or dictionaries).
429 |
430 | // MARK: - ghPage
431 | struct ghPage: Codable, Equatable {
432 | let pageName: String
433 | let htmlurl: String
434 | let title: String
435 | let sha: String
436 | // let summary: JSONNull?
437 | let action: String
438 |
439 | enum CodingKeys: String, CodingKey {
440 | case pageName = "page_name"
441 | case htmlurl = "html_url"
442 | case title = "title"
443 | case sha = "sha"
444 | // case summary = "summary"
445 | case action = "action"
446 | }
447 | }
448 |
449 | //
450 | // Hashable or Equatable:
451 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
452 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
453 | // synthesized for types that have collections (such as arrays or dictionaries).
454 |
455 | // MARK: - ghRepo
456 | struct ghRepo: Codable, Equatable {
457 | let url: String
458 | let id: Int
459 | let name: String
460 |
461 | enum CodingKeys: String, CodingKey {
462 | case url = "url"
463 | case id = "id"
464 | case name = "name"
465 | }
466 | }
467 |
468 | typealias ghEvents = [ghWelcomeElement]
469 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/json_to_swift.rb:
--------------------------------------------------------------------------------
1 | require 'json'
2 | require 'pry-byebug'
3 |
4 | def find(j, name)
5 | # binding.pry
6 | if j.kind_of?(Array)
7 | return j.detect { |jj| find(jj, name) }
8 | elsif j.kind_of?(Hash)
9 | a = j.detect do |k, v|
10 | next v if k == name && v != nil# && !v.kind_of?(Hash) && !v.kind_of?(Array)
11 | next find(v, name) if v.kind_of?(Hash) || v.kind_of?(Array)
12 | next nil
13 | end
14 | return a
15 | end
16 | nil
17 | end
18 |
19 | def process(orig, name, j)
20 | # binding.pry if name == "urls"
21 | if j.kind_of?(Array)
22 | return "[#{process(orig, name, j.first)}]"
23 | elsif j.kind_of?(Hash)
24 | members = {}
25 | j.each do |k, v|
26 | members[k] = process(orig, k, v)
27 | end
28 | $structs << [name, members]
29 | return name
30 | elsif j.kind_of?(Float)
31 | return "Double"
32 | elsif j.kind_of?(Integer)
33 | return "Int"
34 | elsif j.kind_of?(String)
35 | return "String"
36 | elsif j == true || j == false
37 | return "Bool"
38 | elsif j == nil
39 | match = find(orig, name)
40 | if match == nil
41 | puts "aborted for #{name}"
42 | return nil
43 | end
44 | return process(orig, name, match) + "?"
45 | end
46 | end
47 |
48 | def string(structs)
49 | str = ""
50 | $structs.reverse.each do |name, members|
51 | str << "struct #{name} {\n"
52 | members.each do |k, v|
53 | str << " let `#{k}`: #{v}\n"
54 | end
55 | str << "}\n\n"
56 | end
57 | str
58 | end
59 |
60 | files = `ls *.json`.split("\n")
61 | files.each do |f|
62 | next unless f.end_with?(".json")
63 | puts f
64 | name = File.basename(f)[0..-6]
65 | $structs = []
66 | orig = JSON.parse(IO.read(f))
67 | process(orig, name, orig)
68 | str = string($structs)
69 | # binding.pry
70 | IO.write(name + ".swift", str)
71 | end
72 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/mesh.swift:
--------------------------------------------------------------------------------
1 | struct mesh: Codable, Equatable {
2 | let `batches`: [batches]
3 | let `morphTargets`: morphTargets
4 | let `positions`: [Double]
5 | let `tex0`: [Double]
6 | let `colors`: [Int]
7 | let `influences`: [[Double]]
8 | let `normals`: [Double]
9 | let `indices`: [Int]
10 | }
11 |
12 | struct morphTargets: Codable, Equatable {
13 | }
14 |
15 | struct batches: Codable, Equatable {
16 | let `indexRange`: [Int]
17 | let `vertexRange`: [Int]
18 | let `usedBones`: [Int]
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/random.swift:
--------------------------------------------------------------------------------
1 | struct random: Codable, Equatable {
2 | let `id`: Int
3 | let `jsonrpc`: String
4 | let `total`: Int
5 | let `result`: [result]
6 | }
7 |
8 | struct result: Codable, Equatable {
9 | let `id`: Int
10 | let `avatar`: String
11 | let `age`: Int
12 | let `admin`: Bool
13 | let `name`: String
14 | let `company`: String
15 | let `phone`: String
16 | let `email`: String
17 | let `birthDate`: String
18 | let `friends`: [friends]
19 | let `field`: String
20 | }
21 |
22 | struct friends: Codable, Equatable {
23 | let `id`: Int
24 | let `name`: String
25 | let `phone`: String
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/twitter.swift:
--------------------------------------------------------------------------------
1 | // This file was generated from JSON Schema using quicktype, do not modify it directly.
2 | // To parse the JSON, add this file to your project and do:
3 | //
4 | // let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
5 |
6 | //
7 | // Hashable or Equatable:
8 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
9 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
10 | // synthesized for types that have collections (such as arrays or dictionaries).
11 |
12 | import Foundation
13 |
14 | // MARK: - Welcome
15 | struct Twitter: Codable, Equatable {
16 | let statuses: [Status]
17 | let searchMetadata: SearchMetadata
18 |
19 | enum CodingKeys: String, CodingKey {
20 | case statuses = "statuses"
21 | case searchMetadata = "search_metadata"
22 | }
23 | }
24 |
25 | //
26 | // Hashable or Equatable:
27 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
28 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
29 | // synthesized for types that have collections (such as arrays or dictionaries).
30 |
31 | // MARK: - SearchMetadata
32 | struct SearchMetadata: Codable, Equatable {
33 | let completedIn: Double
34 | let maxid: Double
35 | let maxidStr: String
36 | let nextResults: String
37 | let query: String
38 | let refreshurl: String
39 | let count: Int
40 | let sinceid: Int
41 | let sinceidStr: String
42 |
43 | enum CodingKeys: String, CodingKey {
44 | case completedIn = "completed_in"
45 | case maxid = "max_id"
46 | case maxidStr = "max_id_str"
47 | case nextResults = "next_results"
48 | case query = "query"
49 | case refreshurl = "refresh_url"
50 | case count = "count"
51 | case sinceid = "since_id"
52 | case sinceidStr = "since_id_str"
53 | }
54 | }
55 |
56 | //
57 | // Hashable or Equatable:
58 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
59 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
60 | // synthesized for types that have collections (such as arrays or dictionaries).
61 |
62 | // MARK: - Status
63 | struct Status: Codable, Equatable {
64 | let metadata: Metadata
65 | let createdAt: String
66 | let id: Double
67 | let idStr: String
68 | let text: String
69 | let source: String
70 | let truncated: Bool
71 | let inReplyToStatusid: Double?
72 | let inReplyToStatusidStr: String?
73 | let inReplyToUserid: Int?
74 | let inReplyToUseridStr: String?
75 | let inReplyToScreenName: String?
76 | let user: User
77 | //let geo: NSNull?
78 | // let coordinates: NSNull?
79 | // let place: NSNull?
80 | // let contributors: NSNull?
81 | let retweetCount: Int
82 | let favoriteCount: Int
83 | let entities: StatusEntities
84 | let favorited: Bool
85 | let retweeted: Bool
86 | let lang: String
87 | // let retweetedStatus: Status?
88 | let possiblySensitive: Bool?
89 |
90 | enum CodingKeys: String, CodingKey {
91 | case metadata = "metadata"
92 | case createdAt = "created_at"
93 | case id = "id"
94 | case idStr = "id_str"
95 | case text = "text"
96 | case source = "source"
97 | case truncated = "truncated"
98 | case inReplyToStatusid = "in_reply_to_status_id"
99 | case inReplyToStatusidStr = "in_reply_to_status_id_str"
100 | case inReplyToUserid = "in_reply_to_user_id"
101 | case inReplyToUseridStr = "in_reply_to_user_id_str"
102 | case inReplyToScreenName = "in_reply_to_screen_name"
103 | case user = "user"
104 | // case geo = "geo"
105 | // case coordinates = "coordinates"
106 | // case place = "place"
107 | // case contributors = "contributors"
108 | case retweetCount = "retweet_count"
109 | case favoriteCount = "favorite_count"
110 | case entities = "entities"
111 | case favorited = "favorited"
112 | case retweeted = "retweeted"
113 | case lang = "lang"
114 | // case retweetedStatus = "retweeted_status"
115 | case possiblySensitive = "possibly_sensitive"
116 | }
117 | }
118 |
119 | //
120 | // Hashable or Equatable:
121 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
122 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
123 | // synthesized for types that have collections (such as arrays or dictionaries).
124 |
125 | // MARK: - StatusEntities
126 | struct StatusEntities: Codable, Equatable {
127 | let hashtags: [Hashtag]
128 | // let symbols: NSNull
129 | let urls: [URLElement]
130 | let userMentions: [UserMention]
131 | let media: [Media]?
132 |
133 | enum CodingKeys: String, CodingKey {
134 | case hashtags = "hashtags"
135 | // case symbols = "symbols"
136 | case urls = "urls"
137 | case userMentions = "user_mentions"
138 | case media = "media"
139 | }
140 | }
141 |
142 | //
143 | // Hashable or Equatable:
144 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
145 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
146 | // synthesized for types that have collections (such as arrays or dictionaries).
147 |
148 | // MARK: - Hashtag
149 | struct Hashtag: Codable, Equatable {
150 | let text: String
151 | let indices: [Int]
152 |
153 | enum CodingKeys: String, CodingKey {
154 | case text = "text"
155 | case indices = "indices"
156 | }
157 | }
158 |
159 | //
160 | // Hashable or Equatable:
161 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
162 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
163 | // synthesized for types that have collections (such as arrays or dictionaries).
164 |
165 | // MARK: - Media
166 | struct Media: Codable, Equatable {
167 | let id: Double
168 | let idStr: String
169 | let indices: [Int]
170 | let mediaurl: String
171 | let mediaurlhttps: String
172 | let url: String
173 | let displayurl: String
174 | let expandedurl: String
175 | let type: String
176 | let sizes: Sizes
177 | let sourceStatusid: Double?
178 | let sourceStatusidStr: String?
179 |
180 | enum CodingKeys: String, CodingKey {
181 | case id = "id"
182 | case idStr = "id_str"
183 | case indices = "indices"
184 | case mediaurl = "media_url"
185 | case mediaurlhttps = "media_url_https"
186 | case url = "url"
187 | case displayurl = "display_url"
188 | case expandedurl = "expanded_url"
189 | case type = "type"
190 | case sizes = "sizes"
191 | case sourceStatusid = "source_status_id"
192 | case sourceStatusidStr = "source_status_id_str"
193 | }
194 | }
195 |
196 | //
197 | // Hashable or Equatable:
198 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
199 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
200 | // synthesized for types that have collections (such as arrays or dictionaries).
201 |
202 | // MARK: - Sizes
203 | struct Sizes: Codable, Equatable {
204 | let medium: Large
205 | let small: Large
206 | let thumb: Large
207 | let large: Large
208 |
209 | enum CodingKeys: String, CodingKey {
210 | case medium = "medium"
211 | case small = "small"
212 | case thumb = "thumb"
213 | case large = "large"
214 | }
215 | }
216 |
217 | //
218 | // Hashable or Equatable:
219 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
220 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
221 | // synthesized for types that have collections (such as arrays or dictionaries).
222 |
223 | // MARK: - Large
224 | struct Large: Codable, Equatable {
225 | let w: Int
226 | let h: Int
227 | let resize: String
228 |
229 | enum CodingKeys: String, CodingKey {
230 | case w = "w"
231 | case h = "h"
232 | case resize = "resize"
233 | }
234 | }
235 |
236 | //
237 | // Hashable or Equatable:
238 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
239 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
240 | // synthesized for types that have collections (such as arrays or dictionaries).
241 |
242 | // MARK: - URLElement
243 | struct URLElement: Codable, Equatable {
244 | let url: String
245 | let expandedurl: String
246 | let displayurl: String
247 | let indices: [Int]
248 |
249 | enum CodingKeys: String, CodingKey {
250 | case url = "url"
251 | case expandedurl = "expanded_url"
252 | case displayurl = "display_url"
253 | case indices = "indices"
254 | }
255 | }
256 |
257 | //
258 | // Hashable or Equatable:
259 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
260 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
261 | // synthesized for types that have collections (such as arrays or dictionaries).
262 |
263 | // MARK: - UserMention
264 | struct UserMention: Codable, Equatable {
265 | let screenName: String
266 | let name: String
267 | let id: Int
268 | let idStr: String
269 | let indices: [Int]
270 |
271 | enum CodingKeys: String, CodingKey {
272 | case screenName = "screen_name"
273 | case name = "name"
274 | case id = "id"
275 | case idStr = "id_str"
276 | case indices = "indices"
277 | }
278 | }
279 |
280 | //
281 | // Hashable or Equatable:
282 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
283 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
284 | // synthesized for types that have collections (such as arrays or dictionaries).
285 |
286 | // MARK: - Metadata
287 | struct Metadata: Codable, Equatable {
288 | let resultType: String
289 | let isoLanguageCode: String
290 |
291 | enum CodingKeys: String, CodingKey {
292 | case resultType = "result_type"
293 | case isoLanguageCode = "iso_language_code"
294 | }
295 | }
296 |
297 | //
298 | // Hashable or Equatable:
299 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
300 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
301 | // synthesized for types that have collections (such as arrays or dictionaries).
302 |
303 | // MARK: - User
304 | struct User: Codable, Equatable {
305 | let id: Int
306 | let idStr: String
307 | let name: String
308 | let screenName: String
309 | let location: String
310 | let userDescription: String
311 | let url: String?
312 | let entities: UserEntities
313 | let protected: Bool
314 | let followersCount: Int
315 | let friendsCount: Int
316 | let listedCount: Int
317 | let createdAt: String
318 | let favouritesCount: Int
319 | let utcOffset: Int?
320 | let timeZone: String?
321 | let geoEnabled: Bool
322 | let verified: Bool
323 | let statusesCount: Int
324 | let lang: String
325 | let contributorsEnabled: Bool
326 | let isTranslator: Bool
327 | let isTranslationEnabled: Bool
328 | let profileBackgroundColor: String
329 | let profileBackgroundImageurl: String
330 | let profileBackgroundImageurlhttps: String
331 | let profileBackgroundTile: Bool
332 | let profileImageurl: String
333 | let profileImageurlhttps: String
334 | let profileBannerurl: String?
335 | let profileLinkColor: String
336 | let profileSidebarBorderColor: String
337 | let profileSidebarFillColor: String
338 | let profileTextColor: String
339 | let profileUseBackgroundImage: Bool
340 | let defaultProfile: Bool
341 | let defaultProfileImage: Bool
342 | let following: Bool
343 | let followRequestSent: Bool
344 | let notifications: Bool
345 |
346 | enum CodingKeys: String, CodingKey {
347 | case id = "id"
348 | case idStr = "id_str"
349 | case name = "name"
350 | case screenName = "screen_name"
351 | case location = "location"
352 | case userDescription = "description"
353 | case url = "url"
354 | case entities = "entities"
355 | case protected = "protected"
356 | case followersCount = "followers_count"
357 | case friendsCount = "friends_count"
358 | case listedCount = "listed_count"
359 | case createdAt = "created_at"
360 | case favouritesCount = "favourites_count"
361 | case utcOffset = "utc_offset"
362 | case timeZone = "time_zone"
363 | case geoEnabled = "geo_enabled"
364 | case verified = "verified"
365 | case statusesCount = "statuses_count"
366 | case lang = "lang"
367 | case contributorsEnabled = "contributors_enabled"
368 | case isTranslator = "is_translator"
369 | case isTranslationEnabled = "is_translation_enabled"
370 | case profileBackgroundColor = "profile_background_color"
371 | case profileBackgroundImageurl = "profile_background_image_url"
372 | case profileBackgroundImageurlhttps = "profile_background_image_url_https"
373 | case profileBackgroundTile = "profile_background_tile"
374 | case profileImageurl = "profile_image_url"
375 | case profileImageurlhttps = "profile_image_url_https"
376 | case profileBannerurl = "profile_banner_url"
377 | case profileLinkColor = "profile_link_color"
378 | case profileSidebarBorderColor = "profile_sidebar_border_color"
379 | case profileSidebarFillColor = "profile_sidebar_fill_color"
380 | case profileTextColor = "profile_text_color"
381 | case profileUseBackgroundImage = "profile_use_background_image"
382 | case defaultProfile = "default_profile"
383 | case defaultProfileImage = "default_profile_image"
384 | case following = "following"
385 | case followRequestSent = "follow_request_sent"
386 | case notifications = "notifications"
387 | }
388 | }
389 |
390 | //
391 | // Hashable or Equatable:
392 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
393 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
394 | // synthesized for types that have collections (such as arrays or dictionaries).
395 |
396 | // MARK: - UserEntities
397 | struct UserEntities: Codable, Equatable {
398 | let entitiesDescription: Description
399 | let url: Description?
400 |
401 | enum CodingKeys: String, CodingKey {
402 | case entitiesDescription = "description"
403 | case url = "url"
404 | }
405 | }
406 |
407 | //
408 | // Hashable or Equatable:
409 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
410 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
411 | // synthesized for types that have collections (such as arrays or dictionaries).
412 |
413 | // MARK: - Description
414 | struct Description: Codable, Equatable {
415 | let urls: [URLElement]
416 |
417 | enum CodingKeys: String, CodingKey {
418 | case urls = "urls"
419 | }
420 | }
421 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/twitter2.json:
--------------------------------------------------------------------------------
1 | {
2 | "statuses": [
3 | {
4 | "metadata": {
5 | "result_type": "recent",
6 | "iso_language_code": "ja"
7 | },
8 | "created_at": "Sun Aug 31 00:29:15 +0000 2014",
9 | "id": 505874924095815700,
10 | "id_str": "505874924095815681",
11 | "text": "@aym0566x \n\n名前:前田あゆみ\n第一印象:なんか怖っ!\n今の印象:とりあえずキモい。噛み合わない\n好きなところ:ぶすでキモいとこ😋✨✨\n思い出:んーーー、ありすぎ😊❤️\nLINE交換できる?:あぁ……ごめん✋\nトプ画をみて:照れますがな😘✨\n一言:お前は一生もんのダチ💖",
12 | "source": "Twitter for iPhone",
13 | "truncated": false,
14 | "in_reply_to_status_id": null,
15 | "in_reply_to_status_id_str": null,
16 | "in_reply_to_user_id": 866260188,
17 | "in_reply_to_user_id_str": "866260188",
18 | "in_reply_to_screen_name": "aym0566x",
19 | "user": {
20 | "id": 1186275104,
21 | "id_str": "1186275104",
22 | "name": "AYUMI",
23 | "screen_name": "ayuu0123",
24 | "location": "",
25 | "description": "元野球部マネージャー❤︎…最高の夏をありがとう…❤︎",
26 | "url": null,
27 | "entities": {
28 | "description": {
29 | "urls": []
30 | }
31 | },
32 | "protected": false,
33 | "followers_count": 262,
34 | "friends_count": 252,
35 | "listed_count": 0,
36 | "created_at": "Sat Feb 16 13:40:25 +0000 2013",
37 | "favourites_count": 235,
38 | "utc_offset": null,
39 | "time_zone": null,
40 | "geo_enabled": false,
41 | "verified": false,
42 | "statuses_count": 1769,
43 | "lang": "en",
44 | "contributors_enabled": false,
45 | "is_translator": false,
46 | "is_translation_enabled": false,
47 | "profile_background_color": "C0DEED",
48 | "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png",
49 | "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png",
50 | "profile_background_tile": false,
51 | "profile_image_url": "http://pbs.twimg.com/profile_images/497760886795153410/LDjAwR_y_normal.jpeg",
52 | "profile_image_url_https": "https://pbs.twimg.com/profile_images/497760886795153410/LDjAwR_y_normal.jpeg",
53 | "profile_banner_url": "https://pbs.twimg.com/profile_banners/1186275104/1409318784",
54 | "profile_link_color": "0084B4",
55 | "profile_sidebar_border_color": "C0DEED",
56 | "profile_sidebar_fill_color": "DDEEF6",
57 | "profile_text_color": "333333",
58 | "profile_use_background_image": true,
59 | "default_profile": true,
60 | "default_profile_image": false,
61 | "following": false,
62 | "follow_request_sent": false,
63 | "notifications": false
64 | },
65 | "geo": null,
66 | "coordinates": null,
67 | "place": null,
68 | "contributors": null,
69 | "retweet_count": 0,
70 | "favorite_count": 0,
71 | "entities": {
72 | "hashtags": [],
73 | "symbols": [],
74 | "urls": [],
75 | "user_mentions": [
76 | {
77 | "screen_name": "aym0566x",
78 | "name": "前田あゆみ",
79 | "id": 866260188,
80 | "id_str": "866260188",
81 | "indices": [
82 | 0,
83 | 9
84 | ]
85 | }
86 | ]
87 | },
88 | "favorited": false,
89 | "retweeted": false,
90 | "lang": "ja"
91 | }
92 | ],
93 | "search_metadata": {
94 | "completed_in": 0.087,
95 | "max_id": 505874924095815700,
96 | "max_id_str": "505874924095815681",
97 | "next_results": "?max_id=505874847260352512&q=%E4%B8%80&count=100&include_entities=1",
98 | "query": "%E4%B8%80",
99 | "refresh_url": "?since_id=505874924095815681&q=%E4%B8%80&include_entities=1",
100 | "count": 100,
101 | "since_id": 0,
102 | "since_id_str": "0"
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/twitterCamel.swift:
--------------------------------------------------------------------------------
1 | // This file was generated from JSON Schema using quicktype, do not modify it directly.
2 | // To parse the JSON, add this file to your project and do:
3 | //
4 | // let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
5 |
6 | //
7 | // Hashable or Equatable:
8 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
9 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
10 | // synthesized for types that have collections (such as arrays or dictionaries).
11 |
12 | /*import Foundation
13 |
14 | // MARK: - Welcome
15 | struct TwitterCamel: Codable, Equatable {
16 | let statuses: [Status]
17 | let searchMetadata: SearchMetadata
18 | }
19 |
20 | //
21 | // Hashable or Equatable:
22 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
23 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
24 | // synthesized for types that have collections (such as arrays or dictionaries).
25 |
26 | // MARK: - SearchMetadata
27 | struct SearchMetadata: Codable, Equatable {
28 | let completedIn: Double
29 | let maxid: Double
30 | let maxidStr: String
31 | let nextResults: String
32 | let query: String
33 | let refreshurl: String
34 | let count: Int
35 | let sinceid: Int
36 | let sinceidStr: String
37 | }
38 |
39 | //
40 | // Hashable or Equatable:
41 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
42 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
43 | // synthesized for types that have collections (such as arrays or dictionaries).
44 |
45 | // MARK: - Status
46 | struct Status: Codable, Equatable {
47 | let metadata: Metadata
48 | let createdAt: String
49 | let id: Double
50 | let idStr: String
51 | let text: String
52 | let source: String
53 | let truncated: Bool
54 | let inReplyToStatusid: Double?
55 | let inReplyToStatusidStr: String?
56 | let inReplyToUserid: Int?
57 | let inReplyToUseridStr: String?
58 | let inReplyToScreenName: String?
59 | let user: User
60 | //let geo: NSNull?
61 | // let coordinates: NSNull?
62 | // let place: NSNull?
63 | // let contributors: NSNull?
64 | let retweetCount: Int
65 | let favoriteCount: Int
66 | let entities: StatusEntities
67 | let favorited: Bool
68 | let retweeted: Bool
69 | let lang: String
70 | // let retweetedStatus: Status?
71 | let possiblySensitive: Bool?
72 | }
73 |
74 | //
75 | // Hashable or Equatable:
76 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
77 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
78 | // synthesized for types that have collections (such as arrays or dictionaries).
79 |
80 | // MARK: - StatusEntities
81 | struct StatusEntities: Codable, Equatable {
82 | let hashtags: [Hashtag]
83 | // let symbols: NSNull
84 | let urls: [URLElement]
85 | let userMentions: [UserMention]
86 | let media: [Media]?
87 | }
88 |
89 | //
90 | // Hashable or Equatable:
91 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
92 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
93 | // synthesized for types that have collections (such as arrays or dictionaries).
94 |
95 | // MARK: - Hashtag
96 | struct Hashtag: Codable, Equatable {
97 | let text: String
98 | let indices: [Int]
99 | }
100 |
101 | //
102 | // Hashable or Equatable:
103 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
104 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
105 | // synthesized for types that have collections (such as arrays or dictionaries).
106 |
107 | // MARK: - Media
108 | struct Media: Codable, Equatable {
109 | let id: Double
110 | let idStr: String
111 | let indices: [Int]
112 | let mediaurl: String
113 | let mediaurlhttps: String
114 | let url: String
115 | let displayurl: String
116 | let expandedurl: String
117 | let type: String
118 | let sizes: Sizes
119 | let sourceStatusid: Double?
120 | let sourceStatusidStr: String?
121 | }
122 |
123 | //
124 | // Hashable or Equatable:
125 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
126 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
127 | // synthesized for types that have collections (such as arrays or dictionaries).
128 |
129 | // MARK: - Sizes
130 | struct Sizes: Codable, Equatable {
131 | let medium: Large
132 | let small: Large
133 | let thumb: Large
134 | let large: Large
135 | }
136 |
137 | //
138 | // Hashable or Equatable:
139 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
140 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
141 | // synthesized for types that have collections (such as arrays or dictionaries).
142 |
143 | // MARK: - Large
144 | struct Large: Codable, Equatable {
145 | let w: Int
146 | let h: Int
147 | let resize: String
148 | }
149 |
150 | //
151 | // Hashable or Equatable:
152 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
153 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
154 | // synthesized for types that have collections (such as arrays or dictionaries).
155 |
156 | // MARK: - URLElement
157 | struct URLElement: Codable, Equatable {
158 | let url: String
159 | let expandedurl: String
160 | let displayurl: String
161 | let indices: [Int]
162 | }
163 |
164 | //
165 | // Hashable or Equatable:
166 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
167 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
168 | // synthesized for types that have collections (such as arrays or dictionaries).
169 |
170 | // MARK: - UserMention
171 | struct UserMention: Codable, Equatable {
172 | let screenName: String
173 | let name: String
174 | let id: Int
175 | let idStr: String
176 | let indices: [Int]
177 | }
178 |
179 | //
180 | // Hashable or Equatable:
181 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
182 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
183 | // synthesized for types that have collections (such as arrays or dictionaries).
184 |
185 | // MARK: - Metadata
186 | struct Metadata: Codable, Equatable {
187 | let resultType: String
188 | let isoLanguageCode: String
189 | }
190 |
191 | //
192 | // Hashable or Equatable:
193 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
194 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
195 | // synthesized for types that have collections (such as arrays or dictionaries).
196 |
197 | // MARK: - User
198 | struct User: Codable, Equatable {
199 | let id: Int
200 | let idStr: String
201 | let name: String
202 | let screenName: String
203 | let location: String
204 | let userDescription: String
205 | let url: String?
206 | let entities: UserEntities
207 | let protected: Bool
208 | let followersCount: Int
209 | let friendsCount: Int
210 | let listedCount: Int
211 | let createdAt: String
212 | let favouritesCount: Int
213 | let utcOffset: Int?
214 | let timeZone: String?
215 | let geoEnabled: Bool
216 | let verified: Bool
217 | let statusesCount: Int
218 | let lang: String
219 | let contributorsEnabled: Bool
220 | let isTranslator: Bool
221 | let isTranslationEnabled: Bool
222 | let profileBackgroundColor: String
223 | let profileBackgroundImageurl: String
224 | let profileBackgroundImageurlhttps: String
225 | let profileBackgroundTile: Bool
226 | let profileImageurl: String
227 | let profileImageurlhttps: String
228 | let profileBannerurl: String?
229 | let profileLinkColor: String
230 | let profileSidebarBorderColor: String
231 | let profileSidebarFillColor: String
232 | let profileTextColor: String
233 | let profileUseBackgroundImage: Bool
234 | let defaultProfile: Bool
235 | let defaultProfileImage: Bool
236 | let following: Bool
237 | let followRequestSent: Bool
238 | let notifications: Bool
239 | }
240 |
241 | //
242 | // Hashable or Equatable:
243 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
244 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
245 | // synthesized for types that have collections (such as arrays or dictionaries).
246 |
247 | // MARK: - UserEntities
248 | struct UserEntities: Codable, Equatable {
249 | let entitiesDescription: Description
250 | let url: Description?
251 | }
252 |
253 | //
254 | // Hashable or Equatable:
255 | // The compiler will not be able to synthesize the implementation of Hashable or Equatable
256 | // for types that require the use of JSONAny, nor will the implementation of Hashable be
257 | // synthesized for types that have collections (such as arrays or dictionaries).
258 |
259 | // MARK: - Description
260 | struct Description: Codable, Equatable {
261 | let urls: [URLElement]
262 | }*/
263 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Models/user.swift:
--------------------------------------------------------------------------------
1 | //
2 | // user.swift
3 | // ZipPhone
4 | //
5 | // Created by Michael Eisel on 2/22/20.
6 | // Copyright © 2020 Michael Eisel. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct User2: Decodable, Equatable {
12 | let firstName: String
13 | let lastName: String
14 | let email: String
15 | let login: String
16 | let passwordHash: String
17 | let gender: String
18 | let avatarUrl: String
19 | let country: String
20 | let city: String
21 | let zipCode: Int
22 | let phone: String
23 | let isVip: Bool
24 | let isFamous: Bool
25 | }
26 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/BinaryDecoderNegativeTests.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// BinaryDecoderNegativeTests.swift
3 | ///
4 | /// Copyright 2017 Tony Stone
5 | ///
6 | /// Licensed under the Apache License, Version 2.0 (the "License");
7 | /// you may not use this file except in compliance with the License.
8 | /// You may obtain a copy of the License at
9 | ///
10 | /// http://www.apache.org/licenses/LICENSE-2.0
11 | ///
12 | /// Unless required by applicable law or agreed to in writing, software
13 | /// distributed under the License is distributed on an "AS IS" BASIS,
14 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | /// See the License for the specific language governing permissions and
16 | /// limitations under the License.
17 | ///
18 | /// Created by Tony Stone on 10/23/17.
19 | ///
20 | import XCTest
21 |
22 | ///
23 | /// Note: This file contains public interface tests so do not use @testable.
24 | ///
25 | import ZippyJSON
26 |
27 | ///
28 | /// `BinaryDecoderNegativeTests`
29 | ///
30 | class BinaryDecoderNegativeTests: XCTestCase {
31 |
32 | ///
33 | /// Test to that `_BinaryDecoder.container(forKey:)` func throws a `DecoderError.typeMismatch` error if it encounters a container type other than `KeyedDecodingContainer`.
34 | ///
35 | func testContainerWhenTypeMismatch() {
36 |
37 | struct InputType: Codable {
38 | var value: String
39 | init(value: String) { self.value = value }
40 |
41 | init(from decoder: Decoder) throws {
42 | var container = try decoder.unkeyedContainer()
43 | self.value = try container.decode(String.self)
44 | }
45 | func encode(to encoder: Encoder) throws {
46 | var container = encoder.unkeyedContainer()
47 | try container.encode(self.value)
48 | }
49 | }
50 |
51 | struct ExpectedType: Codable {
52 | var value: String
53 | init(value: String) { self.value = value }
54 | enum CodingKeys: CodingKey { case value }
55 | }
56 | _testDecodeTypeMismatch(input: InputType(value: "Test String"), expected: (type: ExpectedType.self, errorType: KeyedDecodingContainer.self, codingPath: [], description: "Expected to decode KeyedDecodingContainer but found UnkeyedDecodingContainer instead."))
57 | }
58 |
59 | ///
60 | /// Test to that `_BinaryDecoder.unkeydContainer()` func throws a `DecoderError.typeMismatch` error if it encounters a container type other than `UnkeyedDecodingContainer`.
61 | ///
62 | func testUnkeyedContainerWhenTypeMismatch() {
63 |
64 | struct InputType: Codable {
65 | var value: String
66 | init(value: String) { self.value = value }
67 | enum CodingKeys: CodingKey { case value }
68 | }
69 |
70 | struct ExpectedType: Codable {
71 | var value: String
72 | init(value: String) { self.value = value }
73 |
74 | enum CodingKeys: CodingKey { case value }
75 |
76 | init(from decoder: Decoder) throws {
77 | var container = try decoder.unkeyedContainer()
78 | self.value = try container.decode(String.self)
79 | }
80 | func encode(to encoder: Encoder) throws {
81 | var container = encoder.unkeyedContainer()
82 | try container.encode(self.value)
83 | }
84 | }
85 | _testDecodeTypeMismatch(input: InputType(value: "Test String"), expected: (type: ExpectedType.self, errorType: UnkeyedDecodingContainer.self, codingPath: [], description: "Expected to decode UnkeyedDecodingContainer but found KeyedDecodingContainer instead."))
86 | }
87 |
88 | ///
89 | /// Test to that `_BinaryDecoder.singleValueContainer()` func throws a `DecoderError.typeMismatch` error if it encounters a container type other than `SingleValueDecodingContainer`.
90 | ///
91 | func testSingleValueContainerWhenTypeMismatch() {
92 |
93 | struct InputType: Codable {
94 | var value: String
95 | init(value: String) { self.value = value }
96 | enum CodingKeys: CodingKey { case value }
97 | }
98 |
99 | struct ExpectedType: Codable {
100 | var value: String
101 | init(value: String) { self.value = value }
102 |
103 | enum CodingKeys: CodingKey { case value }
104 |
105 | init(from decoder: Decoder) throws {
106 | let container = try decoder.singleValueContainer()
107 | self.value = try container.decode(String.self)
108 | }
109 | func encode(to encoder: Encoder) throws {
110 | var container = encoder.singleValueContainer()
111 | try container.encode(self.value)
112 | }
113 | }
114 | _testDecodeTypeMismatch(input: InputType(value: "Test String"), expected: (type: ExpectedType.self, errorType: String.self, codingPath: [], description: "Expected to decode String but found KeyedDecodingContainer instead."))
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/BinaryEncoderNegativeTests.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// BinaryEncoderNegativeTests.swift
3 | ///
4 | /// Copyright 2017 Tony Stone
5 | ///
6 | /// Licensed under the Apache License, Version 2.0 (the "License");
7 | /// you may not use this file except in compliance with the License.
8 | /// You may obtain a copy of the License at
9 | ///
10 | /// http://www.apache.org/licenses/LICENSE-2.0
11 | ///
12 | /// Unless required by applicable law or agreed to in writing, software
13 | /// distributed under the License is distributed on an "AS IS" BASIS,
14 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | /// See the License for the specific language governing permissions and
16 | /// limitations under the License.
17 | ///
18 | /// Created by Tony Stone on 10/23/17.
19 | ///
20 | import XCTest
21 |
22 | ///
23 | /// Note: This file contains public interface tests so do not use @testable.
24 | ///
25 | import ZippyJSON
26 |
27 | ///
28 | /// Negative tests for `BinaryEncoder` when a user object throws an error.
29 | ///
30 | class BinaryEncoderUserObjectNegativeTests: XCTestCase {
31 |
32 | ///
33 | /// Test the ability to rethrow an error occurring in the user's code during encoding.
34 | ///
35 | func testEncodeWithUserObjectThrowing() {
36 |
37 | enum Error: Swift.Error { case testError(String) }
38 |
39 | struct InputType: Codable {
40 | var value: Int
41 | init(value: Int) { self.value = value }
42 |
43 | enum CodingKeys: CodingKey { case value }
44 |
45 | init(from decoder: Decoder) throws {
46 | throw Error.testError("Test Error")
47 | }
48 | func encode(to encoder: Encoder) throws {
49 | throw Error.testError("Test Error")
50 | }
51 | }
52 |
53 | let encoder = JSONEncoder()
54 |
55 | XCTAssertThrowsError(try encoder.encode(InputType(value: 1))) { (error) in
56 | switch error {
57 | case Error.testError(let message):
58 | XCTAssertEqual(message, "Test Error")
59 |
60 | default: XCTFail("Incorrect error returned: \(error)")
61 | }
62 | }
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/BinaryEncodingSingleValueContainerNegativeTests.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// BinaryEncodingSingleValueContainerNegativeTests.swift
3 | ///
4 | /// Copyright 2017 Tony Stone
5 | ///
6 | /// Licensed under the Apache License, Version 2.0 (the "License");
7 | /// you may not use this file except in compliance with the License.
8 | /// You may obtain a copy of the License at
9 | ///
10 | /// http://www.apache.org/licenses/LICENSE-2.0
11 | ///
12 | /// Unless required by applicable law or agreed to in writing, software
13 | /// distributed under the License is distributed on an "AS IS" BASIS,
14 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | /// See the License for the specific language governing permissions and
16 | /// limitations under the License.
17 | ///
18 | /// Created by Tony Stone on 10/23/17.
19 | ///
20 | import XCTest
21 |
22 | ///
23 | /// Note: This file contains public interface tests so do not use @testable.
24 | ///
25 | import ZippyJSON
26 |
27 | /// =================================== NOTICE ========================================
28 | /// Do NOT edit this file directly as it will be regenerated automatically when needed.
29 | ///
30 | /// Modify the *.swift.gyb file instead.
31 | /// ===================================================================================
32 |
33 | ///
34 | /// Negative tests for single value containers.
35 | ///
36 | class BinaryEncodingSingleValueContainerNegativeTests: XCTestCase {
37 |
38 | // MARK: - `Bool` Tests
39 |
40 | ///
41 | /// Test that a `DecodingError.valueNotFound` is thrown when a user tries to decode a `Bool` value that was not prevously encoded at the root-level.
42 | ///
43 | func testDecodeValueNotFoundOfBool() {
44 | _testDecodeValueNotFound(input: [Optional.none], expected: ([Bool].self, [Bool].self, [], "Expected Bool value but found null instead."))
45 | }
46 |
47 | ///
48 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `Bool` value at the root-level and finds a container other than a SingleValueDecodingContainer.
49 | ///
50 | func testDecodeTypeMismatchOfBoolWhenIncorrectContainerType() {
51 |
52 | struct InputType: Codable {
53 | var value: Bool
54 | init(value: Bool) { self.value = value }
55 | }
56 | _testDecodeTypeMismatch(input: InputType(value: true), expected: (Bool.self, Bool.self, [], "Expected to decode Bool but found KeyedDecodingContainer instead."))
57 | }
58 |
59 | // MARK: - `Int` Tests
60 |
61 | ///
62 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `Int` value at the root-level and finds a container other than a SingleValueDecodingContainer.
63 | ///
64 | func testDecodeTypeMismatchOfIntWhenIncorrectContainerType() {
65 |
66 | struct InputType: Codable {
67 | var value: Int
68 | init(value: Int) { self.value = value }
69 | }
70 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (Int.self, Int.self, [], "Expected to decode Int but found KeyedDecodingContainer instead."))
71 | }
72 |
73 | // MARK: - `Int8` Tests
74 |
75 | ///
76 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `Int8` value at the root-level and finds a container other than a SingleValueDecodingContainer.
77 | ///
78 | func testDecodeTypeMismatchOfInt8WhenIncorrectContainerType() {
79 |
80 | struct InputType: Codable {
81 | var value: Int8
82 | init(value: Int8) { self.value = value }
83 | }
84 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (Int8.self, Int8.self, [], "Expected to decode Int8 but found KeyedDecodingContainer instead."))
85 | }
86 |
87 | // MARK: - `Int16` Tests
88 |
89 | ///
90 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `Int16` value at the root-level and finds a container other than a SingleValueDecodingContainer.
91 | ///
92 | func testDecodeTypeMismatchOfInt16WhenIncorrectContainerType() {
93 |
94 | struct InputType: Codable {
95 | var value: Int16
96 | init(value: Int16) { self.value = value }
97 | }
98 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (Int16.self, Int16.self, [], "Expected to decode Int16 but found KeyedDecodingContainer instead."))
99 | }
100 |
101 | // MARK: - `Int32` Tests
102 |
103 | ///
104 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `Int32` value at the root-level and finds a container other than a SingleValueDecodingContainer.
105 | ///
106 | func testDecodeTypeMismatchOfInt32WhenIncorrectContainerType() {
107 |
108 | struct InputType: Codable {
109 | var value: Int32
110 | init(value: Int32) { self.value = value }
111 | }
112 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (Int32.self, Int32.self, [], "Expected to decode Int32 but found KeyedDecodingContainer instead."))
113 | }
114 |
115 | // MARK: - `Int64` Tests
116 |
117 | ///
118 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `Int64` value at the root-level and finds a container other than a SingleValueDecodingContainer.
119 | ///
120 | func testDecodeTypeMismatchOfInt64WhenIncorrectContainerType() {
121 |
122 | struct InputType: Codable {
123 | var value: Int64
124 | init(value: Int64) { self.value = value }
125 | }
126 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (Int64.self, Int64.self, [], "Expected to decode Int64 but found KeyedDecodingContainer instead."))
127 | }
128 |
129 | // MARK: - `UInt` Tests
130 |
131 | ///
132 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `UInt` value at the root-level and finds a container other than a SingleValueDecodingContainer.
133 | ///
134 | func testDecodeTypeMismatchOfUIntWhenIncorrectContainerType() {
135 |
136 | struct InputType: Codable {
137 | var value: UInt
138 | init(value: UInt) { self.value = value }
139 | }
140 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (UInt.self, UInt.self, [], "Expected to decode UInt but found KeyedDecodingContainer instead."))
141 | }
142 |
143 | // MARK: - `UInt8` Tests
144 |
145 | ///
146 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `UInt8` value at the root-level and finds a container other than a SingleValueDecodingContainer.
147 | ///
148 | func testDecodeTypeMismatchOfUInt8WhenIncorrectContainerType() {
149 |
150 | struct InputType: Codable {
151 | var value: UInt8
152 | init(value: UInt8) { self.value = value }
153 | }
154 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (UInt8.self, UInt8.self, [], "Expected to decode UInt8 but found KeyedDecodingContainer instead."))
155 | }
156 |
157 | // MARK: - `UInt16` Tests
158 |
159 | ///
160 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `UInt16` value at the root-level and finds a container other than a SingleValueDecodingContainer.
161 | ///
162 | func testDecodeTypeMismatchOfUInt16WhenIncorrectContainerType() {
163 |
164 | struct InputType: Codable {
165 | var value: UInt16
166 | init(value: UInt16) { self.value = value }
167 | }
168 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (UInt16.self, UInt16.self, [], "Expected to decode UInt16 but found KeyedDecodingContainer instead."))
169 | }
170 |
171 | // MARK: - `UInt32` Tests
172 |
173 | ///
174 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `UInt32` value at the root-level and finds a container other than a SingleValueDecodingContainer.
175 | ///
176 | func testDecodeTypeMismatchOfUInt32WhenIncorrectContainerType() {
177 |
178 | struct InputType: Codable {
179 | var value: UInt32
180 | init(value: UInt32) { self.value = value }
181 | }
182 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (UInt32.self, UInt32.self, [], "Expected to decode UInt32 but found KeyedDecodingContainer instead."))
183 | }
184 |
185 | // MARK: - `UInt64` Tests
186 |
187 | ///
188 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `UInt64` value at the root-level and finds a container other than a SingleValueDecodingContainer.
189 | ///
190 | func testDecodeTypeMismatchOfUInt64WhenIncorrectContainerType() {
191 |
192 | struct InputType: Codable {
193 | var value: UInt64
194 | init(value: UInt64) { self.value = value }
195 | }
196 | _testDecodeTypeMismatch(input: InputType(value: 32), expected: (UInt64.self, UInt64.self, [], "Expected to decode UInt64 but found KeyedDecodingContainer instead."))
197 | }
198 |
199 | // MARK: - `Float` Tests
200 |
201 | ///
202 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `Float` value at the root-level and finds a container other than a SingleValueDecodingContainer.
203 | ///
204 | func testDecodeTypeMismatchOfFloatWhenIncorrectContainerType() {
205 |
206 | struct InputType: Codable {
207 | var value: Float
208 | init(value: Float) { self.value = value }
209 | }
210 | _testDecodeTypeMismatch(input: InputType(value: 32.0), expected: (Float.self, Float.self, [], "Expected to decode Float but found KeyedDecodingContainer instead."))
211 | }
212 |
213 | // MARK: - `Double` Tests
214 |
215 | ///
216 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `Double` value at the root-level and finds a container other than a SingleValueDecodingContainer.
217 | ///
218 | func testDecodeTypeMismatchOfDoubleWhenIncorrectContainerType() {
219 |
220 | struct InputType: Codable {
221 | var value: Double
222 | init(value: Double) { self.value = value }
223 | }
224 | _testDecodeTypeMismatch(input: InputType(value: 32.0), expected: (Double.self, Double.self, [], "Expected to decode Double but found KeyedDecodingContainer instead."))
225 | }
226 |
227 | // MARK: - `String` Tests
228 |
229 | ///
230 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `String` value at the root-level and finds a container other than a SingleValueDecodingContainer.
231 | ///
232 | func testDecodeTypeMismatchOfStringWhenIncorrectContainerType() {
233 |
234 | struct InputType: Codable {
235 | var value: String
236 | init(value: String) { self.value = value }
237 | }
238 | _testDecodeTypeMismatch(input: InputType(value: "Test String"), expected: (String.self, String.self, [], "Expected to decode String but found KeyedDecodingContainer instead."))
239 | }
240 |
241 | // MARK: - `CodableType` Tests
242 |
243 | ///
244 | /// Note: Currently codable types using a SingleValueDecodingContainer have different error messages and type that single values because of the nature of the encoding.
245 | ///
246 |
247 | ///
248 | /// Test that a `DecodingError.typeMismatch` is thrown when a user tries to decode a `CodableType` value at the root-level and finds a container other that a SingleValueDecodingContainer.
249 | ///
250 | func testDecodeTypeMismatchOfCodableTypeWhenIncorrectContainerType() {
251 |
252 | struct InputType: Codable {
253 | var value: CodableType
254 | init(value: CodableType) { self.value = value }
255 | }
256 | _testDecodeTypeMismatch(input: InputType(value: CodableType(32)), expected: (CodableType.self, Int.self, [CodableType.CodingKeys.value], "Expected to decode Int but found KeyedDecodingContainer instead."))
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/BinaryEncodingStructuredTypeTests.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// BinaryEncodingStructuredTypeTests.swift
3 | ///
4 | /// Copyright 2017 Tony Stone
5 | ///
6 | /// Licensed under the Apache License, Version 2.0 (the "License");
7 | /// you may not use this file except in compliance with the License.
8 | /// You may obtain a copy of the License at
9 | ///
10 | /// http://www.apache.org/licenses/LICENSE-2.0
11 | ///
12 | /// Unless required by applicable law or agreed to in writing, software
13 | /// distributed under the License is distributed on an "AS IS" BASIS,
14 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | /// See the License for the specific language governing permissions and
16 | /// limitations under the License.
17 | ///
18 | /// Created by Tony Stone on 10/6/17.
19 | ///
20 | import XCTest
21 |
22 | import ZippyJSON
23 |
24 | ///
25 | /// Tests relating to both `BinaryEncoder` and `BinaryDecoder` encoding structured types Array's, Dictionaries, and custom types.
26 | ///
27 | class BinaryEncodingStructuredTypeTests: XCTestCase {
28 |
29 | // MARK: - `Array`
30 |
31 | ///
32 | /// Test the ability to encode/decode an Array of Ints.
33 | ///
34 | func testEncodingDecodeOfArrayOfInt() throws {
35 | let input: [Int] = [1, 2, 3, 4, 5, 6]
36 | let expected: [Int] = [1, 2, 3, 4, 5, 6]
37 |
38 | _testCodableRoundTrip(input: input) { (result) in
39 | XCTAssertEqual(result, expected)
40 | }
41 | }
42 |
43 | // MARK: - `Dictionary`
44 |
45 | ///
46 | /// Test the ability to encode/decode a simple Dictionary.
47 | ///
48 | func testEncodingDecodeOfDictionaryOfEnum() throws {
49 |
50 | enum Enum { case zero, one, two, three, four, five, six }
51 |
52 | let input: [String: Int] = ["zero": 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6]
53 | let expected: [String: Int] = ["zero": 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6]
54 |
55 | _testCodableRoundTrip(input: input) { (result) in
56 | XCTAssertEqual(result, expected)
57 | }
58 | }
59 |
60 | ///
61 | /// Test the ability to encode/decode a simple Dictionary.
62 | ///
63 | func testEncodingDecodeOfDictionaryOfEnumValues() throws {
64 |
65 | enum Enum: Int, Codable { case zero, one, two, three, four, five, six }
66 |
67 | let input: [String: Enum] = ["zero": Enum.zero, "one": Enum.one, "two": Enum.two, "three": Enum.three, "four": Enum.four, "five": Enum.five, "six": Enum.six]
68 | let expected: [String: Enum] = ["zero": Enum.zero, "one": Enum.one, "two": Enum.two, "three": Enum.three, "four": Enum.four, "five": Enum.five, "six": Enum.six]
69 |
70 | _testCodableRoundTrip(input: input) { (result) in
71 | XCTAssertEqual(result, expected)
72 | }
73 | }
74 |
75 | ///
76 | /// Test the ability to encode/decode a simple Dictionary.
77 | ///
78 | func testEncodingDecodeOfDictionaryOfEnumKeys() throws {
79 |
80 | enum Enum: Int, Codable { case zero, one, two, three, four, five, six }
81 |
82 | let input: [Enum: String] = [Enum.zero: "zero", Enum.one: "one", Enum.two: "two", Enum.three: "three", Enum.four: "four", Enum.five: "five", Enum.six: "six"]
83 | let expected: [Enum: String] = [Enum.zero: "zero", Enum.one: "one", Enum.two: "two", Enum.three: "three", Enum.four: "four", Enum.five: "five", Enum.six: "six"]
84 |
85 | _testCodableRoundTrip(input: input) { (result) in
86 | XCTAssertEqual(result, expected)
87 | }
88 | }
89 |
90 | // MARK: - Structured types
91 |
92 | ///
93 | /// Test the ability to encode/decode a basic structured type class.
94 | ///
95 | func testEncodingDecodeOfBasicClass() {
96 |
97 | _testCodableRoundTrip(input: BasicClass()) { (result) in
98 | XCTAssertEqual(result, BasicClass())
99 | }
100 | }
101 |
102 | ///
103 | /// Test the ability to encode/decode a basic structured type struct.
104 | ///
105 | func testEncodingDecodeOfBasicStruct() {
106 |
107 | _testCodableRoundTrip(input: BasicStruct()) { (result) in
108 | XCTAssertEqual(result, BasicStruct())
109 | }
110 | }
111 |
112 | ///
113 | /// Test the ability to encode/decode a more complex structured type class.
114 | ///
115 | func testEncodingDecodeOfComplexClass() {
116 |
117 | _testCodableRoundTrip(input: ComplexClass()) { (result) in
118 | XCTAssertEqual(result, ComplexClass())
119 | }
120 | }
121 |
122 | ///
123 | /// Test the ability to encode/decode a more complex structured type struct.
124 | ///
125 | func testEncodingDecodeOfComplexStruct() {
126 |
127 | _testCodableRoundTrip(input: ComplexStruct()) { (result) in
128 | XCTAssertEqual(result, ComplexStruct())
129 | }
130 | }
131 |
132 | ///
133 | /// Test the ability to encode/decode a more complex structured type class with a super class.
134 | ///
135 | func testEncodingDecodeOfComplexSubclassClass() {
136 |
137 | _testCodableRoundTrip(input: ComplexSubclassClass()) { (result) in
138 | XCTAssertEqual(result, ComplexSubclassClass())
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/DocumentationExampleTests.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// DocumentationExampleTests.swift
3 | ///
4 | /// Copyright 2019 Tony Stone
5 | ///
6 | /// Licensed under the Apache License, Version 2.0 (the "License");
7 | /// you may not use this file except in compliance with the License.
8 | /// You may obtain a copy of the License at
9 | ///
10 | /// http://www.apache.org/licenses/LICENSE-2.0
11 | ///
12 | /// Unless required by applicable law or agreed to in writing, software
13 | /// distributed under the License is distributed on an "AS IS" BASIS,
14 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | /// See the License for the specific language governing permissions and
16 | /// limitations under the License.
17 | ///
18 | /// Created by Tony Stone on 2/13/19.
19 | ///
20 | import XCTest
21 |
22 | import ZippyJSON
23 |
24 | class DocumentationExampleTests: XCTestCase {
25 |
26 | let encoder = JSONEncoder()
27 | let decoder = ZippyJSONDecoder()
28 |
29 | func testBinaryEncoderExample2() throws {
30 |
31 | /// Actual code in example.
32 | ///
33 | struct Employee: Codable {
34 | let first: String
35 | let last: String
36 | let employeeNumber: Int
37 | }
38 |
39 | let employee = Employee(first: "John", last: "Doe", employeeNumber: 2345643)
40 |
41 | let _ = try encoder.encode(employee)
42 | }
43 |
44 | func testBinaryDecoderExample1() throws {
45 |
46 | struct Employee: Codable {
47 | let first: String
48 | let last: String
49 | let employeeNumber: Int
50 | }
51 |
52 | let bytes = try encoder.encode(Employee(first: "John", last: "Doe", employeeNumber: 2345643))
53 |
54 | /// Actual code in example.
55 | ///
56 | let _ = try decoder.decode(Employee.self, from: bytes)
57 | }
58 |
59 | func testEncodedDataExample1() throws {
60 |
61 | /// Code from previous example used as boiler plate for this example.
62 | ///
63 | struct Employee: Codable {
64 | let first: String
65 | let last: String
66 | let employeeNumber: Int
67 | }
68 |
69 | let employee = Employee(first: "John", last: "Doe", employeeNumber: 2345643)
70 |
71 | /// Actual code in example.
72 | ///
73 | let bytes = try encoder.encode(employee)
74 |
75 | FileManager.default.createFile(atPath: "employee.bin", contents: Data(bytes))
76 | }
77 |
78 | func testArrayConstructionExample() throws {
79 |
80 | let encoder = JSONEncoder()
81 | let bytes = try encoder.encode(["String 1", "String 2"])
82 |
83 | let _ = Array(bytes)
84 | }
85 |
86 | func testDataConstructionExample() throws {
87 |
88 | let encoder = JSONEncoder()
89 | let bytes = try encoder.encode(["String 1", "String 2"])
90 |
91 | let _ = Data(bytes)
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/EncodeDecodeTester.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// EncodingTester.swift
3 | /// Sticky
4 | ///
5 | /// Created by Tony Stone on 10/8/17.
6 | ///
7 | ///
8 | import XCTest
9 |
10 | import ZippyJSON
11 |
12 | ///
13 | /// Generic func to test encoding and decoding round trip.
14 | ///
15 | internal func _testCodableRoundTrip(input: T, file: StaticString = #file, line: UInt = #line, validation: (T) -> Void) {
16 | do {
17 | let encoder = JSONEncoder()
18 | let decoder = ZippyJSONDecoder()
19 |
20 | let data = try encoder.encode(input)
21 | let result = try decoder.decode(T.self, from: data)
22 |
23 | validation(result)
24 |
25 | } catch { XCTFail("Expected test not to throw but threw: \(error)", file: file, line: line) }
26 | }
27 |
28 | ///
29 | /// Generic func to test that decoding throws a `DecodingError.typeMismatch` when required to.
30 | ///
31 | internal func _testDecodeTypeMismatch(input: I, expected: (type: T.Type, errorType: Any.Type, codingPath: [CodingKey], description: String), file: StaticString = #file, line: UInt = #line) where I: Codable, T: Codable {
32 | do {
33 | let encoder = JSONEncoder()
34 | let decoder = ZippyJSONDecoder()
35 |
36 | let data = try encoder.encode(input)
37 |
38 | XCTAssertThrowsError(try decoder.decode(expected.type, from: data)) { (error) in
39 | }
40 | } catch { XCTFail("Expected test not to throw but threw: \(error)", file: file, line: line) }
41 | }
42 |
43 | ///
44 | /// Generic func to test that decoding throws a `DecodingError.valueNotFound` when required to.
45 | ///
46 | internal func _testDecodeValueNotFound(input: I, expected: (type: T.Type, errorType: E.Type, codingPath: [CodingKey], description: String), file: StaticString = #file, line: UInt = #line) where I: Codable, T: Codable {
47 | do {
48 | let encoder = JSONEncoder()
49 | let decoder = ZippyJSONDecoder()
50 |
51 | let data = try encoder.encode(input)
52 |
53 | XCTAssertThrowsError(try decoder.decode(expected.type, from: data)) { (error) in
54 | }
55 | } catch { XCTFail("Expected test not to throw but threw: \(error)", file: file, line: line) }
56 | }
57 |
58 | ///
59 | /// Generic func to test that decoding throws a `DecodingError.keyNotFound` when required to.
60 | ///
61 | internal func _testDecodeKeyNotFound(input: I, expected: (type: T.Type, key: CodingKey, codingPath: [CodingKey], description: String), file: StaticString = #file, line: UInt = #line) where I: Codable, T: Codable {
62 | do {
63 | let encoder = JSONEncoder()
64 | let decoder = ZippyJSONDecoder()
65 |
66 | let data = try encoder.encode(input)
67 |
68 | XCTAssertThrowsError(try decoder.decode(expected.type, from: data)) { (error) in
69 | }
70 | } catch { XCTFail("Expected test not to throw but threw: \(error)", file: file, line: line) }
71 | }
72 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/TestFixtures/BasicType.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// BasicType.swift
3 | ///
4 | /// Copyright 2017 Tony Stone
5 | ///
6 | /// Licensed under the Apache License, Version 2.0 (the "License");
7 | /// you may not use this file except in compliance with the License.
8 | /// You may obtain a copy of the License at
9 | ///
10 | /// http://www.apache.org/licenses/LICENSE-2.0
11 | ///
12 | /// Unless required by applicable law or agreed to in writing, software
13 | /// distributed under the License is distributed on an "AS IS" BASIS,
14 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | /// See the License for the specific language governing permissions and
16 | /// limitations under the License.
17 | ///
18 | /// Created by Tony Stone on 10/8/17.
19 | ///
20 | import Swift
21 |
22 | /// =================================== NOTICE ========================================
23 | /// Do NOT edit this file directly as it will be regenerated automatically when needed.
24 | ///
25 | /// Modify the *.swift.gyb file instead.
26 | /// ===================================================================================
27 |
28 | ///
29 | /// Type: `BasicClass`
30 | ///
31 | /// Test type which represents a basic class used by a developer using the library.
32 | ///
33 | public class BasicClass: Codable, EquatableType, CustomStringConvertible {
34 | public var boolVar: Bool
35 | public var intVar: Int
36 | public var doubleVar: Double
37 | public var stringVar: String
38 |
39 | public init(boolVar: Bool = false, intVar: Int = 0, doubleVar: Double = 0.00, stringVar: String = "") {
40 | self.boolVar = boolVar
41 | self.intVar = intVar
42 | self.doubleVar = doubleVar
43 | self.stringVar = stringVar
44 | }
45 |
46 | public func equals(_ other: T) -> Bool where T: BasicClass {
47 |
48 | return self.boolVar == other.boolVar &&
49 | self.intVar == other.intVar &&
50 | self.doubleVar == other.doubleVar &&
51 | self.stringVar == other.stringVar
52 | }
53 |
54 | public static func == (lhs: BasicClass, rhs: BasicClass) -> Bool {
55 | return lhs.equals(rhs)
56 | }
57 |
58 | public var description: String {
59 | return "\(String(describing: type(of: self)))(boolVar: \(self.boolVar), intVar: \(self.intVar), doubleVar: \(self.doubleVar), stringVar: \"\(self.stringVar)\")"
60 | }
61 | }
62 |
63 | ///
64 | /// Type: `BasicStruct`
65 | ///
66 | /// Test type which represents a basic struct used by a developer using the library.
67 | ///
68 | public struct BasicStruct: Codable, EquatableType, CustomStringConvertible {
69 | public var boolVar: Bool
70 | public var intVar: Int
71 | public var doubleVar: Double
72 | public var stringVar: String
73 |
74 | public init(boolVar: Bool = false, intVar: Int = 0, doubleVar: Double = 0.00, stringVar: String = "") {
75 | self.boolVar = boolVar
76 | self.intVar = intVar
77 | self.doubleVar = doubleVar
78 | self.stringVar = stringVar
79 | }
80 |
81 | public func equals(_ other: BasicStruct) -> Bool {
82 |
83 | return self.boolVar == other.boolVar &&
84 | self.intVar == other.intVar &&
85 | self.doubleVar == other.doubleVar &&
86 | self.stringVar == other.stringVar
87 | }
88 |
89 | public static func == (lhs: BasicStruct, rhs: BasicStruct) -> Bool {
90 | return lhs.equals(rhs)
91 | }
92 |
93 | public var description: String {
94 | return "\(String(describing: type(of: self)))(boolVar: \(self.boolVar), intVar: \(self.intVar), doubleVar: \(self.doubleVar), stringVar: \"\(self.stringVar)\")"
95 | }
96 | }
97 |
98 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/TestFixtures/CodableType.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// CodableType.swift
3 | ///
4 | /// Copyright 2017 Tony Stone
5 | ///
6 | /// Licensed under the Apache License, Version 2.0 (the "License");
7 | /// you may not use this file except in compliance with the License.
8 | /// You may obtain a copy of the License at
9 | ///
10 | /// http://www.apache.org/licenses/LICENSE-2.0
11 | ///
12 | /// Unless required by applicable law or agreed to in writing, software
13 | /// distributed under the License is distributed on an "AS IS" BASIS,
14 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | /// See the License for the specific language governing permissions and
16 | /// limitations under the License.
17 | ///
18 | /// Created by Tony Stone on 11/1/17.
19 | ///
20 | ///
21 | import Swift
22 |
23 | ///
24 | /// Special type created to test custom objects that are Codable.
25 | ///
26 | internal struct CodableType: Codable, Equatable {
27 | let value: Int
28 | init(_ value: Int) { self.value = value }
29 |
30 | enum CodingKeys: CodingKey { case value }
31 |
32 | public static func == (lhs: CodableType, rhs: CodableType) -> Bool {
33 | return lhs.value == rhs.value
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/TestFixtures/ComplexSubclassClass.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// ComplexSubclassType.swift
3 | ///
4 | /// Copyright 2017 Tony Stone
5 | ///
6 | /// Licensed under the Apache License, Version 2.0 (the "License");
7 | /// you may not use this file except in compliance with the License.
8 | /// You may obtain a copy of the License at
9 | ///
10 | /// http://www.apache.org/licenses/LICENSE-2.0
11 | ///
12 | /// Unless required by applicable law or agreed to in writing, software
13 | /// distributed under the License is distributed on an "AS IS" BASIS,
14 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | /// See the License for the specific language governing permissions and
16 | /// limitations under the License.
17 | ///
18 | /// Created by Tony Stone on 10/6/17.
19 | ///
20 | import Swift
21 |
22 | ///
23 | /// Type: `ComplexSubclassClass`
24 | ///
25 | /// Test class which represents a complex subclass class used by
26 | /// a developer using the library.
27 | ///
28 | public class ComplexSubclassClass: ComplexClass {
29 | public var subclassStringVar: String
30 |
31 | public init(subclassStringVar: String = "", boolVar: Bool = false, intVar: Int = 0, doubleVar: Double = 0.00, stringVar: String = "", optionalStringVar: String? = nil, objectVar: BasicClass = BasicClass(), objectArrayVar: [BasicClass] = []) {
32 | self.subclassStringVar = subclassStringVar
33 |
34 | super.init(boolVar: boolVar, intVar: intVar, doubleVar: doubleVar, stringVar: stringVar, optionalStringVar: optionalStringVar, objectVar: objectVar, objectArrayVar: objectArrayVar)
35 | }
36 |
37 | // MARK: - `Codable` conformance
38 |
39 | enum CodingKeys: CodingKey {
40 | case subclassStringVar
41 | }
42 |
43 | ///
44 | /// - Note: On subclasses, Codable init and encode methods are auto generated so must be created explicitly
45 | ///
46 | public required init(from decoder: Decoder) throws {
47 | let container = try decoder.container(keyedBy: CodingKeys.self)
48 |
49 | self.subclassStringVar = try container.decode(String.self, forKey: .subclassStringVar)
50 |
51 | try super.init(from: decoder)
52 | }
53 |
54 | public override func encode(to encoder: Encoder) throws {
55 | var container = encoder.container(keyedBy: CodingKeys.self)
56 |
57 | try container.encode(self.subclassStringVar, forKey: .subclassStringVar)
58 |
59 | try super.encode(to: encoder)
60 | }
61 |
62 | // MARK: - `Equatable` conformance for testing/debugging.
63 |
64 | public override func equals(_ other: T) -> Bool where T: ComplexClass {
65 |
66 | guard super.equals(other) else { return false }
67 | guard let sub = other as? ComplexSubclassClass else { return false }
68 | return self.subclassStringVar == sub.subclassStringVar
69 | }
70 |
71 | // MARK: - `CustomStringConvertible` conformance for testing/debugging.
72 |
73 | public override var description: String {
74 | return "\(String(describing: type(of: self)))(subclassStringVar: \"\(self.subclassStringVar)\", boolVar: \(self.boolVar), intVar: \(self.intVar), doubleVar: \(self.doubleVar), stringVar: \"\(self.stringVar)\", optionalStringVar: \"\(self.optionalStringVar ?? "nil")\", objectVar: \(self.objectVar), objectArrayVar: \(self.objectArrayVar))"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/TestFixtures/ComplexType.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// ComplexType.swift
3 | ///
4 | /// Copyright 2017 Tony Stone
5 | ///
6 | /// Licensed under the Apache License, Version 2.0 (the "License");
7 | /// you may not use this file except in compliance with the License.
8 | /// You may obtain a copy of the License at
9 | ///
10 | /// http://www.apache.org/licenses/LICENSE-2.0
11 | ///
12 | /// Unless required by applicable law or agreed to in writing, software
13 | /// distributed under the License is distributed on an "AS IS" BASIS,
14 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | /// See the License for the specific language governing permissions and
16 | /// limitations under the License.
17 | ///
18 | /// Created by Tony Stone on 10/8/17.
19 | ///
20 | import Swift
21 |
22 | /// =================================== NOTICE ========================================
23 | /// Do NOT edit this file directly as it will be regenerated automatically when needed.
24 | ///
25 | /// Modify the *.swift.gyb file instead.
26 | /// ===================================================================================
27 |
28 | ///
29 | /// Type: `ComplexClass`
30 | ///
31 | /// Test type which represents a complex class used by a developer using the library.
32 | ///
33 | public class ComplexClass: Codable, EquatableType, CustomStringConvertible {
34 | public var boolVar: Bool
35 | public var intVar: Int
36 | public var doubleVar: Double
37 | public var stringVar: String
38 | public var optionalStringVar: String?
39 | public var objectVar: BasicClass
40 | public var objectArrayVar: [BasicClass]
41 |
42 | public init(boolVar: Bool = false, intVar: Int = 0, doubleVar: Double = 0.00, stringVar: String = "", optionalStringVar: String? = nil, objectVar: BasicClass = BasicClass(), objectArrayVar: [BasicClass] = []) {
43 | self.boolVar = boolVar
44 | self.intVar = intVar
45 | self.doubleVar = doubleVar
46 | self.stringVar = stringVar
47 | self.optionalStringVar = optionalStringVar
48 | self.objectVar = objectVar
49 | self.objectArrayVar = objectArrayVar
50 | }
51 |
52 | public func equals(_ other: T) -> Bool where T: ComplexClass {
53 |
54 | return self.boolVar == other.boolVar &&
55 | self.intVar == other.intVar &&
56 | self.doubleVar == other.doubleVar &&
57 | self.stringVar == other.stringVar &&
58 | self.optionalStringVar == other.optionalStringVar &&
59 | self.objectVar == other.objectVar &&
60 | self.objectArrayVar.elementsEqual(other.objectArrayVar)
61 | }
62 |
63 | public static func == (lhs: ComplexClass, rhs: ComplexClass) -> Bool {
64 | return lhs.equals(rhs)
65 | }
66 |
67 | public var description: String {
68 | return "\(String(describing: type(of: self)))(boolVar: \(self.boolVar), intVar: \(self.intVar), doubleVar: \(self.doubleVar), stringVar: \"\(self.stringVar)\", optionalStringVar: \"\(self.optionalStringVar ?? "nil")\", objectVar: \(self.objectVar), objectArrayVar: \(self.objectArrayVar))"
69 | }
70 | }
71 |
72 | ///
73 | /// Type: `ComplexStruct`
74 | ///
75 | /// Test type which represents a complex struct used by a developer using the library.
76 | ///
77 | public struct ComplexStruct: Codable, EquatableType, CustomStringConvertible {
78 | public var boolVar: Bool
79 | public var intVar: Int
80 | public var doubleVar: Double
81 | public var stringVar: String
82 | public var optionalStringVar: String?
83 | public var objectVar: BasicStruct
84 | public var objectArrayVar: [BasicStruct]
85 |
86 | public init(boolVar: Bool = false, intVar: Int = 0, doubleVar: Double = 0.00, stringVar: String = "", optionalStringVar: String? = nil, objectVar: BasicStruct = BasicStruct(), objectArrayVar: [BasicStruct] = []) {
87 | self.boolVar = boolVar
88 | self.intVar = intVar
89 | self.doubleVar = doubleVar
90 | self.stringVar = stringVar
91 | self.optionalStringVar = optionalStringVar
92 | self.objectVar = objectVar
93 | self.objectArrayVar = objectArrayVar
94 | }
95 |
96 | public func equals(_ other: ComplexStruct) -> Bool {
97 |
98 | return self.boolVar == other.boolVar &&
99 | self.intVar == other.intVar &&
100 | self.doubleVar == other.doubleVar &&
101 | self.stringVar == other.stringVar &&
102 | self.optionalStringVar == other.optionalStringVar &&
103 | self.objectVar == other.objectVar &&
104 | self.objectArrayVar.elementsEqual(other.objectArrayVar)
105 | }
106 |
107 | public static func == (lhs: ComplexStruct, rhs: ComplexStruct) -> Bool {
108 | return lhs.equals(rhs)
109 | }
110 |
111 | public var description: String {
112 | return "\(String(describing: type(of: self)))(boolVar: \(self.boolVar), intVar: \(self.intVar), doubleVar: \(self.doubleVar), stringVar: \"\(self.stringVar)\", optionalStringVar: \"\(self.optionalStringVar ?? "nil")\", objectVar: \(self.objectVar), objectArrayVar: \(self.objectArrayVar))"
113 | }
114 | }
115 |
116 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/StickyEncodingTests/TestFixtures/EquatableType.swift:
--------------------------------------------------------------------------------
1 | ///
2 | /// EquatableType.swift
3 | /// Sticky
4 | ///
5 | /// Created by Tony Stone on 10/8/17.
6 | ///
7 | ///
8 | import Swift
9 |
10 | public protocol EquatableType: Equatable {
11 |
12 | func equals(_ other: Self) -> Bool
13 | }
14 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/Tests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import ZippyJSON
3 |
4 | class Tests: XCTestCase {
5 |
6 | override func setUp() {
7 | super.setUp()
8 | // Put setup code here. This method is called before the invocation of each test method in the class.
9 | }
10 |
11 | override func tearDown() {
12 | // Put teardown code here. This method is called after the invocation of each test method in the class.
13 | super.tearDown()
14 | }
15 |
16 | func testExample() {
17 | // This is an example of a functional test case.
18 | XCTAssert(true, "Pass")
19 | }
20 |
21 | func testPerformanceExample() {
22 | // This is an example of a performance test case.
23 | self.measure() {
24 | // Put the code you want to measure the time of here.
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(ZippyJSONDecoderTests.allTests),
7 | testCase(AppleJSONDecoderTests.allTests),
8 | ]
9 | }
10 | #endif
11 |
--------------------------------------------------------------------------------
/Tests/ZippyJSONTests/ZippyJSONDecoderTests.swift:
--------------------------------------------------------------------------------
1 | //Copyright (c) 2018 Michael Eisel. All rights reserved.
2 |
3 | import XCTest
4 | @testable import ZippyJSON
5 | import ZippyJSONCFamily
6 |
7 | struct TestCodingKey: CodingKey {
8 | var stringValue: String
9 |
10 | init?(stringValue: String) {
11 | self.stringValue = stringValue
12 | }
13 |
14 | var intValue: Int? {
15 | return nil
16 | }
17 |
18 | init?(intValue: Int) {
19 | return nil
20 | }
21 | }
22 |
23 | extension DecodingError: Equatable {
24 | public static func == (lhs: DecodingError, rhs: DecodingError) -> Bool {
25 | switch lhs {
26 | case .typeMismatch(let lType, let lContext):
27 | if case let DecodingError.typeMismatch(rType, rContext) = rhs {
28 | return /*lType == rType && */rContext == lContext
29 | }
30 | case .valueNotFound(let lType, let lContext):
31 | if case let DecodingError.valueNotFound(rType, rContext) = rhs {
32 | return /*lType == rType && */rContext == lContext
33 | }
34 | case .keyNotFound(let lKey, let lContext):
35 | if case let DecodingError.keyNotFound(rKey, rContext) = rhs {
36 | return keysEqual(lKey, rKey) && rContext == lContext
37 | }
38 | case .dataCorrupted(let lContext):
39 | if case let DecodingError.dataCorrupted(rContext) = rhs {
40 | return rContext == lContext
41 | }
42 | @unknown default:
43 | return false
44 | }
45 | return false
46 | }
47 | }
48 |
49 | extension JSONKey: Equatable {
50 | public static func == (lhs: JSONKey, rhs: JSONKey) -> Bool {
51 | return lhs.intValue == rhs.intValue && lhs.stringValue == rhs.stringValue
52 | }
53 | }
54 |
55 | func aKeysEqual(_ lhs: [CodingKey], _ rhs: [CodingKey]) -> Bool {
56 | guard lhs.count == rhs.count else { return false }
57 | return zip(lhs, rhs).map { (l, r) -> Bool in
58 | return l.stringValue == r.stringValue || (l.intValue != nil && l.intValue == r.intValue)
59 | }.reduce(true) { $0 && $1 }
60 | }
61 |
62 | func keysEqual(_ lhs: CodingKey, _ rhs: CodingKey) -> Bool {
63 | return lhs.stringValue == rhs.stringValue || (lhs.intValue != nil && lhs.intValue == rhs.intValue)
64 | }
65 |
66 | public func testRoundTrip(_ object: T) {
67 | let data: Data = try! JSONEncoder().encode(object)
68 | let json = String(data: data, encoding: .utf8)!
69 | testRoundTrip(of: T.self, json: json)
70 | }
71 |
72 | func threadTime() -> CFTimeInterval {
73 | var tp: timespec = timespec()
74 | if #available(macOS 10.12, *) {
75 | clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp)
76 | } else {
77 | abort()
78 | }
79 | return Double(tp.tv_sec) + Double(tp.tv_nsec) / 1e9;
80 | }
81 |
82 | func time(_ closure: () -> ()) -> CFTimeInterval {
83 | let start = threadTime()
84 | //let _: Int = autoreleasepool {
85 | closure()
86 | //return 0
87 | //}
88 | let end = threadTime()
89 | return end - start
90 | }
91 |
92 | func averageTime(_ closure: () -> ()) -> CFTimeInterval {
93 | let count = 10
94 | var times: [CFTimeInterval] = []
95 | for _ in 0..(appleDecoder: JSONDecoder, zippyDecoder: ZippyJSONDecoder, json: Data, type: T.Type) {
102 | let zippyTime = averageTime {
103 | let _ = try! zippyDecoder.decode(type, from: json)
104 | }
105 | let appleTime = averageTime {
106 | let _ = try! appleDecoder.decode(type, from: json)
107 | }
108 | XCTAssert(zippyTime < appleTime / 3)
109 | }
110 |
111 | public func testRoundTrip(of value: T.Type,
112 | json: String,
113 | outputFormatting: JSONEncoder.OutputFormatting = [],
114 | dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .deferredToDate,
115 | dateDecodingStrategy: ZippyJSONDecoder.DateDecodingStrategy = .deferredToDate,
116 | dataEncodingStrategy: JSONEncoder.DataEncodingStrategy = .base64,
117 | dataDecodingStrategy: ZippyJSONDecoder.DataDecodingStrategy = .base64,
118 | keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys,
119 | keyDecodingStrategy: ZippyJSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
120 | nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .throw,
121 | nonConformingFloatDecodingStrategy: ZippyJSONDecoder.NonConformingFloatDecodingStrategy = .throw,
122 | testPerformance: Bool = false) where T : Decodable, T : Equatable {
123 | do {
124 |
125 | let d = JSONDecoder()
126 | d.dateDecodingStrategy = ZippyJSONDecoder.convertDateDecodingStrategy(dateDecodingStrategy)
127 | d.dataDecodingStrategy = ZippyJSONDecoder.convertDataDecodingStrategy(dataDecodingStrategy)
128 | d.nonConformingFloatDecodingStrategy = ZippyJSONDecoder.convertNonConformingFloatDecodingStrategy(nonConformingFloatDecodingStrategy)
129 | d.keyDecodingStrategy = ZippyJSONDecoder.convertKeyDecodingStrategy(keyDecodingStrategy)
130 | let apple = try d.decode(T.self, from: json.data(using: .utf8)!)
131 |
132 | let decoder = ZippyJSONDecoder()
133 | decoder.dateDecodingStrategy = dateDecodingStrategy
134 | decoder.dataDecodingStrategy = dataDecodingStrategy
135 | decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy
136 | decoder.keyDecodingStrategy = keyDecodingStrategy
137 | let decoded = try decoder.decode(T.self, from: json.data(using: .utf8)!)
138 |
139 | XCTAssertEqual(decoded, apple)
140 | if decoded == apple && testPerformance {
141 | testPerf(appleDecoder: d, zippyDecoder: decoder, json: json.data(using: .utf8)!, type: T.self)
142 | }
143 | } catch {
144 | XCTFail("Failed to decode \(T.self) from JSON: \(error)")
145 | }
146 | }
147 |
148 | extension JSONKey {
149 | fileprivate static func create(_ values: [StringOrInt]) -> [JSONKey] {
150 | return values.map {
151 | if let i = $0 as? Int {
152 | return JSONKey(intValue: i)!
153 | }
154 | return JSONKey(stringValue: $0 as! String)!
155 | }
156 | }
157 | }
158 |
159 | protocol StringOrInt {
160 | }
161 |
162 | extension String: StringOrInt {}
163 |
164 | extension Int: StringOrInt {}
165 |
166 | extension CodingKey {
167 | static func == (lhs: Self, rhs: Self) -> Bool {
168 | return lhs.stringValue == rhs.stringValue && lhs.intValue == rhs.intValue
169 | }
170 | }
171 |
172 | func pathsEqual(_ lPath: [CodingKey], _ rPath: [CodingKey]) -> Bool {
173 | return lPath.count == rPath.count && zip(lPath, rPath).allSatisfy { (a, b) in
174 | keysEqual(a, b)
175 | }
176 | }
177 |
178 | extension DecodingError.Context: Equatable {
179 | public static func == (lhs: DecodingError.Context, rhs: DecodingError.Context) -> Bool {
180 | let lPath = lhs.codingPath//.drop { $0.stringValue == "" && $0.intValue == nil }
181 | let rPath = rhs.codingPath//.drop { $0.stringValue == "" && $0.intValue == nil }
182 | let pathsEqual = lPath.count == rPath.count && zip(lPath, rPath).allSatisfy { (a, b) in
183 | keysEqual(a, b)
184 | }
185 | return pathsEqual// && lhs.debugDescription == rhs.debugDescription
186 | }
187 | }
188 |
189 | class ZippyJSONTests: XCTestCase {
190 | let decoder = ZippyJSONDecoder()
191 | lazy var base64Data = {
192 | return dataFromFile("base64.json")
193 | }()
194 | lazy var twitterData = {
195 | dataFromFile("twitter.json")
196 | }()
197 | lazy var canadaData = {
198 | self.dataFromFile("canada.json")
199 | }()
200 |
201 | func dataFromFile(_ file: String) -> Data {
202 | #if SWIFT_PACKAGE
203 | let path = Bundle.module.path(forResource: file, ofType: "")!
204 | #else
205 | let path = Bundle(for: type(of: self)).path(forResource: file, ofType: "")!
206 | #endif
207 | let string = try! String(contentsOfFile: path)
208 | return string.data(using: .utf8)!
209 | }
210 |
211 | func testExceptionSafetyAroundObjectPool() {
212 | // https://github.com/michaeleisel/ZippyJSON/issues/20
213 | struct Aa: Equatable & Decodable {
214 | let value: String
215 | enum Keys: String, CodingKey {
216 | case value
217 | }
218 | init(from decoder: Decoder) throws {
219 | let outer = try decoder.container(keyedBy: Keys.self)
220 | try autoreleasepool {
221 | let _ = try decoder.container(keyedBy: JSONKey.self)
222 | }
223 | if let _ = try? outer.decode(Bb.self, forKey: .value) {
224 | XCTFail()
225 | value = ""
226 | } else if let _ = try? outer.decode(Bb.self, forKey: .value) {
227 | XCTFail()
228 | value = ""
229 | } else {
230 | value = try outer.decode(String.self, forKey: .value)
231 | }
232 | }
233 | }
234 |
235 | struct Bb: Equatable & Decodable {
236 | let placeholder: String
237 | init(from decoder: Decoder) throws {
238 | let _ = try decoder.container(keyedBy: JSONKey.self)
239 | placeholder = "bar"
240 | }
241 | }
242 | testRoundTrip(of: Aa.self, json: #"{"value": "foo"}"#)
243 | }
244 |
245 | func testData() {
246 | //let error = DecodingError.dataCorrupted(DecodingError.Context(codingPath: [JSONKey(index: 0)], debugDescription: "Encountered Data is not valid Base64."))
247 | _testFailure(of: [Data].self, json: #"["😊"]"#)
248 | }
249 |
250 | func testVeryNestedArray() {
251 | testRoundTrip(of: [[[[[Int]]]]].self, json: #"[[[[[2]]]]]"#)
252 | }
253 |
254 | func assertEqualsApple(data: Data, type: T.Type) {
255 | let testDecoder = ZippyJSONDecoder()
256 | let appleDecoder = JSONDecoder()
257 | let testObject = try! testDecoder.decode(type, from: data)
258 | let appleObject = try! appleDecoder.decode(type, from: data)
259 | XCTAssertEqual(appleObject, testObject)
260 | }
261 |
262 | func testNestedDecode() {
263 | struct Aa: Equatable & Codable {
264 | let a: [Int]
265 | let b: [Int]
266 | init(from decoder: Decoder) throws {
267 | var container = try decoder.unkeyedContainer()
268 | var nestedContainer = try container.nestedUnkeyedContainer()
269 | self.a = [try nestedContainer.decode(Int.self)]
270 | self.b = [try container.decode(Int.self)]
271 | }
272 | }
273 | testRoundTrip(of: Aa.self, json: #"[[2], 3]"#)
274 |
275 | struct Bb: Equatable & Codable {
276 | let a: Int
277 | init(from decoder: Decoder) throws {
278 | let container = try decoder.container(keyedBy: JSONKey.self)
279 | let nestedContainer = try container.nestedContainer(keyedBy: JSONKey.self, forKey: JSONKey(stringValue: "value")!)
280 | self.a = try nestedContainer.decode(Int.self, forKey: JSONKey(stringValue: "inner")!)
281 | }
282 | }
283 | testRoundTrip(of: Bb.self, json: #"{"value": {"inner": 4}}"#)
284 | }
285 |
286 | func testJSONKey() {
287 | XCTAssertEqual(JSONKey(intValue: 1), JSONKey(stringValue: "1", intValue: 1))
288 | }
289 |
290 | func testCodingPath() {
291 | struct Zz: Equatable & Codable {
292 | init(from decoder: Decoder) throws {
293 | let expected: [CodingKey] = [JSONKey(stringValue: "asdf")!, JSONKey(index: 0)]
294 | XCTAssert(aKeysEqual(decoder.codingPath, expected))
295 | }
296 | }
297 | struct ZzContainer: Equatable & Codable {
298 | let asdf: [Zz]
299 | }
300 | testRoundTrip(of: ZzContainer.self, json: #"{"asdf": [{}]}"#)
301 | struct Aa: Equatable & Codable {
302 | init(from decoder: Decoder) throws {
303 | let container = try decoder.container(keyedBy: JSONKey.self)
304 | XCTAssert(container.allKeys.count == 0)
305 | //XCTAssertEqual(container.codingPath.count, 0)
306 | }
307 | }
308 | testRoundTrip(of: Aa.self, json: "{}")
309 |
310 | struct Cc: Equatable & Codable {
311 | init(from decoder: Decoder) throws {
312 | for _ in 0..<3 {
313 | let container = try decoder.container(keyedBy: JSONKey.self)
314 | let inner = try container.nestedContainer(keyedBy: JSONKey.self, forKey: JSONKey(stringValue: "inner")!)
315 | XCTAssert(aKeysEqual(container.allKeys, [JSONKey(stringValue: "inner")!]))
316 | let path = [JSONKey(stringValue: "inner")!]
317 | let testPath = inner.codingPath
318 | XCTAssert(aKeysEqual(testPath, path))
319 | }
320 | }
321 | }
322 | //testRoundTrip(of: Cc.self, json: #"{"inner": {"a": 2}}"#)
323 |
324 | struct Bb: Equatable & Codable {
325 | init(from decoder: Decoder) throws {
326 | var container = try decoder.unkeyedContainer()
327 | let _ = try container.nestedUnkeyedContainer()
328 | }
329 | }
330 | //_testFailure(of: Bb.self, json: "[]")
331 | }
332 |
333 | func testMoreCodingPath() {
334 | struct Dd: Equatable & Codable {
335 | init(from decoder: Decoder) throws {
336 | let container = try decoder.container(keyedBy: JSONKey.self)
337 | let emptyDict = try container.nestedContainer(keyedBy: JSONKey.self, forKey: JSONKey(stringValue: "emptyDict")!)
338 | let emptyArray = try container.nestedUnkeyedContainer(forKey: JSONKey(stringValue: "emptyArray")!)
339 | XCTAssert(aKeysEqual(emptyDict.codingPath, JSONKey.create(["emptyDict"])))
340 | // XCTAssert(aKeysEqual(emptyArray.codingPath, JSONKey.create(["emptyArray"])))
341 | //XCTAssert(aKeysEqual(decoder.codingPath, []))
342 | }
343 | }
344 | // testRoundTrip(of: Dd.self, json: #"{"emptyDict": {}, "emptyArray": [], "dict": {"emptyNestedDict": {}, "emptyNestedArray": []}}"#)
345 | }
346 |
347 | func testArrayDecodeNil() {
348 | struct Aa: Equatable & Codable {
349 | let a: [Int?]
350 | init(from decoder: Decoder) throws {
351 | var container = try decoder.unkeyedContainer()
352 | let _ = try container.decodeNil()
353 | self.a = [try container.decode(Int.self)]
354 | }
355 | }
356 |
357 | testRoundTrip(of: Aa.self, json: #"[1, 2]"#)
358 | }
359 |
360 | struct Example: Equatable, Codable {
361 | let key: String
362 |
363 | init(from decoder: Decoder) throws {
364 | let container = try decoder.container(keyedBy: CodingKeys.self)
365 | self.key = (try? container.decode(String.self, forKey: .key)) ?? ""
366 | }
367 | }
368 |
369 | struct Example2: Equatable, Codable {
370 | let key: String
371 |
372 | init(from decoder: Decoder) throws {
373 | let container = try decoder.container(keyedBy: CodingKeys.self)
374 | if let double = try? container.decode(Double.self, forKey: .key) {
375 | self.key = "\(double)"
376 | } else {
377 | self.key = try container.decode(String.self, forKey: .key)
378 | }
379 | }
380 | }
381 |
382 | func testStuff() {
383 | struct Aa: Decodable, Equatable {
384 | let a: [String: String]
385 | }
386 | // testRoundTrip(of: Aa.self, json: #"{"a": {}}"#)
387 | _testFailure(of: Aa.self, json: #"{"a": 2}"#)
388 | }
389 |
390 | func testNilAdvance() {
391 | struct Aa: Codable & Equatable {
392 | let a: Int
393 | init(from decoder: Decoder) throws {
394 | var container = try decoder.unkeyedContainer()
395 | while try container.decodeNil() {
396 | }
397 | a = try container.decode(Int.self)
398 | }
399 | }
400 | testRoundTrip(of: Aa.self, json: #"[2]"#)
401 | testRoundTrip(of: Aa.self, json: #"[null, 2]"#)
402 | testRoundTrip(of: Aa.self, json: #"[null, null, 2]"#)
403 | _testFailure(of: Aa.self, json: #"[]"#)
404 | _testFailure(of: Aa.self, json: #"[null]"#)
405 | }
406 |
407 | func testURLError() {
408 | _testFailure(of: [URL].self, json: #"[""]"#)
409 | }
410 |
411 | func testOptionalInvalidValue() {
412 | testRoundTrip(of: Example.self, json: "{\"key\": 123}")
413 | testRoundTrip(of: Example2.self, json: "{\"key\": 123}")
414 | testRoundTrip(of: Example2.self, json: "{\"key\": \"123\"}")
415 | }
416 |
417 | func testRecursiveDecoding() {
418 | decoder.keyDecodingStrategy = .custom({ (keys) -> CodingKey in
419 | let recursiveDecoder = ZippyJSONDecoder()
420 | let data: Data = keys.last!.stringValue.data(using: .utf8)!
421 | return TestCodingKey(stringValue: try! recursiveDecoder.decode(String.self, from: data))!
422 | })
423 | }
424 |
425 | func dateError(_ msg: String) -> DecodingError {
426 | let path = [JSONKey(index: 0)]
427 | let context = DecodingError.Context(codingPath: path, debugDescription: msg)
428 | return DecodingError.dataCorrupted(context)
429 | }
430 |
431 | func testSuperDecoder() {
432 | struct Aa: Equatable, Codable {
433 | let a: Int
434 | init(from decoder: Decoder) throws {
435 | var container = try decoder.unkeyedContainer()
436 | let superDecoder = try container.superDecoder()
437 | let superContainer = try superDecoder.singleValueContainer()
438 | self.a = try superContainer.decode(Int.self)
439 | }
440 | }
441 |
442 | testRoundTrip(of: Aa.self, json: "[2]")
443 |
444 | struct Bb: Equatable, Codable {
445 | let a: Int
446 | init(from decoder: Decoder) throws {
447 | var container = try decoder.container(keyedBy: JSONKey.self)
448 | let superDecoder = try container.superDecoder()
449 | var superContainer = try superDecoder.unkeyedContainer()
450 | self.a = try superContainer.decode(Int.self)
451 | }
452 | }
453 |
454 | testRoundTrip(of: Bb.self, json: #"{"super": [2]}"#)
455 |
456 | struct Cc: Equatable, Codable {
457 | let a: Int
458 | init(from decoder: Decoder) throws {
459 | var container = try decoder.container(keyedBy: JSONKey.self)
460 | let superDecoder = try container.superDecoder(forKey: JSONKey(stringValue: "foo")!)
461 | var superContainer = try superDecoder.unkeyedContainer()
462 | self.a = try superContainer.decode(Int.self)
463 | }
464 | }
465 |
466 | testRoundTrip(of: Cc.self, json: #"{"foo": [2]}"#)
467 | }
468 |
469 | func testOther() {
470 | struct Aa: Codable & Equatable {
471 | let a: Int
472 | }
473 | _testFailure(of: Aa.self, json: #"{}"#)
474 | struct Bb: Codable & Equatable {
475 | let a: Int
476 | init(from decoder: Decoder) throws {
477 | let c1 = try decoder.container(keyedBy: JSONKey.self)
478 | let c2 = try c1.nestedContainer(keyedBy: JSONKey.self, forKey: JSONKey(stringValue: "a")!)
479 | let c3 = try c2.nestedContainer(keyedBy: JSONKey.self, forKey: JSONKey(stringValue: "b")!)
480 | let c4 = try c3.nestedContainer(keyedBy: JSONKey.self, forKey: JSONKey(stringValue: "c")!)
481 | a = try c4.decode(Int.self, forKey: JSONKey(stringValue: "d")!)
482 | }
483 | }
484 | testRoundTrip(of: Bb.self, json: #"{"a": {"b": {"c": {"d": 2}}}}"#)
485 | _testFailure(of: Bb.self, json: #"{"a": {"b": {"c": {"d": false}}}}"#, relaxedErrorCheck: true)
486 | struct Cc: Codable & Equatable {
487 | let a: Int
488 | init(from decoder: Decoder) throws {
489 | let c1 = try decoder.container(keyedBy: JSONKey.self)
490 | let c2 = try c1.nestedContainer(keyedBy: JSONKey.self, forKey: JSONKey(stringValue: "a")!)
491 | let c3 = try c2.nestedContainer(keyedBy: JSONKey.self, forKey: JSONKey(stringValue: "b")!)
492 | let c4 = try c3.nestedContainer(keyedBy: JSONKey.self, forKey: JSONKey(stringValue: "c")!)
493 | var c5 = try c4.nestedUnkeyedContainer(forKey: JSONKey(stringValue: "d")!)
494 | let c6 = try c5.nestedContainer(keyedBy: JSONKey.self)
495 | var c7 = try c6.nestedUnkeyedContainer(forKey: JSONKey(stringValue: "e")!)
496 | a = try c7.decode(Int.self)
497 | }
498 | }
499 | testRoundTrip(of: Cc.self, json: #"{"a": {"b": {"c": {"d": [{"e": [2]}]}}}}"#)
500 | _testFailure(of: Cc.self, json: #"{"a": {"b": {"c": {"d": [{"e": [false]}]}}}}"#, relaxedErrorCheck: true)
501 | /*let count: Int = Int(UInt32.max) + 1
502 | let d = Data(count: count)
503 | try! ZippyJSONDecoder().decode(Cc.self, from: d)*/
504 | }
505 |
506 | func testJSONKeyCleanupMemorySafe() {
507 | class Holder {
508 | var path: [CodingKey]? = nil
509 | init() {
510 | }
511 | }
512 | struct Aa: Codable & Equatable {
513 | let b: Bb
514 | }
515 | struct Bb: Codable & Equatable {
516 | let c: Int
517 | init(from decoder: Decoder) {
518 | let holder = (decoder.userInfo[CodingUserInfoKey(rawValue: "key")!]) as! Holder
519 | holder.path = decoder.codingPath
520 | c = 0
521 | }
522 | }
523 | let holder = Holder()
524 | autoreleasepool {
525 | let decoder = ZippyJSONDecoder()
526 | decoder.userInfo[CodingUserInfoKey(rawValue: "key")!] = holder
527 | let json = #"{"b": 1}"#.data(using: .utf8)!
528 | let _ = try! decoder.decode(Aa.self, from: json)
529 | }
530 | let _ = holder.path![0].stringValue
531 | }
532 |
533 | func testJSONKeyCleanupThreadSafe() {
534 | struct Aa: Codable & Equatable {
535 | let b: Bb
536 | }
537 | struct Bb: Codable & Equatable {
538 | let c: Cc
539 | }
540 | struct Cc: Codable & Equatable {
541 | enum Key: String, CodingKey{
542 | case one
543 | case two
544 | }
545 | let i: Int
546 | init(from decoder: Decoder) throws {
547 | let codingPath = decoder.codingPath
548 | DispatchQueue.global(qos: .userInteractive).async {
549 | let _ = codingPath[0].stringValue
550 | let keys = JSONKey.create(["b", "c"])
551 | if !pathsEqual(codingPath, keys) {
552 | abort()
553 | }
554 | }
555 | i = try decoder.singleValueContainer().decode(Int.self)
556 | let _ = codingPath[0].stringValue
557 | }
558 | }
559 | {
560 | let decoder = ZippyJSONDecoder()
561 | let json = #"{"b": {"c": 2}}"#.data(using: .utf8)!
562 | let _ = try! decoder.decode(Aa.self, from: json)
563 | }()
564 | usleep(50000)
565 | }
566 |
567 | func testInvalidDates() {
568 | let secondsError = dateError("Expected double/float but found Bool instead.")
569 | testRoundTrip(of: [Date].self, json: "[23908742398047]", dateDecodingStrategy: .secondsSince1970)
570 | _testFailure(of: [Date].self, json: "[false]", expectedError: secondsError, dateDecodingStrategy: .secondsSince1970)
571 |
572 | let millisError = dateError("Expected double/float but found Bool instead.")
573 | testRoundTrip(of: [Date].self, json: "[23908742398047]", dateDecodingStrategy: .millisecondsSince1970)
574 | _testFailure(of: [Date].self, json: "[false]", expectedError: millisError, dateDecodingStrategy: .millisecondsSince1970)
575 |
576 | let error = dateError("Expected date string to be ISO8601-formatted.")
577 | let typeError = DecodingError.typeMismatch(Any.self, DecodingError.Context(codingPath: JSONKey.create([0]), debugDescription: "Expected to decode PKc but found Number instead."))
578 |
579 | testRoundTrip(of: [Date].self, json: #"["2016-06-13T16:00:00+00:00"]"#, dateDecodingStrategy: .iso8601)
580 | _testFailure(of: [Date].self, json: "[23908742398047]", expectedError: typeError, dateDecodingStrategy: .iso8601)
581 | _testFailure(of: [Date].self, json: #"["23908742398047"]"#, relaxedErrorCheck: true, expectedError: error, dateDecodingStrategy: .iso8601)
582 |
583 | testRoundTrip(of: [Date].self, json: #"["1992"]"#, dateDecodingStrategy: .custom({ _ -> Date in
584 | return Date(timeIntervalSince1970: 0)
585 | }))
586 | _testFailure(of: [Date].self, json: "[23908742398047]", expectedError: error, dateDecodingStrategy: .custom({ _ -> Date in
587 | throw error
588 | }))
589 |
590 | let formatter = DateFormatter()
591 | let formatterError = dateError("Date string does not match format expected by formatter.")
592 | formatter.dateFormat = "yyyy"
593 | testRoundTrip(of: [Date].self, json: #"["1992"]"#, dateDecodingStrategy: .formatted(formatter))
594 | _testFailure(of: [Date].self, json: #"["March"]"#, expectedError: formatterError, dateDecodingStrategy: .formatted(formatter))
595 | _testFailure(of: [Date].self, json: "[23423423]", expectedError: typeError, dateDecodingStrategy: .formatted(formatter))
596 | }
597 |
598 | func testDefaultFloatStrings() {
599 | _testFailure(of: [Float].self, json: #"[""]"#)
600 | }
601 |
602 | func testLesserUsedFunctions() {
603 | struct NestedArrayMember: Codable, Equatable {
604 | let a: Int
605 | }
606 | struct Test: Codable, Equatable {
607 | let nestedArray: [NestedArrayMember]
608 | init(from decoder: Decoder) throws {
609 | let container = try decoder.container(keyedBy: JSONKey.self)
610 | var unkeyedContainer = try container.nestedUnkeyedContainer(forKey: JSONKey(stringValue: "array")!)
611 | let nestedArrayMember = try unkeyedContainer.decode(NestedArrayMember.self)
612 | nestedArray = [nestedArrayMember]
613 | }
614 | }
615 |
616 | testRoundTrip(of: Test.self, json: #"{"array": [{"a": 3}]}"#)
617 | }
618 |
619 | func _testFailure(of value: T.Type,
620 | json: String,
621 | relaxedErrorCheck: Bool = false,
622 | expectedError: Error? = nil,
623 | outputFormatting: JSONEncoder.OutputFormatting = [],
624 | dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .deferredToDate,
625 | dateDecodingStrategy: ZippyJSONDecoder.DateDecodingStrategy = .deferredToDate,
626 | dataEncodingStrategy: JSONEncoder.DataEncodingStrategy = .base64,
627 | dataDecodingStrategy: ZippyJSONDecoder.DataDecodingStrategy = .base64,
628 | keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys,
629 | keyDecodingStrategy: ZippyJSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
630 | nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .throw,
631 | nonConformingFloatDecodingStrategy: ZippyJSONDecoder.NonConformingFloatDecodingStrategy = .throw) where T : Decodable, T : Equatable {
632 | let decoder = ZippyJSONDecoder()
633 | decoder.dateDecodingStrategy = dateDecodingStrategy
634 | decoder.dataDecodingStrategy = dataDecodingStrategy
635 | decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy
636 | decoder.keyDecodingStrategy = keyDecodingStrategy
637 | var zippyErrorMaybe: DecodingError?
638 | do {
639 | let _ = try decoder.decode(T.self, from: json.data(using: .utf8)!)
640 | XCTFail()
641 | } catch {
642 | zippyErrorMaybe = error as? DecodingError
643 | }
644 | guard let zippyError = zippyErrorMaybe else {
645 | XCTFail()
646 | return
647 | }
648 | do {
649 | let d = JSONDecoder()
650 | d.dateDecodingStrategy = ZippyJSONDecoder.convertDateDecodingStrategy(dateDecodingStrategy)
651 | d.dataDecodingStrategy = ZippyJSONDecoder.convertDataDecodingStrategy(dataDecodingStrategy)
652 | d.nonConformingFloatDecodingStrategy = ZippyJSONDecoder.convertNonConformingFloatDecodingStrategy(nonConformingFloatDecodingStrategy)
653 | d.keyDecodingStrategy = ZippyJSONDecoder.convertKeyDecodingStrategy(keyDecodingStrategy)
654 | let _ = try d.decode(T.self, from: json.data(using: .utf8)!)
655 | } catch {
656 | guard let appleError = error as? DecodingError else {
657 | XCTFail()
658 | return
659 | }
660 | if !relaxedErrorCheck {
661 | XCTAssertEqual(appleError, zippyError)
662 | }
663 | return
664 | }
665 | XCTFail()
666 | }
667 |
668 | func testDictionaryStuff() {
669 | struct Test: Codable, Equatable {
670 | let a: Bool
671 | }
672 | //testRoundTrip(of: Test.self, json: #"{"a": true}"#)
673 | //testRoundTrip(of: TopLevelWrapper.self, json: #"{"value": {"a": true}}"#)
674 | //_testFailure(of: Test.self, json: #"{"b": true}"#, expectedError: DecodingError.keyNotFound(JSONKey(stringValue: "a")!, DecodingError.Context(codingPath: [], debugDescription: "No value associated with a.")))
675 | //_testFailure(of: Test.self, json: #"{}"#, expectedError: DecodingError.keyNotFound(JSONKey(stringValue: "a")!, DecodingError.Context(codingPath: [], debugDescription: "No value associated with a.")))
676 | //_testFailure(of: TopLevelWrapper.self, json: #"{"value": {}}"#, expectedError: DecodingError.keyNotFound(JSONKey(stringValue: "a")!, DecodingError.Context(codingPath: [], debugDescription: "No value associated with a.")))
677 | _testFailure(of: TopLevelWrapper.self, json: #"{"value": {"b": true}}"#, expectedError: nil) //DecodingError.keyNotFound(JSONKey(stringValue: "a")!, DecodingError.Context(codingPath: [JSONKey(stringValue: "value")!], debugDescription: "No value associated with a.")))
678 | }
679 |
680 | func testNestedDecoding() {
681 | struct Test: Codable, Equatable {
682 | init(from decoder: Decoder) throws {
683 | if (try! ZippyJSONDecoder().decode([Int].self, from: "[1]".data(using: .utf8)!) != [1]) {
684 | abort()
685 | }
686 | }
687 | }
688 | testRoundTrip(of: Test.self, json: "{}")
689 | }
690 |
691 | func testEmptyString() {
692 | _testFailure(of: [Int].self, json: "", expectedError: DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON. Error: Empty")))
693 | }
694 |
695 | func testArrayStuff() {
696 | struct Test: Codable, Equatable {
697 | let a: Bool
698 | let b: Bool
699 |
700 | init(a: Bool, b: Bool) {
701 | self.a = a
702 | self.b = b
703 | }
704 |
705 | init(from decoder: Decoder) throws {
706 | var container = try decoder.unkeyedContainer()
707 | a = try container.decode(Bool.self)
708 | b = try container.decode(Bool.self)
709 | }
710 | }
711 |
712 | // Goes past the end
713 | _testFailure(of: Test.self, json: "[true]", expectedError: DecodingError.valueNotFound(Any.self, DecodingError.Context(codingPath: [JSONKey(index: 0)], debugDescription: "Cannot get next value -- unkeyed container is at end.")))
714 | _testFailure(of: Test.self, json: "[]", expectedError: DecodingError.valueNotFound(Any.self, DecodingError.Context(codingPath: [], debugDescription: "Cannot get next value -- unkeyed container is at end.")))
715 | _testFailure(of: TopLevelWrapper.self, json: #"{"value": [true]}"#, expectedError: DecodingError.valueNotFound(Any.self, DecodingError.Context(codingPath: [JSONKey(stringValue: "value")!, JSONKey(index: 0)], debugDescription: "Cannot get next value -- unkeyed container is at end.")))
716 | _testFailure(of: TopLevelWrapper.self, json: #"{"value": []}"#, expectedError: DecodingError.valueNotFound(Any.self, DecodingError.Context(codingPath: [JSONKey(stringValue: "value")!], debugDescription: "Cannot get next value -- unkeyed container is at end.")))
717 | // Left over
718 | testRoundTrip(of: Test.self, json: "[false, true, false]")
719 | // Normals
720 | testRoundTrip(of: Test.self, json: "[false, true]")
721 | testRoundTrip(of: [[[[Int]]]].self, json: "[[[[]]]]")
722 | testRoundTrip(of: [[[[Int]]]].self, json: "[[[[2, 3]]]]")
723 | testRoundTrip(of: [Bool].self, json: "[false, true]")
724 | _testFailure(of: [Int].self, json: #"{"a": 1}"#, expectedError: DecodingError.typeMismatch([Any].self, DecodingError.Context(codingPath: [], debugDescription: "Tried to unbox array, but it wasn\'t an array")))
725 | }
726 |
727 | func testInvalidJSON() {
728 | _testFailure(of: [Int].self, json: "{a: 255}", expectedError: DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON. Error: Something went wrong while writing to the tape")))
729 | _testFailure(of: [Int].self, json: #"{"key: "yes"}"#, expectedError: DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON. Error: Something went wrong while writing to the tape")))
730 | }
731 |
732 | func testRawValuePassedAsJson() {
733 | testRoundTrip(of: Bool.self, json: "false")
734 | testRoundTrip(of: Bool.self, json: #"true"#)
735 | testRoundTrip(of: Int.self, json: "82")
736 | _testFailure(of: Int.self, json: "82.1")
737 | testRoundTrip(of: Double.self, json: "82.1")
738 | testRoundTrip(of: String.self, json: #""test""#)
739 | _testFailure(of: Int.self, json: #"undefined"#)
740 | }
741 |
742 | func testMultipleRefsToSameDecoder() {
743 | struct Aa: Codable, Equatable {
744 | let value: Int
745 | init(from decoder: Decoder) throws {
746 | var c1 = try decoder.unkeyedContainer()
747 | var c2 = try decoder.unkeyedContainer()
748 | // Get c1 to skip ahead
749 | let _ = try c1.decode(Int.self)
750 | value = try c2.decode(Int.self)
751 | }
752 | }
753 | testRoundTrip(of: Aa.self, json: "[20]")
754 | }
755 |
756 | func testInts() {
757 | testRoundTrip(of: Int64.self, json: "\(Int64.max)")
758 | testRoundTrip(of: UInt64.self, json: "\(UInt64.max)")
759 | _testFailure(of: Int64.self, json: "\(UInt64.max)")
760 | testRoundTrip(of: Double.self, json: "\(Int64.max)")
761 | testRoundTrip(of: Double.self, json: "\(UInt64.max)")
762 | testRoundTrip(of: UInt64.self, json: "\(UInt64.max)")
763 | testRoundTrip(of: [Int8].self, json: "[127]")
764 | testRoundTrip(of: [UInt8].self, json: "[255]")
765 | _testFailure(of: [UInt8].self, json: "[256]", relaxedErrorCheck: true, expectedError: DecodingError.dataCorrupted(DecodingError.Context(codingPath: [JSONKey(index: 0)], debugDescription: "Parsed JSON number 256 does not fit.")))
766 | _testFailure(of: [UInt8].self, json: "[-1]", relaxedErrorCheck: true, expectedError: DecodingError.dataCorrupted(DecodingError.Context(codingPath: [JSONKey(index: 0)], debugDescription: "Parsed JSON number -1 does not fit.")))
767 | testRoundTrip(of: [Int64].self, json: "[\(Int64.max)]")
768 | testRoundTrip(of: [Int64].self, json: "[\(Int64.min)]")
769 | testRoundTrip(of: [UInt64].self, json: "[\(UInt64.max)]")
770 | }
771 |
772 | func testDifferentTypes() {
773 | struct Test: Codable, Equatable {
774 | let i8: Int8
775 | let i16: Int16
776 | let i32: Int32
777 | let i64: Int64
778 | let u8: UInt8
779 | let u16: UInt16
780 | let u32: UInt32
781 | let u64: UInt64
782 | let u: UInt64
783 | let i: Int
784 | }
785 | let expected = Test(i8: 1, i16: 2, i32: 3, i64: 4, u8: 5, u16: 6, u32: 7, u64: 8, u: 9, i: 10)
786 | testRoundTrip(of: Test.self, json: #"{"u8": 1, "u16": 2, "u32": 3, "u64": 4, "i8": 5, "i16": 6, "i32": 7, "i64": 8, "u": 9, "i": 10}"#)
787 | }
788 |
789 | func testAllKeys() {
790 | struct Test: Codable {
791 | let keys: [String]
792 | init(from decoder: Decoder) throws {
793 | let container = try! decoder.container(keyedBy: JSONKey.self)
794 | keys = container.allKeys.map { $0.stringValue }
795 | }
796 | }
797 | let test = try! ZippyJSONDecoder().decode(Test.self, from: #"{"a": 1, "b": 2}"#.data(using: .utf8)!)
798 | XCTAssertEqual(test.keys, ["a", "b"])
799 | }
800 |
801 | func testDoubleParsing() {
802 | testRoundTrip(of: [Double].self, json: "[0.0]")
803 | testRoundTrip(of: [Double].self, json: "[0.0000]")
804 | testRoundTrip(of: [Double].self, json: "[-0.0]")
805 | testRoundTrip(of: [Double].self, json: "[1.0]")
806 | testRoundTrip(of: [Double].self, json: "[1.11111]")
807 | testRoundTrip(of: [Double].self, json: "[1.11211e-2]")
808 | testRoundTrip(of: [Double].self, json: "[1.11211e200]")
809 | }
810 |
811 | // Run with tsan
812 | func testConcurrentUsage() {
813 | let d = ZippyJSONDecoder()
814 | let testResult = try! d.decode(Twitter.self, from: twitterData)
815 | var value: Int32 = 0
816 | for _ in 0..<100 {
817 | DispatchQueue.global(qos: .userInteractive).async {
818 | assert(testResult == (try! d.decode(Twitter.self, from: self.twitterData)))
819 | OSAtomicIncrement32(&value)
820 | }
821 | }
822 | while value < 100 {
823 | usleep(UInt32(1e5))
824 | }
825 | }
826 |
827 | func testCodingKeys() {
828 | struct Test: Codable, Equatable {
829 | let a: Int
830 | let c: Int
831 |
832 | enum CodingKeys: String, CodingKey {
833 | case a = "b"
834 | case c
835 | }
836 | }
837 |
838 | testRoundTrip(of: Test.self, json: #"{"b": 1, "c": 2}"#)
839 | }
840 |
841 | func testSuppressWarnings() {
842 | struct Aa: Decodable {
843 | init(from decoder: Decoder) throws {
844 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: ""))
845 | }
846 | }
847 | XCTAssertThrowsError(try ZippyJSONDecoder().decode(Aa.self, from: "{5345893478957345903475890734}".data(using: .utf8)!))
848 | testRoundTrip([UInt64.max])
849 | ZippyJSONDecoder.zjd_suppressWarnings = true
850 | testRoundTrip([UInt64.max])
851 | ZippyJSONDecoder.zjd_suppressWarnings = false
852 | }
853 |
854 | func testDecimal() {
855 | let decimals: [Decimal] = ["1.2", "1", "0.0000000000000000000000000000001", "-1", "745612491641.4614612344632"].map { (numberString: String) -> Decimal in
856 | return Decimal(string: numberString)!
857 | }
858 | testRoundTrip(decimals)
859 | // NSDecimalNumber doesn't conform to Decodable
860 | //let nsDecimals: [NSDecimalNumber] = [1.2, 1]
861 | //testRoundTrip(nsDecimals)
862 |
863 | /*struct Aa: Equatable & Codable {
864 | init(from decoder: Decoder) throws {
865 | //let value = (decoder as! __JSONDecoder)
866 | var outLength: Int32 = 0
867 | let string = "{}"
868 | string.withCString { (cString) -> Void in
869 | let context = JNTCreateContext(cString, UInt32(string.count), "".utf8CString, "".utf8CString, "".utf8CString)
870 | let value = JNTDocumentFromJSON(context, UnsafeRawPointer(cString), string.count, false, nil, true)
871 | JNTDocumentDecode__DecimalString(value, &outLength)
872 | }
873 | }
874 | }*/
875 |
876 | //_testFailure(of: Aa.self, json: "{}", expectedError: DecodingError.dataCorrupted(DecodingError.Context(codingPath: [JSONKey(index: 0)], debugDescription: "Invalid Decimal")))
877 |
878 | _testFailure(of: [Decimal].self, json: "[true]", expectedError: DecodingError.dataCorrupted(DecodingError.Context(codingPath: [JSONKey(index: 0)], debugDescription: "Invalid Decimal")))
879 | }
880 |
881 | func testNull() {
882 | struct Test: Codable, Equatable {
883 | let a: Int?
884 | }
885 | testRoundTrip(of: Test.self, json: #"{"a": null}"#)
886 | }
887 |
888 | func run(_ filename: String, _ type: T.Type, keyDecoding: ZippyJSONDecoder.KeyDecodingStrategy = .useDefaultKeys, dateDecodingStrategy: ZippyJSONDecoder.DateDecodingStrategy = .deferredToDate) {
889 | let json = dataFromFile(filename + ".json")
890 | testRoundTrip(of: type, json: String(data: json, encoding: .utf8)!,
891 | dateDecodingStrategy: dateDecodingStrategy, testPerformance: false)
892 | }
893 |
894 | func testArrayTypes() {
895 | struct Test: Codable, Equatable {
896 | init(from decoder: Decoder) throws {
897 | var c = try! decoder.unkeyedContainer()
898 | a = try! c.decode(Int8.self)
899 | b = try! c.decode(Int16.self)
900 | cc = try! c.decode(Int32.self)
901 | d = try! c.decode(Int64.self)
902 | e = try! c.decode(Int.self)
903 | f = try! c.decode(UInt8.self)
904 | g = try! c.decode(UInt16.self)
905 | h = try! c.decode(UInt32.self)
906 | i = try! c.decode(UInt64.self)
907 | j = try! c.decode(UInt.self)
908 | k = try! c.decode(Float.self)
909 | l = try! c.decode(Double.self)
910 | }
911 | let a: Int8
912 | let b: Int16
913 | let cc: Int32
914 | let d: Int64
915 | let e: Int
916 | let f: UInt8
917 | let g: UInt16
918 | let h: UInt32
919 | let i: UInt64
920 | let j: UInt
921 | let k: Float
922 | let l: Double
923 | }
924 | testRoundTrip(of: Test.self, json: "[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]")
925 | }
926 |
927 | func testRealJsons() {
928 | run("apache_builds", ApacheBuilds.self)
929 | run("random", random.self)
930 | run("mesh", mesh.self)
931 | run("canada", canada.self)
932 | run("github_events", ghEvents.self, dateDecodingStrategy: .iso8601)
933 | run("twitter", Twitter.self, keyDecoding: .convertFromSnakeCase)
934 | run("twitterescaped", Twitter.self)
935 | }
936 | }
937 |
--------------------------------------------------------------------------------
/ZippyJSON.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'ZippyJSON'
3 | s.version = '1.2.15'
4 | s.summary = 'A ~4x faster, drop-in replacement for JSONDecoder'
5 |
6 | s.description = <<-DESC
7 | ZippyJSON is a very fast library for converting JSON into Swift objects. It is feature-complete, so just use s/JSONDecoder/ZippyJSONDecoder to incorporate it into your app.
8 | DESC
9 |
10 | s.homepage = 'https://github.com/michaeleisel/ZippyJSON'
11 | s.license = { :type => 'MIT', :file => 'LICENSE' }
12 | s.author = { 'michaeleisel' => 'michael.eisel@gmail.com' }
13 | s.source = { :git => 'https://github.com/michaeleisel/ZippyJSON.git', :tag => s.version.to_s }
14 |
15 | s.ios.deployment_target = '11.0'
16 | s.tvos.deployment_target = '12.0'
17 | s.osx.deployment_target = '10.13'
18 |
19 | s.source_files = 'Sources/**/*.{h,hh,mm,m,c,cpp,swift}'
20 | s.dependency 'ZippyJSONCFamily', '1.2.9'
21 | s.dependency 'JJLISO8601DateFormatter', '0.1.8'
22 | s.swift_version = '5.0'
23 |
24 | s.test_spec 'Tests' do |test_spec|
25 | # test_spec.requires_app_host = true
26 | test_spec.source_files = 'Tests/ZippyJSONTests/**/*.{swift,h,m}'
27 | test_spec.resources = 'Tests/ZippyJSONTests/**/*.json'
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/misc/snakify.rb:
--------------------------------------------------------------------------------
1 | class String
2 | def underscore
3 | self.gsub(/::/, '/').
4 | gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
5 | gsub(/([a-z\d])([A-Z])/,'\1_\2').
6 | tr("-", "_").
7 | downcase
8 | end
9 | end
10 |
11 | r = IO.read("TwitterUser.swift").split("\n").map do |line|
12 | next line unless line.match(/^\s*let /)
13 | # bits = line.split("_")
14 | # [bits[0]] + bits.drop(1).map { |b| b.capitalize }
15 | idx = line.index(":")
16 | line[0...idx].underscore + line[idx..-1]
17 | end.to_a
18 | puts r
19 |
--------------------------------------------------------------------------------
/misc/template.swift:
--------------------------------------------------------------------------------
1 | // UnkeyedBegin
2 | <% types.each do |type| %>
3 | <%= inline %>public mutating func decode(_ type: <%= type %>.Type) throws -> <%= type %> {
4 | currentValue = try valueFromIterator()
5 | let decoded = try decoder.unbox(currentValue, as: <%= type %>.self, key: IndexKey(index: currentIndex))
6 | advanceArray()
7 | return decoded
8 | }
9 |
10 | <% end %>
11 | // UnboxBegin
12 | <% types.each do |type| %>
13 | <%= inline %>fileprivate func unbox(_ value: Value, as type: <%= type %>.Type, key: CodingKey?) throws -> <%= type %> {
14 | let result = JNTDocumentDecode__<%= c_type(type) %>(value)
15 | try throwErrorIfNecessary(value, decoder: self, key: key)
16 | return <%= convert(type) %>
17 | }
18 |
19 | <% end %>
20 | // SingleValueBegin
21 | <% types.each do |type| %>
22 | public func decode(_ type: <%= type %>.Type) throws -> <%= type %> {
23 | return try unbox(containers.topContainer, as: <%= type %>.self, key: nil)
24 | }
25 |
26 | <% end %>
27 | // KeyedBegin
28 | <% (types + ["T"]).each do |type| %>
29 | <%= inline %>fileprivate func decode<%= type == "T" ? "" : "" %>(_ type: <%= type %>.Type, forKey key: K) <%= throws(type) %>-> <%= type %> {
30 | let subValue: Value = try key.stringValue.withCString(fetchValue)
31 | return <%= try(type) %>decoder.unbox(subValue, as: <%= type %>.self, key: key)
32 | }
33 |
34 | <% end %>
35 |
--------------------------------------------------------------------------------
/misc/template_generation.rb:
--------------------------------------------------------------------------------
1 | require 'erb'
2 | # require 'pry-byebug'
3 |
4 | $number_types = (["UInt", "Int"].product([8, 16, 32, 64].map(&:to_s))).map { |a, b| a + b }
5 | $other_types = ["Bool", "String", "Double", "Float", "Int", "UInt"]
6 | $extended_types = ["Date", "Data", "Decimal"]
7 |
8 | inline = "@inline(__always) "
9 |
10 | def additional_args(type)
11 | args = {
12 | "Date" => "dateDecodingStrategy",
13 | "Data" => "dataDecodingStrategy",
14 | }[type]
15 | args ? "" : ", #{args}"
16 | end
17 |
18 | def c_type(type)
19 | if $number_types.include?(type)
20 | type.downcase + "_t"
21 | else
22 | type
23 | end
24 | end
25 |
26 | def convert(type)
27 | conversion = {
28 | "String" => "String(utf8String: result!)!"
29 | }[type]
30 | conversion || "result"
31 | end
32 |
33 | def throws(type)
34 | "throws " # type == "T" ? "throws " : ""
35 | end
36 |
37 | def try(type)
38 | "try " # type == "T" ? "try " : ""
39 | end
40 |
41 | Dir.chdir(__dir__)
42 |
43 | types = $number_types + $other_types
44 | all_types = types + $extended_types
45 | template = ERB.new(IO.read("template.swift"), 0, "<>").result
46 | name_to_template = template.split("\n").chunk_while { |a, b| a.start_with?("//") == b.start_with?("//") }.each_slice(2).to_h
47 | # binding.pry
48 | def is_delimiter(line)
49 | line.match(/^\s*\/\/.*(Begin|End)$/)
50 | end
51 | code_file = "../Sources/ZippyJSON/ZippyJSONDecoder.swift"
52 | chunks = IO.read(code_file).split("\n").chunk_while do |a, b|
53 | is_delimiter(a) == is_delimiter(b)
54 | end
55 |
56 | product = (chunks.first + chunks.drop(1).each_slice(2).flat_map do |name_a, lines|
57 | # next lines unless is_delimiter(line) && line.end_with?("Begin")
58 | # binding.pry
59 | name = name_a.first
60 | next name_a + lines unless name.end_with?("Begin")
61 | # binding.pry
62 | [name] + name_to_template[[name.strip]]
63 | end.flatten).join("\n")
64 | # binding.pry
65 | `cp #{code_file} /tmp/backup.swift`
66 | IO.write(code_file, product)
67 |
--------------------------------------------------------------------------------