├── .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 | ![Coverage: 96%](https://img.shields.io/static/v1?label=coverage&message=96%&color=brightgreen) 4 | [![Cocoapods compatible](https://img.shields.io/badge/Cocoapods-compatible-4BC51D.svg?style=flat)](https://cocoapods.com) 5 | [![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible%20%28iOS%29-brightgreen)](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 | --------------------------------------------------------------------------------