├── .circleci └── config.yml ├── .codebeatignore ├── .gitattributes ├── .gitignore ├── Cartfile ├── Cartfile.resolved ├── LICENSE ├── Package.swift ├── Package@swift-5.0.swift ├── Package@swift-5.1.swift ├── Package@swift-5.2.swift ├── README.md ├── Serpent.podspec ├── Serpent ├── Example │ ├── SerpentExample.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── WorkspaceSettings.xcsettings │ └── SerpentExample │ │ ├── Classes │ │ ├── AppDelegate.swift │ │ ├── Managers │ │ │ └── ConnectionManager.swift │ │ ├── Models │ │ │ ├── Identification.swift │ │ │ ├── Location.swift │ │ │ ├── LoginInfo.swift │ │ │ ├── NameInfo.swift │ │ │ ├── ProfilePicture.swift │ │ │ └── User.swift │ │ └── User Interface │ │ │ ├── Cells │ │ │ ├── UserListCell.swift │ │ │ └── UserListCell.xib │ │ │ ├── HomeVC.swift │ │ │ └── UserDetailsVC.swift │ │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-29@2x.png │ │ │ ├── Icon-29@3x.png │ │ │ ├── Icon-40@2x.png │ │ │ ├── Icon-40@3x.png │ │ │ ├── Icon-60@2x.png │ │ │ └── Icon-60@3x.png │ │ │ ├── Contents.json │ │ │ ├── Logo.imageset │ │ │ ├── Contents.json │ │ │ ├── Logo.png │ │ │ ├── Logo@2x.png │ │ │ └── Logo@3x.png │ │ │ └── LogoText.imageset │ │ │ ├── Contents.json │ │ │ ├── LogoText.png │ │ │ ├── LogoText@2x.png │ │ │ └── LogoText@3x.png │ │ ├── Storyboards │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ └── Supporting Files │ │ └── Info.plist ├── Serpent.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── Serpent OSX.xcscheme │ │ ├── Serpent iOS.xcscheme │ │ ├── Serpent tvOS.xcscheme │ │ └── Serpent watchOS.xcscheme ├── Serpent │ ├── Classes │ │ ├── Extensions │ │ │ ├── AlamofireExtension.swift │ │ │ ├── CashierExtension.swift │ │ │ └── Extensions.swift │ │ ├── Operators │ │ │ └── Operator.swift │ │ ├── Other │ │ │ └── BridgingBox.swift │ │ └── Serpent.swift │ └── Supporting Files │ │ ├── Info.plist │ │ └── Serpent.h └── SerpentTests │ ├── Info.plist │ ├── Models │ ├── CustomOperatorsTest.json │ ├── CustomOperatorsTestModel.swift │ ├── DecodableModel.swift │ ├── DecodableTest.json │ ├── EnumsTest.json │ ├── EnumsTestModel.swift │ ├── HexInitializableTest.json │ ├── HexInitializableTestModel.swift │ ├── NetworkTestModel.swift │ ├── NilTestModel.swift │ ├── PerformanceSmallTest.json │ ├── PerformanceTest.json │ ├── PerformanceTestModel.swift │ ├── PerformanceTestSmallModel.swift │ ├── PrimitivesTest.json │ ├── PrimitivesTestModel.swift │ ├── SerializableEntityTest.json │ ├── SerializableEntityTestModel.swift │ ├── SimpleModel.json │ ├── SimpleModel.swift │ ├── StringInitializableTest.json │ └── StringInitializableTestModel.swift │ ├── TestEndpoint │ ├── ArrayTest.json │ ├── ArrayTestOneObject.json │ ├── BadArrayTest.json │ ├── Empty.json │ ├── NestedArrayTest.json │ ├── NetworkModel.json │ └── NetworkModelBad.json │ └── Tests │ ├── AlamofireExtensionTests.swift │ ├── BridgingBoxTests.swift │ ├── CashierExtensionTests.swift │ ├── CustomOperatorsTests.swift │ ├── DecodableTests.swift │ ├── HexInitalizableTests.swift │ ├── SerializableEntityTests.swift │ ├── SerializableEnumsTests.swift │ ├── SerializableNilEntitiesTests.swift │ ├── SerializablePerformanceTest.swift │ ├── SerializablePrimitivesTests.swift │ └── StringInitializableTests.swift ├── Serpent_icon.png ├── Sources └── Serpent │ ├── BridgingBox.swift │ ├── Extensions.swift │ ├── Operator.swift │ └── Serpent.swift └── codecov.yml /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # iOS CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/ios-migrating-from-1-2/ for more details 4 | # 5 | 6 | ### Serpent config file ### 7 | version: 2 8 | jobs: 9 | build: 10 | 11 | # Specify the Xcode version to use 12 | macos: 13 | xcode: "9.4.1" 14 | 15 | steps: 16 | - checkout 17 | 18 | - run: 19 | name: Install Carthage 20 | command: carthage bootstrap --no-use-binaries 21 | 22 | # Build the app and run tests 23 | - run: 24 | name: Build and run tests 25 | command: fastlane scan --project "Serpent/Serpent.xcodeproj" 26 | environment: 27 | SCAN_DEVICE: iPhone 6 28 | SCAN_SCHEME: Serpent iOS 29 | 30 | # Collect XML test results data to show in the UI, 31 | # and save the same XML files under test-results folder 32 | # in the Artifacts tab 33 | - store_test_results: 34 | path: test_output/report.xml 35 | - store_artifacts: 36 | path: /tmp/test-results 37 | destination: scan-test-results 38 | - store_artifacts: 39 | path: ~/Library/Logs/scan 40 | destination: scan-logs 41 | 42 | 43 | # workflows: 44 | # version: 2 45 | # build-and-test: 46 | # jobs: 47 | # - build -------------------------------------------------------------------------------- /.codebeatignore: -------------------------------------------------------------------------------- 1 | Serpent/Example/** 2 | Serpent/SerpentTests/** 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | Serpent.podspec linguist-documentation 2 | Serpent/Serpent/Supporting[[:space:]]Files/Serpent.h linguist-documentation 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | .DS_Store 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata 20 | 21 | ## Other 22 | *.xccheckout 23 | *.moved-aside 24 | *.xcuserstate 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | Carthage/ 54 | #Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/screenshots 65 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" ~> 4.1.0 2 | github "nodes-ios/Cashier" ~> 2.0 3 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" "4.8.1" 2 | github "nodes-ios/Cashier" "1.3" 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nodes Agency - iOS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Serpent", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "Serpent", 12 | targets: ["Serpent"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "Serpent", 23 | dependencies: []), 24 | .testTarget( 25 | name: "SerpentTests", 26 | dependencies: ["Serpent"], 27 | path: "Serpent/SerpentTests"), 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /Package@swift-5.0.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Serpent", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "Serpent", 12 | targets: ["Serpent"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "Serpent", 23 | dependencies: []), 24 | .testTarget( 25 | name: "SerpentTests", 26 | dependencies: ["Serpent"], 27 | path: "Serpent/SerpentTests"), 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /Package@swift-5.1.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Serpent", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "Serpent", 12 | targets: ["Serpent"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "Serpent", 23 | dependencies: []), 24 | .testTarget( 25 | name: "SerpentTests", 26 | dependencies: ["Serpent"], 27 | path: "Serpent/SerpentTests"), 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /Package@swift-5.2.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Serpent", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "Serpent", 12 | targets: ["Serpent"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "Serpent", 23 | dependencies: []), 24 | .testTarget( 25 | name: "SerpentTests", 26 | dependencies: ["Serpent"], 27 | path: "Serpent/SerpentTests"), 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This library has been deprecated and the repo has been archived. 2 | ### The code is still here and you can still clone it, however the library will not receive any more updates or support. 3 | 4 |

Serpent

5 | 6 | [![CircleCI](https://circleci.com/gh/nodes-ios/Serpent.svg?style=shield)](https://circleci.com/gh/nodes-ios/Serpent) 7 | [![Codecov](https://img.shields.io/codecov/c/github/nodes-ios/Serpent.svg)](https://codecov.io/github/nodes-ios/Serpent) 8 | [![codebeat badge](https://codebeat.co/badges/bf41edec-511c-405d-9036-a7253492c118)](https://codebeat.co/projects/github-com-nodes-ios-serpent) 9 | [![Carthage Compatible](https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 10 | [![CocoaPods](https://img.shields.io/cocoapods/v/Serpent.svg)](https://cocoapods.org/pods/Serpent) 11 | ![Plaforms](https://img.shields.io/badge/platforms-iOS%20|%20macOS%20|%20tvOS%20|%20watchOS%20-lightgrey.svg) 12 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nodes-ios/Serpent/blob/master/LICENSE) 13 | 14 | **Serpent** *(previously known as Serializable)* is a framework made for creating model objects or structs that can be easily serialized and deserialized from/to JSON. It's easily expandable and handles all common data types used when consuming a REST API, as well as recursive parsing of custom objects. Designed for use with Alamofire. 15 | 16 | It's designed to be used together with our helper app, the [![ModelBoiler](http://i.imgur.com/V5UzMVk.png)](https://github.com/nodes-ios/ModelBoiler) [Model Boiler](https://github.com/nodes-ios/ModelBoiler), making model creation a breeze. 17 | 18 | Serpent is implemented using protocol extensions and static typing. 19 | 20 | ## 📑 Table of Contents 21 | 22 | - [🐍 Why Serpent?](#-why-serpent) 23 | - [📝 Requirements](#-requirements) 24 | - [📦 Installation](#-installation) 25 | - [Carthage](#carthage) 26 | - [CocoaPods](#cocoapods) 27 | - [Swift Package Manager](#swift-package-manager) 28 | - [🔧 Setup](#-setup) 29 | - [💻 Usage](#-usage) 30 | - [Getting started](#getting-started) 31 | - [Using Serpent models](#using-serpent-models) 32 | - [More complex examples](#more-complex-examples) 33 | - [Using with Alamofire](#using-with-alamofire) 34 | - [Date parsing](#date-parsing) 35 | - [👥 Credits](#-credits) 36 | - [📄 License](#-license) 37 | 38 | ## 🐍 Why Serpent? 39 | There are plenty of other Encoding and Decoding frameworks available. Why should you use Serpent? 40 | 41 | * [Performance](https://github.com/nodes-ios/SerpentPerformanceComparison). Serpent is fast, up to 4x faster than similar frameworks. 42 | * [Features](https://github.com/nodes-ios/SerpentPerformanceComparison#-feature-comparison). Serpent can parse anything you throw at it. Nested objects, Enums, URLs, UIColor, you name it! 43 | * [![ModelBoiler](http://i.imgur.com/V5UzMVk.png)](https://github.com/nodes-ios/ModelBoiler) [Model Boiler](https://github.com/nodes-ios/ModelBoiler). Every framework of this kind requires tedious boilerplate code that takes forever to write. [![ModelBoiler](http://i.imgur.com/V5UzMVk.png)](https://github.com/nodes-ios/ModelBoiler) [Model Boiler](https://github.com/nodes-ios/ModelBoiler) generates it for you instantly. 44 | * [Alamofire Integration](). Using the included Alamofire extensions makes implementing an API call returning parsed model data as simple as doing a one-liner! 45 | * [Expandability](). Parsing into other datatypes can easily be added. 46 | * [Persisting](). Combined with our caching framework [Cashier](https://github.com/nodes-ios/Cashier), Serpent objects can be very easily persisted to disk. 47 | * Serpent Xcode File Template makes it easier to create the model files in Xcode. 48 | 49 | ## 📝 Requirements 50 | 51 | * iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+ 52 | * Swift 3.0+ 53 | *(Swift 2.2 & Swift 2.3 supported in older versions)* 54 | 55 | ## 📦 Installation 56 | 57 | ### Carthage 58 | ~~~bash 59 | github "nodes-ios/Serpent" ~> 1.0 60 | ~~~ 61 | 62 | > Last versions compatible with lower Swift versions: 63 | > 64 | > **Swift 2.3** 65 | > `github "nodes-ios/Serpent" == 0.13.2` 66 | > 67 | > **Swift 2.2** 68 | > `github "nodes-ios/Serpent" == 0.11.2` 69 | > 70 | > **NOTE:** Serpent was previously known as **Serializable**. 71 | 72 | ### CocoaPods 73 | 74 | Choose one of the following, add it to your `Podfile` and run `pod install`: 75 | 76 | ~~~ruby 77 | pod 'Serpent', '~> 1.0' # Just core 78 | pod 'Serpent/Extensions', '~> 1.0' # Includes core and all extensions 79 | pod 'Serpent/AlamofireExtension', '~> 1.0' # Includes core and Alamofire extension 80 | pod 'Serpent/CashierExtension', '~> 1.0' # Includes core and Cashier extension 81 | ~~~ 82 | 83 | > **NOTE:** CocoaPods only supports Serpent using Swift version 3.0 and higher. 84 | 85 | ### Swift Package Manager 86 | 87 | To use Serpent as a [Swift Package Manager](https://swift.org/package-manager/) package just add the following to your `Package.swift` file. 88 | 89 | ~~~swift 90 | import PackageDescription 91 | 92 | let package = Package( 93 | name: "YourPackage", 94 | dependencies: [ 95 | .Package(url: "https://github.com/nodes-ios/Serpent.git", majorVersion: 1) 96 | ] 97 | ) 98 | ~~~ 99 | 100 | 101 | ## 🔧 Setup 102 | 103 | We **highly** recommend you use our [![ModelBoiler](http://i.imgur.com/V5UzMVk.png)](https://github.com/nodes-ios/ModelBoiler) [Model Boiler](https://github.com/nodes-ios/ModelBoiler) to assist with generating the code needed to conform to Serpent. Instructions for installation and usage can be found at the [Model Boiler GitHub repository](https://github.com/nodes-ios/ModelBoiler). 104 | 105 | ## 💻 Usage 106 | 107 | ### Getting started 108 | 109 | Serpent supports all primitive types, `enum`, `URL`, `Date`, `UIColor`, other `Serpent` model, and `Array` of all of the aforementioned types. Your variable declarations can have a default value or be optional. 110 | 111 | Primitive types do not need to have an explicit type, if Swift is able to infer it normally. `var name: String = ""` works just as well as `var name = ""`. Optionals will of course need an explicit type. 112 | 113 | > **NOTE:** Enums you create must conform to `RawRepresentable`, meaning they must have an explicit type. Otherwise, the parser won't know what to do with the incoming data it receives. 114 | 115 | 116 | #### Create your model struct or class: 117 | 118 | ~~~swift 119 | struct Foo { 120 | var id = 0 121 | var name = "" 122 | var address: String? 123 | } 124 | ~~~ 125 | 126 | > **NOTE:** Classes must be marked `final`. 127 | 128 | #### Add the required methods for `Encodable` and `Decodable`: 129 | 130 | ~~~swift 131 | extension Foo: Serializable { 132 | init(dictionary: NSDictionary?) { 133 | id <== (self, dictionary, "id") 134 | name <== (self, dictionary, "name") 135 | address <== (self, dictionary, "address") 136 | } 137 | 138 | func encodableRepresentation() -> NSCoding { 139 | let dict = NSMutableDictionary() 140 | (dict, "id") <== id 141 | (dict, "name") <== name 142 | (dict, "address") <== address 143 | return dict 144 | } 145 | } 146 | ~~~ 147 | 148 | > **NOTE:** You can add conformance to `Serializable` which is a type alias for both `Encodable` and `Decodable`. 149 | 150 | And thats it! If you're using the [![ModelBoiler](http://i.imgur.com/V5UzMVk.png)](https://github.com/nodes-ios/ModelBoiler) [Model Boiler](https://github.com/nodes-ios/ModelBoiler), this extension will be generated for you, so that you don't need to type it all out for every model you have. 151 | 152 | ### Using Serpent models 153 | 154 | New instances of your model can be created with a dictionary, e.g. from parsed JSON. 155 | 156 | ~~~swift 157 | let dictionary = try JSONSerialization.jsonObject(with: someData, options: .allowFragments) as? NSDictionary 158 | let newModel = Foo(dictionary: dictionary) 159 | ~~~ 160 | 161 | You can generate a dictionary version of your model by calling `encodableRepresentation()`: 162 | 163 | ~~~swift 164 | let encodedDictionary = newModel.encodableRepresentation() 165 | ~~~ 166 | 167 | ### More complex examples 168 | 169 | In this example, we have two models, Student and School. 170 | 171 | ~~~swift 172 | struct Student { 173 | enum Gender: String { 174 | case male = "male" 175 | case female = "female" 176 | case unspecified = "unspecified" 177 | } 178 | 179 | var name = "" 180 | var age: Int = 0 181 | var gender: Gender? 182 | } 183 | 184 | struct School { 185 | enum Sport: Int { 186 | case football 187 | case basketball 188 | case tennis 189 | case swimming 190 | } 191 | 192 | var name = "" 193 | var location = "" 194 | var website: URL? 195 | var students: [Student] = [] 196 | var sports: [Sport]? 197 | } 198 | ~~~ 199 | 200 | 201 | You can get as complicated as you like, and the syntax will always remain the same. The extensions will be: 202 | 203 | ~~~swift 204 | extension Student: Serializable { 205 | init(dictionary: NSDictionary?) { 206 | name <== (self, dictionary, "name") 207 | age <== (self, dictionary, "age") 208 | gender <== (self, dictionary, "gender") 209 | } 210 | 211 | func encodableRepresentation() -> NSCoding { 212 | let dict = NSMutableDictionary() 213 | (dict, "name") <== name 214 | (dict, "age") <== age 215 | (dict, "gender") <== gender 216 | return dict 217 | } 218 | } 219 | 220 | extension School: Serializable { 221 | init(dictionary: NSDictionary?) { 222 | name <== (self, dictionary, "name") 223 | location <== (self, dictionary, "location") 224 | website <== (self, dictionary, "website") 225 | students <== (self, dictionary, "students") 226 | sports <== (self, dictionary, "sports") 227 | } 228 | 229 | func encodableRepresentation() -> NSCoding { 230 | let dict = NSMutableDictionary() 231 | (dict, "name") <== name 232 | (dict, "location") <== location 233 | (dict, "website") <== website 234 | (dict, "students") <== students 235 | (dict, "sports") <== sports 236 | return dict 237 | } 238 | } 239 | ~~~ 240 | Again, the [![ModelBoiler](http://i.imgur.com/V5UzMVk.png)](https://github.com/nodes-ios/ModelBoiler) [Model Boiler](https://github.com/nodes-ios/ModelBoiler) generates all of this code for you in less than a second! 241 | 242 | ### Using with Alamofire 243 | 244 | Serpent comes integrated with Alamofire out of the box, through an extension on Alamofire's `Request` construct, that adds the function `responseSerializable(completion:unwrapper)` 245 | 246 | The extension uses Alamofire's familiar `Response` type to hold the returned data, and uses its generic associated type to automatically parse the data. 247 | 248 | Consider an endpoint returning a single `school` structure matching the struct from the example above. To implement the call, simply add a function to your shared connection manager or where ever you like to put it: 249 | 250 | ~~~swift 251 | func requestSchool(completion: @escaping (DataResponse) -> Void) { 252 | request("http://somewhere.com/school/1", method: .get).responseSerializable(completion) 253 | } 254 | ~~~ 255 | 256 | In the consuming method you use it like this: 257 | 258 | ~~~swift 259 | requestSchool() { (response) in 260 | switch response.result { 261 | case .success(let school): 262 | //Use your new school object! 263 | 264 | case .failure(let error): 265 | //Handle the error object, or check your Response for more detail 266 | } 267 | } 268 | ~~~ 269 | 270 | For an array of objects, use the same technique: 271 | 272 | ~~~swift 273 | static func requestStudents(completion: @escaping (DataResponse<[Student]>) -> Void) { 274 | request("http://somewhere.com/school/1/students", method: .get).responseSerializable(completion) 275 | } 276 | ~~~ 277 | 278 | Some APIs wrap their data in containers. Use the `unwrapper` closure for that. Let's say your `/students` endpoint returns the data wrapped in a `students` object: 279 | 280 | ~~~json 281 | { 282 | "students" : [ 283 | { 284 | "..." : "..." 285 | }, 286 | { 287 | "..." : "..." 288 | } 289 | ] 290 | } 291 | ~~~ 292 | 293 | The `unwrapper` closure has 2 input arguments: The `sourceDictionary` (the JSON Response Dictionary) and the `expectedType` (the *type* of the target Serpent). Return the object that will serve as the input for the Serializable initializer. 294 | 295 | ~~~swift 296 | static func requestStudents(completion: (DataResponse<[Student]>) -> Void) { 297 | request("http://somewhere.com/school/1/students", method: .get).responseSerializable(completion, unwrapper: { $0.0["students"] }) 298 | } 299 | ~~~ 300 | 301 | If you need to unwrap the response data in every call, you can install a default unwrapper using 302 | 303 | ~~~swift 304 | Parser.defaultWrapper = { sourceDictionary, expectedType in 305 | // You custom unwrapper here... 306 | return sourceDictionary 307 | } 308 | ~~~ 309 | 310 | The `expectedType` can be used to dynamically determine the key based on the type name using reflection. This is especially useful when handling paginated data. 311 | 312 | See [here](https://github.com/nodes-ios/Nodes) for an example on how we use this in our projects at Nodes. 313 | 314 | ***NOTE:*** `responseSerializable` Internally calls `validate().responseJSON()` on the request, so you don't have to do that. 315 | 316 | ### Date parsing 317 | Serpent can create `Date` objects from the date strings in the JSON. By default, Serpent can parse the date strings from the following formats: `yyyy-MM-dd'T'HH:mm:ssZZZZZ`, `yyyy-MM-dd'T'HH:mm:ss`, `yyyy-MM-dd`. If you need to parse other date formats, you can do it by adding this line to your code (for example, in `AppDelegate`'s `didFinishLaunchingWithOptions:`: 318 | 319 | ~~~swift 320 | Date.customDateFormats = ["yyyyMMddHHmm", "yyyyMMdd"] // add the custom date formats you need here 321 | ~~~ 322 | 323 | The custom date formats won't replace the default ones, they will be still supported. 324 | 325 | ## 👥 Credits 326 | Made with ❤️ at [Nodes](http://nodesagency.com). 327 | 328 | ## 📄 License 329 | **Serpent** is available under the MIT license. See the [LICENSE](https://github.com/nodes-ios/Serpent/blob/master/LICENSE) file for more info. 330 | -------------------------------------------------------------------------------- /Serpent.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint Serpent.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "Serpent" 19 | s.version = "2.0.3" 20 | s.summary = "A protocol to serialize Swift structs and classes for encoding and decoding." 21 | s.homepage = "https://github.com/nodes-ios/Serpent" 22 | s.description = <<-DESC 23 | Serpent is a framework made for creating model objects or structs that can be easily serialized and deserialized from/to JSON. It's easily expandable and handles all common data types used when consuming a REST API, as well as recursive parsing of custom objects. 24 | It's designed to be used together with our helper app, the Model Boiler, making model creation a breeze. Serpent is implemented using protocol extensions and static typing. 25 | Also provides extensions for Alamofire and Cashier. 26 | DESC 27 | 28 | 29 | s.swift_version = '5' 30 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5' } 31 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 32 | # 33 | 34 | # Permissive license 35 | s.license = "MIT" 36 | 37 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 38 | # 39 | # Specify the authors of the library, with email addresses. Email addresses 40 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 41 | # accepts just a name if you'd rather not provide an email address. 42 | # 43 | # Specify a social_media_url where others can refer to, for example a twitter 44 | # profile URL. 45 | # 46 | 47 | s.author = { "Nodes Agency - iOS" => "ios@nodes.dk" } 48 | s.social_media_url = "http://twitter.com/nodes_ios" 49 | 50 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 51 | # 52 | # If this Pod runs only on iOS or OS X, then specify the platform and 53 | # the deployment target. You can optionally include the target after the platform. 54 | # 55 | 56 | s.platforms = { :ios => "8.0", :osx => "10.10", :watchos => "2.0", :tvos => "9.0" } 57 | 58 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 59 | # 60 | # Specify the location from where the source should be retrieved. 61 | # Supports git, hg, bzr, svn and HTTP. 62 | # 63 | 64 | s.source = { :git => "https://github.com/nodes-ios/Serpent.git", :tag => s.version } 65 | 66 | # ――― Subspecs ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 67 | # 68 | 69 | s.default_subspecs = 'Core' 70 | 71 | # Main subspec 72 | s.subspec 'Core' do |core| 73 | # Add all files 74 | core.source_files = "Serpent/Serpent/Classes/**/*" 75 | 76 | # Exclude extensions by default 77 | core.exclude_files = "Serpent/Serpent/Classes/Extensions/CashierExtension.swift", "Serpent/Serpent/Classes/Extensions/AlamofireExtension.swift" 78 | end 79 | 80 | # Subspec for all extensions 81 | s.subspec 'Extensions' do |ext| 82 | ext.dependency 'Serpent/Core' 83 | ext.dependency 'Serpent/AlamofireExtension' 84 | ext.dependency 'Serpent/CashierExtension' 85 | end 86 | 87 | # Subspec for Alamofire extension 88 | s.subspec 'AlamofireExtension' do |alamo| 89 | alamo.dependency 'Serpent/Core' 90 | alamo.dependency 'Alamofire', '~> 4.1' 91 | alamo.source_files = "Serpent/Serpent/Classes/Extensions/AlamofireExtension.swift" 92 | end 93 | 94 | # Subspec for Cashier extension 95 | s.subspec 'CashierExtension' do |cashier| 96 | cashier.dependency 'Serpent/Core' 97 | cashier.dependency 'Cashier', '~> 1.2.1' 98 | cashier.source_files = "Serpent/Serpent/Classes/Extensions/CashierExtension.swift" 99 | end 100 | 101 | end 102 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Serpent Example 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Serpent 11 | import Alamofire 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 19 | 20 | // Unwrap data from nested dictionary 21 | Parser.defaultUnwrapper = { (sourceDictionary, type) in 22 | if let nestedObject: Any = sourceDictionary.object(forKey: "results") { 23 | return nestedObject 24 | } 25 | 26 | return sourceDictionary 27 | } 28 | 29 | return true 30 | } 31 | 32 | func applicationWillResignActive(_ application: UIApplication) { 33 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 34 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 35 | } 36 | 37 | func applicationDidEnterBackground(_ application: UIApplication) { 38 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 39 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 40 | } 41 | 42 | func applicationWillEnterForeground(_ application: UIApplication) { 43 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 44 | } 45 | 46 | func applicationDidBecomeActive(_ application: UIApplication) { 47 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 48 | } 49 | 50 | func applicationWillTerminate(_ application: UIApplication) { 51 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 52 | } 53 | 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/Managers/ConnectionManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectionManager.swift 3 | // SerpentExample 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import Serpent 10 | import Alamofire 11 | import UIKit 12 | 13 | // MARK: - Basic Setup - 14 | 15 | struct ConnectionManager { 16 | static let manager = SessionManager(configuration: ConnectionManager.configuration()) 17 | 18 | static func configuration() -> URLSessionConfiguration { 19 | let configuration = SessionManager.default.session.configuration 20 | 21 | configuration.timeoutIntervalForRequest = 20 22 | configuration.httpAdditionalHeaders = ["Accept" : "application/json"] 23 | 24 | return configuration 25 | } 26 | } 27 | 28 | extension ConnectionManager { 29 | 30 | static func fetchRandomUsers(userCount count: Int = 150, completion: @escaping (DataResponse<[User]>) -> Void) { 31 | let params: [String: AnyObject] = ["results" : count as AnyObject] 32 | 33 | manager.request("https://randomuser.me/api", method: .get, 34 | parameters: params, headers: nil).responseSerializable(completion) 35 | } 36 | } 37 | 38 | 39 | // MARK: - Extensions - 40 | 41 | extension UIImageView { 42 | 43 | // Taken from http://stackoverflow.com/a/30591853/1001803 44 | public func imageFromUrl(_ url: URL?) { 45 | if let url = url { 46 | let task = URLSession.shared.dataTask(with: url) { data, response, error in 47 | guard let data = data, error == nil else { return } 48 | 49 | DispatchQueue.main.sync() { 50 | self.image = UIImage(data: data) 51 | } 52 | } 53 | task.resume() 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/Models/Identification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Identification.swift 3 | // SerpentExample 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import Serpent 10 | 11 | struct Identification { 12 | var name = "" 13 | var value = "" 14 | } 15 | 16 | extension Identification: Serializable { 17 | init(dictionary: NSDictionary?) { 18 | name <== (self, dictionary, "name") 19 | value <== (self, dictionary, "value") 20 | } 21 | 22 | func encodableRepresentation() -> NSCoding { 23 | let dict = NSMutableDictionary() 24 | (dict, "name") <== name 25 | (dict, "value") <== value 26 | return dict 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/Models/Location.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Location.swift 3 | // SerpentExample 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import Serpent 10 | 11 | struct Location { 12 | var street = "" 13 | var city = "" 14 | var state = "" 15 | var postcode = 0 16 | } 17 | 18 | extension Location: Serializable { 19 | init(dictionary: NSDictionary?) { 20 | street <== (self, dictionary, "street") 21 | city <== (self, dictionary, "city") 22 | state <== (self, dictionary, "state") 23 | postcode <== (self, dictionary, "postcode") 24 | } 25 | 26 | func encodableRepresentation() -> NSCoding { 27 | let dict = NSMutableDictionary() 28 | (dict, "street") <== street 29 | (dict, "city") <== city 30 | (dict, "state") <== state 31 | (dict, "postcode") <== postcode 32 | return dict 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/Models/LoginInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginInfo.swift 3 | // SerpentExample 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import Serpent 10 | 11 | struct LoginInfo { 12 | var username = "" 13 | var password = "" 14 | var salt = "" 15 | var md5 = "" 16 | var sha1 = "" 17 | var sha256 = "" 18 | } 19 | 20 | extension LoginInfo: Serializable { 21 | init(dictionary: NSDictionary?) { 22 | username <== (self, dictionary, "username") 23 | password <== (self, dictionary, "password") 24 | salt <== (self, dictionary, "salt") 25 | md5 <== (self, dictionary, "md5") 26 | sha1 <== (self, dictionary, "sha1") 27 | sha256 <== (self, dictionary, "sha256") 28 | } 29 | 30 | func encodableRepresentation() -> NSCoding { 31 | let dict = NSMutableDictionary() 32 | (dict, "username") <== username 33 | (dict, "password") <== password 34 | (dict, "salt") <== salt 35 | (dict, "md5") <== md5 36 | (dict, "sha1") <== sha1 37 | (dict, "sha256") <== sha256 38 | return dict 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/Models/NameInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NameInfo.swift 3 | // SerpentExample 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import Serpent 10 | 11 | struct NameInfo { 12 | var title = "" 13 | var first = "" 14 | var last = "" 15 | } 16 | 17 | extension NameInfo: Serializable { 18 | init(dictionary: NSDictionary?) { 19 | title <== (self, dictionary, "title") 20 | first <== (self, dictionary, "first") 21 | last <== (self, dictionary, "last") 22 | } 23 | 24 | func encodableRepresentation() -> NSCoding { 25 | let dict = NSMutableDictionary() 26 | (dict, "title") <== title 27 | (dict, "first") <== first 28 | (dict, "last") <== last 29 | return dict 30 | } 31 | } 32 | 33 | extension NameInfo { 34 | var nameString: String { 35 | return "\(first.capitalized) \(last.capitalized)" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/Models/ProfilePicture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfilePicture.swift 3 | // SerpentExample 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import Serpent 10 | 11 | struct ProfilePicture { 12 | var thumbnail: URL? 13 | var medium: URL? 14 | var large: URL? 15 | } 16 | 17 | extension ProfilePicture: Serializable { 18 | init(dictionary: NSDictionary?) { 19 | thumbnail <== (self, dictionary, "thumbnail") 20 | medium <== (self, dictionary, "medium") 21 | large <== (self, dictionary, "large") 22 | } 23 | 24 | func encodableRepresentation() -> NSCoding { 25 | let dict = NSMutableDictionary() 26 | (dict, "thumbnail") <== thumbnail 27 | (dict, "medium") <== medium 28 | (dict, "large") <== large 29 | return dict 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/Models/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // SerpentExample 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import Serpent 10 | 11 | enum Gender: String { 12 | case Male = "male" 13 | case Female = "female" 14 | } 15 | 16 | struct User { 17 | var gender = Gender.Male 18 | var name = NameInfo() 19 | 20 | var location = Location() 21 | 22 | var email = "" 23 | var loginInfo = LoginInfo() // <-login 24 | 25 | var registrationDate = 0 // <-registered 26 | var birthDate = 0 // <-dob 27 | 28 | var phoneNumber = "" // <-phone 29 | var cellNumber = "" // <-cell 30 | 31 | var identification = Identification() // <-id 32 | 33 | var picture = ProfilePicture() 34 | } 35 | 36 | extension User: Serializable { 37 | init(dictionary: NSDictionary?) { 38 | gender <== (self, dictionary, "gender") 39 | name <== (self, dictionary, "name") 40 | location <== (self, dictionary, "location") 41 | email <== (self, dictionary, "email") 42 | loginInfo <== (self, dictionary, "login") 43 | registrationDate <== (self, dictionary, "registered") 44 | birthDate <== (self, dictionary, "dob") 45 | phoneNumber <== (self, dictionary, "phone") 46 | cellNumber <== (self, dictionary, "cell") 47 | identification <== (self, dictionary, "id") 48 | picture <== (self, dictionary, "picture") 49 | } 50 | 51 | func encodableRepresentation() -> NSCoding { 52 | let dict = NSMutableDictionary() 53 | (dict, "gender") <== gender 54 | (dict, "name") <== name 55 | (dict, "location") <== location 56 | (dict, "email") <== email 57 | (dict, "login") <== loginInfo 58 | (dict, "registered") <== registrationDate 59 | (dict, "dob") <== birthDate 60 | (dict, "phone") <== phoneNumber 61 | (dict, "cell") <== cellNumber 62 | (dict, "id") <== identification 63 | (dict, "picture") <== picture 64 | return dict 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/User Interface/Cells/UserListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserListCell.swift 3 | // SerpentExample 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Alamofire 11 | 12 | class UserListCell: UITableViewCell { 13 | 14 | static let reuseIdentifier = "UserListCell" 15 | static let staticHeight: CGFloat = 72.0 16 | 17 | @IBOutlet weak var profileImageView: UIImageView! 18 | @IBOutlet weak var nameLabel: UILabel! 19 | @IBOutlet weak var emailLabel: UILabel! 20 | 21 | override func awakeFromNib() { 22 | super.awakeFromNib() 23 | 24 | profileImageView.layer.cornerRadius = 2 25 | profileImageView.layer.masksToBounds = true 26 | } 27 | 28 | override func prepareForReuse() { 29 | super.prepareForReuse() 30 | profileImageView.image = nil 31 | nameLabel.text = nil 32 | emailLabel.text = nil 33 | } 34 | } 35 | 36 | // MARK: - Populate - 37 | 38 | extension UserListCell { 39 | func populateWithUser(_ user: User) { 40 | // Set image 41 | if let thumbnail = user.picture.thumbnail { 42 | profileImageView.imageFromUrl(thumbnail) 43 | } 44 | 45 | // Set name and email 46 | nameLabel.text = user.name.nameString 47 | emailLabel.text = user.email 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/User Interface/Cells/UserListCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/User Interface/HomeVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeVC.swift 3 | // Serpent Example 4 | // 5 | // Created by Dominik Hádl on 17/04/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HomeVC: UIViewController, UITableViewDataSource, UITableViewDelegate { 12 | 13 | @IBOutlet weak var tableView: UITableView! 14 | var users: [User] = [] 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | tableView.register(UINib(nibName: UserListCell.reuseIdentifier, bundle: nil), 20 | forCellReuseIdentifier: UserListCell.reuseIdentifier) 21 | 22 | reloadData() 23 | } 24 | 25 | // MARK: - Callbacks - 26 | 27 | func reloadData() { 28 | ConnectionManager.fetchRandomUsers { (response) in 29 | switch response.result { 30 | case .success(let users): 31 | self.users = users 32 | self.tableView.reloadData() 33 | case .failure(let error): 34 | let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert) 35 | alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) 36 | self.present(alert, animated: true, completion: nil) 37 | } 38 | } 39 | } 40 | 41 | @IBAction func refreshPressed(_ sender: UIBarButtonItem) { 42 | reloadData() 43 | } 44 | 45 | // MARK: - UITableView Data Source & Delegate - 46 | 47 | func numberOfSections(in tableView: UITableView) -> Int { 48 | return 1 49 | } 50 | 51 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | return users.count 53 | } 54 | 55 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 56 | return UserListCell.staticHeight 57 | } 58 | 59 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 60 | let cell = tableView.dequeueReusableCell(withIdentifier: UserListCell.reuseIdentifier, for: indexPath) as? UserListCell ?? UserListCell() 61 | cell.populateWithUser(users[indexPath.row]) 62 | return cell 63 | } 64 | 65 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 66 | tableView.deselectRow(at: indexPath, animated: true) 67 | performSegue(withIdentifier: "ShowUserDetails", sender: indexPath) 68 | } 69 | 70 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 71 | if segue.identifier == "ShowUserDetails" { 72 | if let userVC = segue.destination as? UserDetailsVC, let indexPath = sender as? IndexPath { 73 | userVC.user = users[indexPath.row] 74 | } 75 | } 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Classes/User Interface/UserDetailsVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDetailsVC.swift 3 | // SerpentExample 4 | // 5 | // Created by Pablo Surfate on 4/18/16. 6 | // Copyright © 2016 Nodes ApS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UserDetailsVC: UIViewController { 12 | 13 | @IBOutlet weak var profileImageView: UIImageView! 14 | @IBOutlet weak var usernameLabel: UILabel! 15 | @IBOutlet weak var locationLabel: UILabel! 16 | @IBOutlet weak var genderLabel: UILabel! 17 | @IBOutlet weak var emailLabel: UILabel! 18 | @IBOutlet weak var phoneLabel: UILabel! 19 | 20 | var user = User() 21 | 22 | override func viewWillAppear(_ animated: Bool) { 23 | super.viewWillAppear(animated) 24 | updateUI() 25 | } 26 | 27 | fileprivate func updateUI() { 28 | // Set Labels 29 | usernameLabel.text = user.name.nameString 30 | genderLabel.text = user.gender.encodableRepresentation() 31 | locationLabel.text = user.location.city + ", " + user.location.state 32 | emailLabel.text = user.email 33 | phoneLabel.text = user.phoneNumber 34 | 35 | // Set image 36 | if let picture = user.picture.large { 37 | profileImageView.imageFromUrl(picture) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "Icon-60@3x.png", 47 | "scale" : "3x" 48 | } 49 | ], 50 | "info" : { 51 | "version" : 1, 52 | "author" : "xcode" 53 | } 54 | } -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/Logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Logo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Logo@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Logo@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/Logo.imageset/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/Logo.imageset/Logo.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/Logo.imageset/Logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/Logo.imageset/Logo@2x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/Logo.imageset/Logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/Logo.imageset/Logo@3x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/LogoText.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LogoText.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LogoText@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LogoText@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/LogoText.imageset/LogoText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/LogoText.imageset/LogoText.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/LogoText.imageset/LogoText@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/LogoText.imageset/LogoText@2x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Resources/Assets.xcassets/LogoText.imageset/LogoText@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/Example/SerpentExample/Resources/Assets.xcassets/LogoText.imageset/LogoText@3x.png -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Storyboards/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Storyboards/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 121 | 127 | 133 | 139 | 145 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /Serpent/Example/SerpentExample/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Serpent 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Serpent/Serpent.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Serpent/Serpent.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Serpent/Serpent.xcodeproj/xcshareddata/xcschemes/Serpent OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Serpent/Serpent.xcodeproj/xcshareddata/xcschemes/Serpent iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Serpent/Serpent.xcodeproj/xcshareddata/xcschemes/Serpent tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Serpent/Serpent.xcodeproj/xcshareddata/xcschemes/Serpent watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Serpent/Serpent/Classes/Extensions/AlamofireExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlamofireExtension.swift 3 | // Serializable 4 | // 5 | // Created by Kasper Welner on 08/03/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | public struct Parser { 13 | public static let APICallSucceededNotification = "APICallSucceededNotification" 14 | } 15 | 16 | public extension Parser { 17 | 18 | /** 19 | Parse any generic object using the parsing handler. 20 | */ 21 | 22 | internal static func serializer(_ parsingHandler: (( _ data: Any? ) -> T?)?) -> DataResponseSerializer { 23 | return DataResponseSerializer { (request, response, data, error) -> Result in 24 | 25 | //If we have an error here - pass it on 26 | if let error = error { 27 | return .failure(error) 28 | } 29 | 30 | let result = Request.serializeResponseJSON(options: .allowFragments, response: response, data: data, error: error) 31 | 32 | switch result { 33 | case let .success(value): 34 | if let parsedObject: T = parsingHandler?( value ) { 35 | NotificationCenter.default.post(name: Notification.Name(rawValue: APICallSucceededNotification), object: nil) 36 | return .success(parsedObject) 37 | } else { 38 | return .failure(NSError(domain: "Serializable.Parser", code: 2048, userInfo: [ NSLocalizedDescriptionKey : "Parsing block failed!", "JSONResponse" : value])) 39 | } 40 | 41 | case .failure(let error): 42 | //TODO: Add stubbed request for testing response not being empty 43 | var responseDict = [NSLocalizedDescriptionKey : "Serialization failed!"] 44 | responseDict["error"] = "\(error.localizedDescription)" 45 | responseDict["response"] = String(describing: response) 46 | return .failure(NSError(domain: "Serializable.Parser", code: 2048, userInfo: responseDict)) 47 | } 48 | } 49 | } 50 | 51 | typealias Unwrapper = (_ sourceDictionary: NSDictionary, _ expectedType:Any) -> Any? 52 | 53 | /** 54 | The default unwrapper. Default implementation just passes data straight through. 55 | */ 56 | 57 | static var defaultUnwrapper: Unwrapper = { a, b in return a } 58 | } 59 | 60 | 61 | //MARK: - Wrapper methods 62 | 63 | public extension Alamofire.DataRequest 64 | { 65 | /** 66 | Adds a handler that attempts to parse the result of the request into a **Decodable** 67 | 68 | - parameter completionHandler:A closure that is invoked when the request is finished 69 | 70 | - parameter unwrapper: A closure that extracts the data to be parsed from the JSON response data. 71 | The default implementation checks for a "data" field in the JSON response, then checks for a field 72 | with the same name as the target model. If not found, it passes the JSON response straight through. 73 | 74 | - returns: The request 75 | */ 76 | @discardableResult 77 | func responseSerializable(_ completionHandler: @escaping (DataResponse) -> Void, 78 | unwrapper:@escaping Parser.Unwrapper = Parser.defaultUnwrapper) -> Self { 79 | let serializer = Parser.serializer { (data: Any?) -> T? in 80 | 81 | if let sourceDictionary = data as? NSDictionary { 82 | let unwrappedDictionary = unwrapper(sourceDictionary, T.self) as? NSDictionary ?? sourceDictionary 83 | return T(dictionary: unwrappedDictionary) as T? 84 | } else if let array = data as? NSArray, array.count == 1, let dictionary = array[0] as? NSDictionary { 85 | let unwrapped = unwrapper(dictionary, T.self) as? NSDictionary ?? dictionary 86 | return T(dictionary: unwrapped) as T? 87 | } 88 | 89 | return nil 90 | } 91 | 92 | return validate().response(responseSerializer: serializer, completionHandler: completionHandler) 93 | } 94 | 95 | /** 96 | Adds a handler that attempts to parse the result of the request into an array of **Decodable** 97 | 98 | - parameter completionHandler:A closure that is invoked when the request is finished 99 | 100 | - parameter unwrapper: A closure that extracts the data to be parsed from the JSON response data. 101 | The default implementation checks for a "data" field in the JSON response, then checks for a field 102 | with the same name as the target model. If not found, it passes the JSON response straight through. 103 | 104 | - returns: The request 105 | */ 106 | @discardableResult 107 | func responseSerializable(_ completionHandler: @escaping (DataResponse<[T]>) -> Void, 108 | unwrapper:@escaping Parser.Unwrapper = Parser.defaultUnwrapper) -> Self { 109 | 110 | let serializer = Parser.serializer { (data: Any?) -> [T]? in 111 | if let dataDict = data as? NSDictionary, let unwrappedArray = unwrapper(dataDict, T.self) as? NSArray { 112 | return T.array(unwrappedArray) 113 | } else if let array = data as? NSArray { 114 | return T.array(array) 115 | } 116 | 117 | return nil 118 | } 119 | 120 | return validate().response(responseSerializer: serializer, completionHandler: completionHandler) 121 | } 122 | 123 | /** 124 | Convenience method for a handler that does not need to parse the results of the network request. 125 | 126 | - parameter completionHandler:A closure that is invoked when the request is finished 127 | 128 | - returns: The request 129 | */ 130 | 131 | @discardableResult 132 | func responseSerializable(_ completionHandler: @escaping (DataResponse) -> Void) -> Self { 133 | return validate().responseJSON(completionHandler: completionHandler) 134 | } 135 | 136 | } 137 | // Convenience type for network requests with no response data 138 | public typealias NilSerializable = Any 139 | -------------------------------------------------------------------------------- /Serpent/Serpent/Classes/Extensions/CashierExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CashierExtension.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 12/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cashier 11 | 12 | public extension Cashier { 13 | 14 | /** 15 | Set an `object` to the `BridgingBox` and link it with a `key`. 16 | 17 | - Parameters: 18 | - object: Generic type which confroms with `Serializable`. 19 | - key: Key as a `String`. 20 | */ 21 | func setSerializable(_ object: T, forKey key: String) where T: Serializable { 22 | let box = BridgingBox(object) 23 | self.setObject(box, forKey: key) 24 | BridgingBox.sharedBoxCache[self.id+key] = object 25 | } 26 | 27 | /** 28 | Set an `array` to the `BridgingBox` and link it with a `key`. 29 | 30 | - Parameters: 31 | - object: Generic type which confroms with `_ArrayType`. 32 | - key: Key as a `String`. 33 | */ 34 | func setSerializable(_ object: T, forKey key: String) where T: Sequence, T.Iterator.Element: Serializable { 35 | let boxedArray = object.map { BridgingBox($0) } 36 | self.setObject(boxedArray, forKey: key) 37 | BridgingBox.sharedBoxCache[self.id+key] = object 38 | } 39 | 40 | /** 41 | Check first if an object with the `key` from `BridgingBox sharedBoxCache` exists and if the `key` is valid. 42 | If an object for that `key` exisits and the `key` is valid, return the onject of `BridgingBox sharedBoxCache` for that key, if not return nil. 43 | 44 | If the object for that `key` of `BridgingBox sharedBoxCache` does not exist check if an `objectForKey` as `BridgingBox` with that `key` exists. Else return nil. 45 | If the object exisits get the value of that `BridgingBox` and assign it to `BridgingBox.sharedBoxCache` and return the object. 46 | 47 | - Parameter key: `Key` for `BridgingBox` or `objectForKey` as `String`. 48 | - Returns: Generic type stored value/object that conforms with `Serializable` or return `nil`. 49 | */ 50 | func serializableForKey(_ key: String) -> T? where T:Serializable { 51 | if let cachedSerializable = BridgingBox.sharedBoxCache[self.id+key] as? T { 52 | return self.object(forKeyIsValid: key) ? cachedSerializable : nil 53 | } 54 | 55 | guard let box:BridgingBox = self.object(forKey: key) as? BridgingBox else { 56 | return nil 57 | } 58 | 59 | let finalVal:T? = box.value() 60 | BridgingBox.sharedBoxCache[self.id+key] = finalVal 61 | return finalVal 62 | } 63 | 64 | 65 | /** 66 | Check first if an array with the `key` of `BridgingBox sharedBoxCache` exists and if the `key` is valid. 67 | If an array exisits for that `key` and the `key` is valid return the `BridgingBox sharedBoxCache` for that key, if not it will return nil. 68 | 69 | If the array for that `key` of `BridgingBox sharedBoxCache` does not exists check if an `objectForKey` as an `array` with that `key` exists that holds objects of `BridgingBox`. Else return nil. 70 | If the array exists get the values of that `BridgingBox` and append them to a `returnArray` and assign the array to `BridgingBox.sharedBoxCache` with the defined `key` and return the `array`. 71 | 72 | - Parameter key: `Key` for `BridgingBox` as `String`. 73 | - Returns: Array that holds object that conforms with `Serializable` or return `nil`. 74 | */ 75 | func serializableForKey(_ key: String) -> [T]? where T:Serializable { 76 | if let cachedSerializable = BridgingBox.sharedBoxCache[self.id+key] as? [T] { 77 | return self.object(forKeyIsValid: key) ? cachedSerializable : nil 78 | } 79 | 80 | guard let box = self.object(forKey: key) as? [BridgingBox] else { 81 | return nil 82 | } 83 | 84 | /// Why did I not just use the Map() function? Because it results in a Segmentation fault 11 from the compiler! Thanks, Jobs!!! :-( 85 | 86 | var returnArray = [T]() 87 | for boxval in box { 88 | if let val:T = boxval.value() { 89 | returnArray.append(val) 90 | } 91 | } 92 | 93 | BridgingBox.sharedBoxCache[self.id+key] = returnArray 94 | return returnArray 95 | } 96 | 97 | /** 98 | Clears all saved disk data and optionally purges all memory caches. 99 | 100 | - parameter purgeMemoryCaches: If set to `true`, it will also purge all memory caches, including 101 | the shared bridging box cache for objects adhering to `Serializable`. 102 | */ 103 | func clearAllData(_ purgeMemoryCaches: Bool) { 104 | self.clearAllData() 105 | 106 | if purgeMemoryCaches { 107 | BridgingBox.sharedBoxCache.removeAll() 108 | } 109 | } 110 | 111 | /** 112 | Removes a serializable value from saved disk, and optionally the memory cache. 113 | 114 | - parameter key: `Key` for `BridgingBox` as `String`. 115 | - parameter purgeMemoryCache: If set to `true`, it will also purge the memory cache. 116 | */ 117 | func deleteSerializableForKey(_ key: String, purgeMemoryCache purge: Bool = true) { 118 | if purge { 119 | BridgingBox.sharedBoxCache.removeValue(forKey: self.id+key) 120 | } 121 | self.deleteObject(forKey: key) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Serpent/Serpent/Classes/Extensions/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 16/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Protocols - 12 | // MARK: String Initializable 13 | 14 | public protocol StringInitializable { 15 | static func fromString(_ string: String) -> T? 16 | func stringRepresentation() -> String 17 | } 18 | 19 | extension URL: StringInitializable { 20 | public static func fromString(_ string: String) -> T? { 21 | return self.init(string: string) as? T 22 | } 23 | 24 | public func stringRepresentation() -> String { 25 | return self.absoluteString 26 | } 27 | } 28 | 29 | extension Date: StringInitializable { 30 | static fileprivate let internalDateFormatter = DateFormatter() 31 | static fileprivate let allowedDateFormats = ["yyyy-MM-dd'T'HH:mm:ssZZZZZ", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd"] 32 | static public var customDateFormats: [String] = [] 33 | 34 | public static func fromString(_ string: String) -> T? { 35 | for format in allowedDateFormats + customDateFormats { 36 | internalDateFormatter.dateFormat = format 37 | if let date = internalDateFormatter.date(from: string) as? T { 38 | return date 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | 45 | public func stringRepresentation() -> String { 46 | Date.internalDateFormatter.dateFormat = Date.allowedDateFormats.first 47 | return Date.internalDateFormatter.string(from: self) 48 | } 49 | } 50 | 51 | // MARK: Hex Initializable 52 | #if os(OSX) 53 | import Cocoa 54 | typealias Color = NSColor 55 | #else 56 | import UIKit 57 | typealias Color = UIColor 58 | #endif 59 | 60 | public protocol HexInitializable { 61 | static func fromHexString(_ hexString: String) -> T? 62 | } 63 | 64 | extension Color: HexInitializable { 65 | 66 | public static func fromHexString(_ hexString: String) -> T? { 67 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 68 | var int = UInt32() 69 | let a, r, g, b: UInt32 70 | 71 | guard Scanner(string: hex).scanHexInt32(&int) else { 72 | return nil 73 | } 74 | 75 | switch hex.count { 76 | // RGB (12-bit) 77 | case 3: 78 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 79 | // RRGGBB (24-bit) 80 | case 6: 81 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 82 | // ARGB (32-bit) 83 | case 8: 84 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 85 | default: 86 | return nil 87 | } 88 | 89 | return self.init(red: CGFloat(r) / 255, 90 | green: CGFloat(g) / 255, 91 | blue: CGFloat(b) / 255, 92 | alpha: CGFloat(a) / 255) as? T 93 | } 94 | } 95 | 96 | // MARK: - Extensions - 97 | // MARK: SequenceType 98 | 99 | public extension Sequence where Iterator.Element:Encodable { 100 | func encodableRepresentation() -> [NSCoding] { 101 | return self.map { element in return element.encodableRepresentation() } 102 | } 103 | } 104 | 105 | // MARK: RawRepresentable 106 | 107 | public extension RawRepresentable { 108 | func encodableRepresentation() -> RawValue { 109 | return self.rawValue 110 | } 111 | } 112 | 113 | 114 | -------------------------------------------------------------------------------- /Serpent/Serpent/Classes/Operators/Operator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Operator.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 16/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | // MARK: - Custom Operators - 13 | 14 | precedencegroup SerializablePrecedence { 15 | associativity: left 16 | } 17 | 18 | infix operator <== : SerializablePrecedence 19 | 20 | // For Encodable 21 | public func <==(left: (dict: NSMutableDictionary, key: String), right: T?) { 22 | left.dict.setValue(right, forKey: left.key) 23 | } 24 | 25 | public func <==(left: (dict: NSMutableDictionary, key: String), right: T?) { 26 | left.dict.setValue(right?.encodableRepresentation(), forKey: left.key) 27 | } 28 | 29 | public func <==(left: (dict: NSMutableDictionary, key: String), right: T?) { 30 | left.dict.setValue(right?.stringRepresentation(), forKey: left.key) 31 | } 32 | 33 | public func <==(left: (dict: NSMutableDictionary, key: String), right: T?) where T:Sequence, T.Iterator.Element: Encodable { 34 | left.dict.setValue(right?.encodableRepresentation(), forKey: left.key) 35 | } 36 | 37 | public func <==(left: (dict: NSMutableDictionary, key: String), right: T?) { 38 | left.dict.setValue(right?.encodableRepresentation(), forKey: left.key) 39 | } 40 | 41 | // For Decodable 42 | 43 | // Primitive 44 | public func <==(left: inout T?, right: (instance: S, dict: NSDictionary?, key: String)) where S: Keymappable { 45 | let value: T? = right.instance.mapped(right.dict, key: right.key) 46 | left = value ?? left 47 | } 48 | 49 | public func <==(left: inout T, right: (instance: S, dict: NSDictionary?, key: String)) where S: Keymappable { 50 | let value: T? = right.instance.mapped(right.dict, key: right.key) 51 | left = value ?? left 52 | } 53 | 54 | // Serializable 55 | public func <==(left: inout T?, right: (instance: S, dict: NSDictionary?, key: String)) where T: Decodable, S: Keymappable { 56 | let value: T? = right.instance.mapped(right.dict, key: right.key) 57 | left = value ?? left 58 | } 59 | 60 | public func <==(left: inout T, right: (instance: S, dict: NSDictionary?, key: String)) where T: Decodable, S: Keymappable { 61 | let value: T? = right.instance.mapped(right.dict, key: right.key) 62 | left = value ?? left 63 | } 64 | 65 | // Enum 66 | public func <==(left: inout T?, right: (instance: S, dict: NSDictionary?, key: String)) where T: RawRepresentable, S: Keymappable { 67 | let value: T? = right.instance.mapped(right.dict, key: right.key) 68 | left = value ?? left 69 | } 70 | 71 | public func <==(left: inout T, right: (instance: S, dict: NSDictionary?, key: String)) where T: RawRepresentable, S: Keymappable { 72 | let value: T? = right.instance.mapped(right.dict, key: right.key) 73 | left = value ?? left 74 | } 75 | 76 | public func <==(left: inout [T]?, right: (instance: S, dict: NSDictionary?, key: String)) where T: RawRepresentable, S: Keymappable { 77 | let value: [T]? = right.instance.mapped(right.dict, key: right.key) 78 | left = value ?? left 79 | } 80 | 81 | public func <==(left: inout [T], right: (instance: S, dict: NSDictionary?, key: String)) where T: RawRepresentable, S: Keymappable { 82 | let value: [T]? = right.instance.mapped(right.dict, key: right.key) 83 | left = value ?? left 84 | } 85 | 86 | // [Serializable] 87 | public func <==(left: inout T?, right: (instance: S, dict: NSDictionary?, key: String)) where T:Sequence, T.Iterator.Element: Decodable, S: Keymappable { 88 | let value: T? = right.instance.mapped(right.dict, key: right.key) 89 | left = value ?? left 90 | } 91 | 92 | public func <==(left: inout T, right: (instance: S, dict: NSDictionary?, key: String)) where T:Sequence, T.Iterator.Element: Decodable, S: Keymappable { 93 | let value: T? = right.instance.mapped(right.dict, key: right.key) 94 | left = value ?? left 95 | } 96 | 97 | // StringInitializable 98 | 99 | public func <==(left: inout T?, right: (instance: S, dict: NSDictionary?, key: String)) where T: StringInitializable, S: Keymappable { 100 | let value: T? = right.instance.mapped(right.dict, key: right.key) 101 | left = value ?? left 102 | } 103 | 104 | public func <==(left: inout T, right: (instance: S, dict: NSDictionary?, key: String)) where T: StringInitializable, S: Keymappable { 105 | let value: T? = right.instance.mapped(right.dict, key: right.key) 106 | left = value ?? left 107 | } 108 | 109 | // HexInitializable 110 | 111 | public func <==(left: inout T?, right: (instance: S, dict: NSDictionary?, key: String)) where T: HexInitializable, S: Keymappable { 112 | let value: T? = right.instance.mapped(right.dict, key: right.key) 113 | left = value ?? left 114 | } 115 | 116 | public func <==(left: inout T, right: (instance: S, dict: NSDictionary?, key: String)) where T: HexInitializable, S: Keymappable { 117 | let value: T? = right.instance.mapped(right.dict, key: right.key) 118 | left = value ?? left 119 | } 120 | -------------------------------------------------------------------------------- /Serpent/Serpent/Classes/Other/BridgingBox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bridging.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 16/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc open class BridgingBox : NSObject, NSCoding { 12 | 13 | static var sharedBoxCache = [String : Any]() 14 | 15 | fileprivate var internalValue: Encodable? 16 | open var dictValue:NSDictionary? 17 | 18 | /** 19 | Get value of the `NSDictionary` `dictValue` that will be or was archived and that conforms with `Serializable`. 20 | 21 | - returns: Value of type `Serializable` or `nil`. 22 | */ 23 | open func value() -> T? { 24 | if let dictValue = dictValue , internalValue == nil { 25 | return T(dictionary: dictValue) 26 | } 27 | 28 | return internalValue as? T 29 | } 30 | 31 | public init(_ value: Encodable) { 32 | self.internalValue = value 33 | } 34 | 35 | required public init?(coder aDecoder: NSCoder) { 36 | super.init() 37 | dictValue = aDecoder.decodeObject(forKey: "dictValue") as? NSDictionary 38 | } 39 | 40 | open func encode(with aCoder: NSCoder) { 41 | if let value = internalValue { 42 | if dictValue == nil { 43 | dictValue = value.encodableRepresentation() as? NSDictionary 44 | } 45 | 46 | aCoder.encode(dictValue, forKey:"dictValue") 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Serpent/Serpent/Classes/Serpent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Serializable.swift 3 | // NOCore 4 | // 5 | // Created by Kasper Welner on 22/01/15. 6 | // Copyright (c) 2015 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Serializable - 12 | 13 | public protocol Serializable: Decodable, Encodable, Keymappable {} 14 | 15 | // MARK: - Encodable - 16 | 17 | public protocol Encodable { 18 | func encodableRepresentation() -> NSCoding 19 | } 20 | 21 | // MARK: - Decodable - 22 | 23 | public protocol Decodable { 24 | init(dictionary:NSDictionary?) 25 | } 26 | 27 | public extension Decodable { 28 | static func array(_ source: Any?) -> [Self] { 29 | guard let source = source as? [NSDictionary] else { 30 | return [Self]() 31 | } 32 | return source.map { 33 | Self(dictionary: ($0)) 34 | } 35 | } 36 | } 37 | 38 | private struct DefaultKeyMappings { 39 | fileprivate static let mappings = [String : String]() 40 | } 41 | 42 | // MARK: - Keymappable - 43 | 44 | public protocol Keymappable {} 45 | 46 | public extension Keymappable { 47 | 48 | /** 49 | Maps the content of value for **key** in **dictionary** to generic type **T**, conforming to **Serializable** protocol. 50 | 51 | - parameter dictionary: An optional dictionary containing values which should be parsed. 52 | - parameter key: ValueForKey will be called on the dictionary to extract the value to be parsed 53 | 54 | - returns: A mapped object conforming to *Serializable*, or nil if parsing failed 55 | */ 56 | func mapped(_ dictionary: NSDictionary?, key: String) -> T? where T:Decodable { 57 | 58 | // Ensure the dictionary is not nil 59 | guard let dict = dictionary else { return nil } 60 | 61 | // Get the value from the dictionary for our key 62 | let sourceOpt = dict[key] 63 | 64 | // Check if we have the correct type and return it if possible 65 | if sourceOpt != nil && sourceOpt is NSDictionary { 66 | return T(dictionary: (sourceOpt as! NSDictionary)) 67 | } 68 | 69 | return nil 70 | } 71 | 72 | /** 73 | Maps the content of value for **key** in **dictionary** to an array containing where elements is of generic type **T**, conforming to **Serializable** protocol. 74 | 75 | - parameter dictionary: An optional dictionary containing values which should be parsed. 76 | - parameter key: ValueForKey will be called on the dictionary to extract the value to be parsed. 77 | 78 | - returns: An array of mapped objects conforming to *Serializable*, or an empty array if parsing failed. 79 | */ 80 | func mapped(_ dictionary: NSDictionary?, key: String) -> T? where T:Sequence, T.Iterator.Element: Decodable { 81 | // Ensure the dictionary is not nil and get the value from the dictionary for our key 82 | guard let dict = dictionary, let sourceOpt = dict[key] else { return nil } 83 | 84 | if sourceOpt is [NSDictionary] { 85 | let source = (sourceOpt as! [NSDictionary]) 86 | let finalArray = source.map { T.Iterator.Element.init(dictionary: $0) } as? T 87 | return finalArray 88 | } 89 | 90 | return nil 91 | } 92 | 93 | /** 94 | A generic mapping function that will try to parse primitive types from the provided dictionary. 95 | Currently supported types are `Int`, `Float`, `Double`, `Bool`, `Char` and `String`. 96 | 97 | The `key` parameter will be first used to check value in custom input key mappings and if 98 | no value is found, then `key` is used as the key to get the value stored in `dictionary`. 99 | 100 | - parameter dictionary: An optional dictionary containing values which should be parsed. 101 | - parameter key: The key which will be used to get the actual key from input key mappings 102 | or as the actual key for the value being parsed from the dictionary. 103 | 104 | - returns: The value of primitive type `T` or `nil` if parsing was unsuccessful. 105 | */ 106 | func mapped(_ dictionary: NSDictionary?, key: String) -> T? { 107 | 108 | // Ensure the dictionary is not nil 109 | guard let dict = dictionary else { return nil } 110 | 111 | // Get the value from the dictionary for our key 112 | let sourceOpt = dict[key] 113 | 114 | // Figure out what type is the value we got and parse accordingly 115 | switch sourceOpt { 116 | 117 | case (is T): 118 | return (sourceOpt as! T) 119 | 120 | case (is String) where T.self is Int.Type: 121 | let source = (sourceOpt as! String) 122 | return Int(source) as? T 123 | 124 | case (is String) where T.self is Double.Type: 125 | let source = (sourceOpt as! String) 126 | return Double(source) as? T 127 | 128 | case (is NSString) where T.self is Bool.Type: 129 | let source = (sourceOpt as! NSString) 130 | return source.boolValue as? T 131 | 132 | case (is String) where T.self is Character.Type: 133 | let source = (sourceOpt as! String) 134 | return Character(source) as? T 135 | 136 | case (is NSNumber) where T.self is String.Type: 137 | let source = (sourceOpt as! NSNumber) 138 | return String(describing: source) as? T 139 | 140 | case (is Double) where T.self is Float.Type: 141 | let source = (sourceOpt as! Double) 142 | return Float(source) as? T 143 | 144 | default: 145 | return nil 146 | } 147 | } 148 | 149 | /** 150 | A generic mapping function that will try to parse an object of type `T` from the string 151 | value contained in the provided dictionary. 152 | 153 | The `key` parameter will be first used to check value in custom input key mappings and if 154 | no value is found, then `key` is used as the key to get the value stored in `dictionary`. 155 | 156 | - parameter dictionary: An optional dictionary containing the array which should be parsed. 157 | - parameter key: The key which will be used to get the actual key from input key mappings 158 | or as the actual key for the value being parsed from the dictionary. 159 | 160 | - returns: The value of type `T` or `nil` if parsing was unsuccessful. 161 | */ 162 | func mapped(_ dictionary: NSDictionary?, key: String) -> T? { 163 | if let dict = dictionary, let source = dict[key] as? String , source.isEmpty == false { 164 | return T.fromString(source) 165 | } 166 | 167 | return nil 168 | } 169 | 170 | /** 171 | A generic mapping function that will try to parse enumerations with raw value from the 172 | provided dictionary. 173 | 174 | This function internally uses a variant of the generic `mapped()` function used to parse 175 | primitive types, which means that only enums with raw value of primitive type are supported. 176 | 177 | The `key` parameter will be first used to check value in custom input key mappings and if 178 | no value is found, then `key` is used as the key to get the value stored in `dictionary`. 179 | 180 | - parameter dictionary: An optional dictionary containing values which should be parsed. 181 | - parameter key: The key which will be used to get the actual key from input key mappings 182 | or as the actual key for the value being parsed from the dictionary. 183 | 184 | - returns: The enumeration of enum type `T` or `nil` if parsing was unsuccessful or 185 | enumeration does not exist. 186 | */ 187 | func mapped(_ dictionary: NSDictionary?, key: String) -> T? { 188 | guard let source: T.RawValue = self.mapped(dictionary, key: key) else { 189 | return nil 190 | } 191 | 192 | return T(rawValue: source) 193 | } 194 | 195 | /** 196 | A generic mapping function that will try to parse an array of enumerations with raw value from the 197 | array contained in the provided dictionary. 198 | 199 | The `key` parameter will be first used to check value in custom input key mappings and if 200 | no value is found, then `key` is used as the key to get the value stored in `dictionary`. 201 | 202 | - parameter dictionary: An optional dictionary containing the array which should be parsed. 203 | - parameter key: The key which will be used to get the actual key from input key mappings 204 | or as the actual key for the value being parsed from the dictionary. 205 | 206 | - returns: An array of enum type `T` or an empty array if parsing was unsuccessful. 207 | */ 208 | func mapped(_ dictionary: NSDictionary?, key: String) -> T? where T:Sequence, T.Iterator.Element: RawRepresentable { 209 | if let dict = dictionary, let source = dict[key] as? [T.Iterator.Element.RawValue] { 210 | let finalArray = source.map { T.Iterator.Element.init(rawValue: $0)! } 211 | return (finalArray as! T) 212 | } 213 | 214 | return nil 215 | } 216 | 217 | 218 | 219 | /** 220 | A generic mapping function that will try to parse an object of type `T` from the hex string 221 | value contained in the provided dictionary. 222 | 223 | The `key` parameter will be first used to check value in custom input key mappings and if 224 | no value is found, then `key` is used as the key to get the value stored in `dictionary`. 225 | 226 | - parameter dictionary: An optional dictionary containing the array which should be parsed. 227 | - parameter key: The key which will be used to get the actual key from input key mappings 228 | or as the actual key for the value being parsed from the dictionary. 229 | 230 | - returns: The value of type `T` or `nil` if parsing was unsuccessful. 231 | */ 232 | func mapped(_ dictionary: NSDictionary?, key: String) -> T? { 233 | guard let dict = dictionary, let source = dict[key] else { 234 | return nil 235 | } 236 | 237 | if let hexString = source as? String , hexString.isEmpty == false { 238 | return T.fromHexString(hexString) 239 | } 240 | 241 | return source as? T 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /Serpent/Serpent/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Serpent/Serpent/Supporting Files/Serpent.h: -------------------------------------------------------------------------------- 1 | // 2 | // Serpent.h 3 | // Serpent 4 | // 5 | // Created by Chris Combs on 09/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | @import Foundation; 9 | 10 | //! Project version number for Serializable. 11 | FOUNDATION_EXPORT double SerializableVersionNumber; 12 | 13 | //! Project version string for Serializable. 14 | FOUNDATION_EXPORT const unsigned char SerializableVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/CustomOperatorsTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": "success", 3 | "second_string": "haha", 4 | "other_serializable": { 5 | "integer": 1, 6 | "optional_integer": 2, 7 | "optional_integer_with_default_value": 3, 8 | "string": "success", 9 | "optional_string": "optional success", 10 | "optional_string_with_default_value": "optional default success" 11 | }, 12 | "some_serializable": { 13 | "integer": 1, 14 | "optional_integer": 2, 15 | "optional_integer_with_default_value": 3, 16 | "string": "success", 17 | "optional_string": "optional success", 18 | "optional_string_with_default_value": "optional default success" 19 | }, 20 | "some_url" : "http://www.google.com", 21 | "some_enum" : 1, 22 | "some_array" : [ 23 | { 24 | "integer": 1, 25 | "optional_integer": 2, 26 | "optional_integer_with_default_value": 3, 27 | "string": "success", 28 | "optional_string": "optional success", 29 | "optional_string_with_default_value": "optional default success" 30 | }, 31 | { 32 | "integer": 1, 33 | "optional_integer": 2, 34 | "optional_integer_with_default_value": 3, 35 | "string": "success", 36 | "optional_string": "optional success", 37 | "optional_string_with_default_value": "optional default success" 38 | }, 39 | ], 40 | "some_dictionary" : { 41 | "test" : "value", 42 | "integer" : 1, 43 | "float" : 1.023 44 | } 45 | } -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/CustomOperatorsTestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOperatorsModel.swift 3 | // NOCore 4 | // 5 | // Created by Dominik Hádl on 04/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Serpent 11 | 12 | enum Type: Int { 13 | case first = 0 14 | case second = 1 15 | } 16 | 17 | struct CustomOperatorsTestModel { 18 | var string: String = "" 19 | var secondString = "" 20 | var nilString: String? 21 | var otherSerializable: CustomOperatorsTestNestedModel? 22 | var someSerializable = CustomOperatorsTestNestedModel() 23 | var someUrl = URL(string: "") 24 | var someEnum: Type = .first 25 | var someArray: [CustomOperatorsTestNestedModel] = [] 26 | var someDictionary: [String : AnyObject]? 27 | } 28 | 29 | extension CustomOperatorsTestModel: Serializable { 30 | init(dictionary: NSDictionary?) { 31 | string <== (self, dictionary, "string") 32 | secondString <== (self, dictionary, "second_string") 33 | nilString <== (self, dictionary, "nil_string") 34 | otherSerializable <== (self, dictionary, "other_serializable") 35 | someSerializable <== (self, dictionary, "some_serializable") 36 | someUrl <== (self, dictionary, "some_url") 37 | someEnum <== (self, dictionary, "some_enum") 38 | someArray <== (self, dictionary, "some_array") 39 | someDictionary <== (self, dictionary, "some_dictionary") 40 | } 41 | 42 | func encodableRepresentation() -> NSCoding { 43 | let dict = NSMutableDictionary() 44 | (dict, "string") <== string 45 | (dict, "second_string") <== secondString 46 | (dict, "nil_string") <== nilString 47 | (dict, "other_serializable") <== otherSerializable 48 | (dict, "some_serializable") <== someSerializable 49 | (dict, "some_url") <== someUrl 50 | (dict, "some_enum") <== someEnum 51 | (dict, "some_array") <== someArray 52 | (dict, "some_dictionary") <== someDictionary 53 | return dict 54 | } 55 | } 56 | 57 | struct CustomOperatorsTestNestedModel { 58 | var integer: Int = 0 59 | var optionalInteger: Int? 60 | var optionalIntegerWithDefaultValue: Int? = 0 61 | 62 | var double: Double = 0.0 63 | var optionalDouble: Double? 64 | var optionalDoubleWithDefaultValue: Double? = 0.0 65 | 66 | var string: String = "" 67 | var optionalString: String? 68 | var optionalStringWithDefaultValue: String? = "" 69 | } 70 | 71 | extension CustomOperatorsTestNestedModel: Equatable {} 72 | 73 | func ==(lhs: CustomOperatorsTestNestedModel, rhs: CustomOperatorsTestNestedModel) -> Bool { 74 | return lhs.string == rhs.string 75 | } 76 | 77 | extension CustomOperatorsTestNestedModel:Serializable { 78 | init(dictionary: NSDictionary?) { 79 | integer <== (self, dictionary, "integer") 80 | optionalInteger <== (self, dictionary, "optional_integer") 81 | optionalIntegerWithDefaultValue <== (self, dictionary, "optional_integer_with_default_value") 82 | double <== (self, dictionary, "double") 83 | optionalDouble <== (self, dictionary, "optional_double") 84 | optionalDoubleWithDefaultValue <== (self, dictionary, "optional_doubleWithDefaultValue") 85 | string <== (self, dictionary, "string") 86 | optionalString <== (self, dictionary, "optional_string") 87 | optionalStringWithDefaultValue <== (self, dictionary, "optional_string_with_default_value") 88 | } 89 | 90 | func encodableRepresentation() -> NSCoding { 91 | let dict = NSMutableDictionary() 92 | (dict, "integer") <== integer 93 | (dict, "optional_integer") <== optionalInteger 94 | (dict, "optional_integer_with_default_value") <== optionalIntegerWithDefaultValue 95 | (dict, "double") <== double 96 | (dict, "optional_double") <== optionalDouble 97 | (dict, "optional_doubleWithDefaultValue") <== optionalDoubleWithDefaultValue 98 | (dict, "string") <== string 99 | (dict, "optional_string") <== optionalString 100 | (dict, "optional_string_with_default_value") <== optionalStringWithDefaultValue 101 | return dict 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/DecodableModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodableModel.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 16/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Serpent 11 | 12 | struct DecodableModel { 13 | var id = 0 14 | var name = "" 15 | var text = "" 16 | } 17 | 18 | extension DecodableModel:Serializable { 19 | init(dictionary: NSDictionary?) { 20 | id <== (self, dictionary, key: "id") 21 | name <== (self, dictionary, key: "name") 22 | text <== (self, dictionary, key: "text") 23 | } 24 | 25 | func encodableRepresentation() -> NSCoding { 26 | let dict = NSMutableDictionary() 27 | (dict, "id") <== id 28 | (dict, "name") <== name 29 | (dict, "text") <== text 30 | return dict 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/DecodableTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "models" : [ 3 | { 4 | "id" : 1, 5 | "name" : "Hello", 6 | "text" : "some text" 7 | }, 8 | { 9 | "id" : 2, 10 | "name" : "Hello2", 11 | "text" : "some text2" 12 | }, 13 | { 14 | "id" : 3, 15 | "name" : "Hello3", 16 | "text" : "some text3" 17 | }, 18 | { 19 | "id" : 4, 20 | "name" : "Hello4", 21 | "text" : "some text4" 22 | } 23 | ], 24 | "model" : 25 | { 26 | "id" : 5, 27 | "name" : "Hello5", 28 | "text" : "some text5" 29 | } 30 | } -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/EnumsTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "string_enum" : "value1", 3 | "optional_string_enum" : "DifferentValue", 4 | "optional_string_enum_with_default_value" : "value1", 5 | "wrong_type_string_enum" : 1, 6 | 7 | "double_enum" : 123.456, 8 | "optional_double_enum" : 0, 9 | "optional_double_enum_with_default_value" : -9453.5147684, 10 | "wrong_type_double_enum" : "someval", 11 | 12 | "string_enum_array" : [ "value1", "DifferentValue"], 13 | "optional_string_enum_array" : [ "value1", "DifferentValue"], 14 | "optional_string_enum_array_with_default_value" : [ "value1", "DifferentValue"], 15 | "wrong_type_string_enum_array" : [ 1, "LOL"], 16 | 17 | "double_enum_array" : [ 123.456, -9453.5147684], 18 | "optional_double_enum_array" : [ 123.456, -9453.5147684], 19 | "optional_double_enum_array_with_default_value" : [ 123.456, -9453.5147684], 20 | "wrong_type_double_enum_array" : [ 1.456, "LOL"], 21 | } -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/EnumsTestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrimitivesTestModel.swift 3 | // NOCore 4 | // 5 | // Created by Dominik Hádl on 17/01/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Serpent 11 | 12 | enum StringEnum: String { 13 | case Value1 = "value1" 14 | case DifferentValue = "DifferentValue" 15 | case NoneValue 16 | } 17 | 18 | enum DoubleEnum: Double { 19 | case noneValue 20 | case value1 = 123.456 21 | case differentValue = -9453.5147684 22 | } 23 | 24 | struct EnumsTestModel { 25 | var stringEnum: StringEnum = .NoneValue 26 | var optionalStringEnum: StringEnum? 27 | var optionalStringEnumWithDefaultValue: StringEnum? = .NoneValue 28 | var nonExistentStringEnum: StringEnum? 29 | var wrongTypeStringEnum: StringEnum? 30 | 31 | var stringEnumArray: [StringEnum] = [StringEnum]() 32 | var optionalStringEnumArray: [StringEnum]? 33 | var optionalStringEnumArrayWithDefaultValue: [StringEnum]? = [StringEnum]() 34 | var nonExistentStringEnumArray: [StringEnum]? 35 | var wrongTypeStringEnumArray: [StringEnum]? 36 | 37 | var doubleEnum: DoubleEnum = .noneValue 38 | var optionalDoubleEnum: DoubleEnum? 39 | var optionalDoubleEnumWithDefaultValue: DoubleEnum? = .noneValue 40 | var nonExistentDoubleEnum: DoubleEnum? 41 | var wrongTypeDoubleEnum: DoubleEnum? 42 | 43 | var doubleEnumArray: [DoubleEnum] = [DoubleEnum]() 44 | var optionalDoubleEnumArray: [DoubleEnum]? 45 | var optionalDoubleEnumArrayWithDefaultValue: [DoubleEnum]? = [DoubleEnum]() 46 | var nonExistentDoubleEnumArray: [DoubleEnum]? 47 | var wrongTypeDoubleEnumArray: [DoubleEnum]? 48 | } 49 | 50 | extension EnumsTestModel:Serializable { 51 | init(dictionary: NSDictionary?) { 52 | stringEnum <== (self, dictionary, key: "string_enum") 53 | optionalStringEnum <== (self, dictionary, key: "optional_string_enum") 54 | optionalStringEnumWithDefaultValue <== (self, dictionary, key: "optional_string_enum_with_default_value") 55 | nonExistentStringEnum <== (self, dictionary, key: "non_existent_string_enum") 56 | wrongTypeStringEnum <== (self, dictionary, key: "wrong_type_string_enum") 57 | 58 | stringEnumArray <== (self, dictionary, key: "string_enum_array") 59 | optionalStringEnumArray <== (self, dictionary, key: "optional_string_enum_array") 60 | optionalStringEnumArrayWithDefaultValue <== (self, dictionary, key: "optional_string_enum_array_with_default_value") 61 | nonExistentStringEnumArray <== (self, dictionary, key: "non_existent_string_enum_array") 62 | wrongTypeStringEnumArray <== (self, dictionary, key: "wrong_type_string_enum_array") 63 | 64 | doubleEnum <== (self, dictionary, key: "double_enum") 65 | optionalDoubleEnum <== (self, dictionary, key: "optional_double_enum") 66 | optionalDoubleEnumWithDefaultValue <== (self, dictionary, key: "optional_double_enum_with_default_value") 67 | nonExistentDoubleEnum <== (self, dictionary, key: "non_existent_double_enum") 68 | wrongTypeDoubleEnum <== (self, dictionary, key: "wrong_type_double_enum") 69 | 70 | doubleEnumArray <== (self, dictionary, key: "double_enum_array") 71 | optionalDoubleEnumArray <== (self, dictionary, key: "optional_double_enum_array") 72 | optionalDoubleEnumArrayWithDefaultValue <== (self, dictionary, key: "optional_double_enum_array_with_default_value") 73 | nonExistentDoubleEnumArray <== (self, dictionary, key: "non_existent_double_enum_array") 74 | wrongTypeDoubleEnumArray <== (self, dictionary, key: "wrong_type_double_enum_array") 75 | } 76 | 77 | func encodableRepresentation() -> NSCoding { 78 | let dict = NSMutableDictionary() 79 | (dict, "string_enum") <== stringEnum 80 | (dict, "optional_string_enum") <== optionalStringEnum 81 | (dict, "optional_string_enum_with_default_value") <== optionalStringEnumWithDefaultValue 82 | (dict, "non_existent_string_enum") <== nonExistentStringEnum 83 | (dict, "wrong_type_string_enum") <== wrongTypeStringEnum 84 | (dict, "string_enum_array") <== stringEnumArray 85 | (dict, "optional_string_enum_array") <== optionalStringEnumArray 86 | (dict, "optional_string_enum_array_with_default_value") <== optionalStringEnumArrayWithDefaultValue 87 | (dict, "non_existent_string_enum") <== nonExistentStringEnum 88 | (dict, "wrong_type_string_enum") <== wrongTypeStringEnum 89 | (dict, "double_enum") <== doubleEnum 90 | (dict, "optional_double_enum") <== optionalDoubleEnum 91 | (dict, "optional_double_enum_with_default_value") <== optionalDoubleEnumWithDefaultValue 92 | (dict, "non_existent_double_enum") <== nonExistentDoubleEnum 93 | (dict, "wrong_type_double_enum") <== wrongTypeDoubleEnum 94 | (dict, "double_enum_array") <== doubleEnumArray 95 | (dict, "optional_double_enum_array") <== optionalDoubleEnumArray 96 | (dict, "optional_double_enum_array_with_default_value") <== optionalDoubleEnumArrayWithDefaultValue 97 | (dict, "non_existent_double_enum") <== nonExistentDoubleEnum 98 | (dict, "wrong_type_double_enum") <== wrongTypeDoubleEnum 99 | return dict 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/HexInitializableTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_color" : "55AACC", 3 | "full_color" : "#55AACC", 4 | "bad_color" : "0066", 5 | "not_color" : { 6 | "color" : "asdf" 7 | }, 8 | "three_color" : "5AC", 9 | "invalid_hex_color" : "SSg12F", 10 | "alpha_rgb" : "CC55AACC" 11 | } 12 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/HexInitializableTestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HexInitializableTest.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 10/03/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Serpent 11 | #if os(OSX) 12 | import Cocoa 13 | typealias Color = NSColor 14 | #else 15 | import UIKit 16 | typealias Color = UIColor 17 | #endif 18 | 19 | struct HexInitializableTestModel { 20 | var shortColor = Color.red 21 | var fullColor: Color? 22 | var badColor: Color? 23 | var notColor: Color? 24 | var invalidHexColor: Color? 25 | var threeColor: Color? 26 | var alphaRGB: Color? 27 | } 28 | 29 | extension HexInitializableTestModel: Serializable { 30 | init(dictionary: NSDictionary?) { 31 | shortColor <== (self, dictionary, "short_color") 32 | fullColor <== (self, dictionary, "full_color") 33 | badColor <== (self, dictionary, "bad_color") 34 | notColor <== (self, dictionary, "not_color") 35 | invalidHexColor <== (self, dictionary, "invalid_hex_color") 36 | threeColor <== (self, dictionary, "three_color") 37 | alphaRGB <== (self, dictionary, "alpha_rgb") 38 | } 39 | 40 | func encodableRepresentation() -> NSCoding { 41 | let dict = NSMutableDictionary() 42 | (dict, "short_color") <== shortColor 43 | (dict, "full_color") <== fullColor 44 | (dict, "bad_color") <== badColor 45 | (dict, "not_color") <== notColor 46 | (dict, "invalid_hex_color") <== invalidHexColor 47 | (dict, "three_color") <== threeColor 48 | (dict, "alpha_rgb") <== alphaRGB 49 | return dict 50 | } 51 | } 52 | 53 | struct HexInitializableTestNilModel { 54 | var someColor = Color.red 55 | } 56 | 57 | extension HexInitializableTestNilModel: Serializable { 58 | init(dictionary: NSDictionary?) { 59 | someColor <== (self, dictionary, "some_color") 60 | } 61 | 62 | func encodableRepresentation() -> NSCoding { 63 | let dict = NSMutableDictionary() 64 | (dict, "some_color") <== someColor 65 | return dict 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/NetworkTestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkTestModel.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 18/03/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Serpent 10 | 11 | struct NetworkTestModel { 12 | var origin = "" 13 | var url: NSURL? 14 | } 15 | 16 | extension NetworkTestModel: Serializable { 17 | init(dictionary: NSDictionary?) { 18 | origin <== (self, dictionary, "origin") 19 | url <== (self, dictionary, "url") 20 | } 21 | 22 | func encodableRepresentation() -> NSCoding { 23 | let dict = NSMutableDictionary() 24 | (dict, "origin") <== origin 25 | (dict, "url") <== url 26 | return dict 27 | } 28 | } 29 | 30 | struct BadModel { 31 | var something = "" 32 | } 33 | 34 | extension BadModel: Serializable { 35 | init(dictionary: NSDictionary?) { 36 | something <== (self, dictionary, "something") 37 | } 38 | 39 | func encodableRepresentation() -> NSCoding { 40 | let dict = NSMutableDictionary() 41 | (dict, "something") <== something 42 | return dict 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/NilTestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NilTestModel.swift 3 | // Serializable 4 | // 5 | // Created by Chris on 24/09/2016. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Serpent 11 | 12 | public struct NilModel { 13 | 14 | public enum NilModelType: Int { 15 | case first = 0 16 | case second = 1 17 | } 18 | 19 | var id: Int = 0 20 | var name = SimpleModel() 21 | var names = [SimpleModel]() 22 | var url = URL(string: "http://www.google.com")! 23 | var someEnum: NilModelType = .first 24 | var someEnumArray: [NilModelType] = [] 25 | var somePrimitiveArray: [String] = [] 26 | 27 | var optionalId: Int? 28 | var optionalName: SimpleModel? 29 | var optionalNames: [SimpleModel]? 30 | var optionalUrl: URL? 31 | var optionalEnum: NilModelType? 32 | var optionalEnumArray: [NilModelType]? 33 | var optionalPrimitiveArray: [String]? 34 | } 35 | extension NilModel: Serializable { 36 | public init(dictionary: NSDictionary?) { 37 | id <== (self, dictionary, "id") 38 | name <== (self, dictionary, "name") 39 | names <== (self, dictionary, "names") 40 | url <== (self, dictionary, "url") 41 | someEnum <== (self, dictionary, "some_enum") 42 | someEnumArray <== (self, dictionary, "some_enum_array") 43 | somePrimitiveArray <== (self, dictionary, "some_primitive_array") 44 | optionalId <== (self, dictionary, "optional_id") 45 | optionalName <== (self, dictionary, "optional_name") 46 | optionalNames <== (self, dictionary, "optional_names") 47 | optionalUrl <== (self, dictionary, "optional_url") 48 | optionalEnum <== (self, dictionary, "optional_enum") 49 | optionalEnumArray <== (self, dictionary, "optional_enum_array") 50 | optionalPrimitiveArray <== (self, dictionary, "optional_primitive_array") 51 | } 52 | 53 | public func encodableRepresentation() -> NSCoding { 54 | let dict = NSMutableDictionary() 55 | (dict, "id") <== id 56 | (dict, "name") <== name 57 | (dict, "names") <== names 58 | (dict, "url") <== url 59 | (dict, "some_enum") <== someEnum 60 | (dict, "some_enum_array") <== someEnumArray 61 | (dict, "some_primitive_array") <== somePrimitiveArray 62 | (dict, "optional_id") <== optionalId 63 | (dict, "optional_name") <== optionalName 64 | (dict, "optional_names") <== optionalNames 65 | (dict, "optional_url") <== optionalUrl 66 | (dict, "optional_enum") <== optionalEnum 67 | (dict, "optional_enum_array") <== optionalEnumArray 68 | (dict, "optional_primitive_array") <== optionalPrimitiveArray 69 | return dict 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/PerformanceTestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PerformanceTestModel.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 25/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | 10 | import Serpent 11 | 12 | 13 | struct PerformanceTestModel { 14 | 15 | enum EyeColor: String { 16 | case Blue = "blue" 17 | case Green = "green" 18 | case Brown = "brown" 19 | } 20 | 21 | enum Fruit: String { 22 | case Apple = "apple" 23 | case Banana = "banana" 24 | case Strawberry = "strawberry" 25 | } 26 | 27 | var id = "" 28 | var index = 0 29 | var guid = "" 30 | var isActive = true //<- isActive 31 | var balance = "" 32 | var picture: NSURL? 33 | var age = 0 34 | var eyeColor: EyeColor = .Brown //<- eyeColor 35 | var name = Name() 36 | var company = "" 37 | var email = "" 38 | var phone = "" 39 | var address = "" 40 | var about = "" 41 | var registered = "" 42 | var latitude = 0.0 43 | var longitude = 0.0 44 | var greeting = "" 45 | var favoriteFruit: Fruit? //<- favoriteFruit 46 | } 47 | 48 | extension PerformanceTestModel: Serializable { 49 | init(dictionary: NSDictionary?) { 50 | id <== (self, dictionary, "id") 51 | index <== (self, dictionary, "index") 52 | guid <== (self, dictionary, "guid") 53 | isActive <== (self, dictionary, "isActive") 54 | balance <== (self, dictionary, "balance") 55 | picture <== (self, dictionary, "picture") 56 | age <== (self, dictionary, "age") 57 | eyeColor <== (self, dictionary, "eyeColor") 58 | name <== (self, dictionary, "name") 59 | company <== (self, dictionary, "company") 60 | email <== (self, dictionary, "email") 61 | phone <== (self, dictionary, "phone") 62 | address <== (self, dictionary, "address") 63 | about <== (self, dictionary, "about") 64 | registered <== (self, dictionary, "registered") 65 | latitude <== (self, dictionary, "latitude") 66 | longitude <== (self, dictionary, "longitude") 67 | greeting <== (self, dictionary, "greeting") 68 | favoriteFruit <== (self, dictionary, "favoriteFruit") 69 | } 70 | 71 | func encodableRepresentation() -> NSCoding { 72 | let dict = NSMutableDictionary() 73 | (dict, "id") <== id 74 | (dict, "index") <== index 75 | (dict, "guid") <== guid 76 | (dict, "isActive") <== isActive 77 | (dict, "balance") <== balance 78 | (dict, "picture") <== picture 79 | (dict, "age") <== age 80 | (dict, "eyeColor") <== eyeColor 81 | (dict, "name") <== name 82 | (dict, "company") <== company 83 | (dict, "email") <== email 84 | (dict, "phone") <== phone 85 | (dict, "address") <== address 86 | (dict, "about") <== about 87 | (dict, "registered") <== registered 88 | (dict, "latitude") <== latitude 89 | (dict, "longitude") <== longitude 90 | (dict, "greeting") <== greeting 91 | (dict, "favoriteFruit") <== favoriteFruit 92 | return dict 93 | } 94 | } 95 | 96 | struct Name { 97 | var first = "" 98 | var last = "" 99 | } 100 | 101 | extension Name: Serializable { 102 | init(dictionary: NSDictionary?) { 103 | first <== (self, dictionary, "first") 104 | last <== (self, dictionary, "last") 105 | } 106 | 107 | func encodableRepresentation() -> NSCoding { 108 | let dict = NSMutableDictionary() 109 | (dict, "first") <== first 110 | (dict, "last") <== last 111 | return dict 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/PerformanceTestSmallModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PerformanceTestModel.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 25/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | 10 | import Serpent 11 | 12 | 13 | struct PerformanceTestSmallModel { 14 | var id = "" 15 | var name = "" 16 | } 17 | 18 | extension PerformanceTestSmallModel: Serializable { 19 | init(dictionary: NSDictionary?) { 20 | id <== (self, dictionary, "id") 21 | name <== (self, dictionary, "name") 22 | } 23 | 24 | func encodableRepresentation() -> NSCoding { 25 | let dict = NSMutableDictionary() 26 | (dict, "id") <== id 27 | (dict, "name") <== name 28 | return dict 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/PrimitivesTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "integer" : 1, 3 | "optional_integer" : 2, 4 | "optional_integer_with_default_value" : 3, 5 | 6 | "negative_integer" : -10, 7 | "optional_negative_integer" : -20, 8 | "optional_negative_integer_with_default_value" : -30, 9 | 10 | "double" : 123.1234567, 11 | "optional_double" : 234.2345678, 12 | "optional_double_with_default_value" : 345.3456789, 13 | 14 | "float" : 1.234, 15 | "optional_float" : 2.345, 16 | "optional_float_with_default_value" : 3.456, 17 | 18 | "bool" : "yes", 19 | "optional_bool" : "1", 20 | "optional_bool_with_default_value" : true, 21 | 22 | "char" : "S", 23 | "optional_char" : "O", 24 | "optional_char_with_default_value" : "D", 25 | 26 | "string" : "success", 27 | "optional_string" : "optional success", 28 | "optional_string_with_default_value" : "optional default success", 29 | 30 | "intString" : "1", 31 | "doubleString" : "1.5", 32 | "boolString" : "true", 33 | "boolIntString" : "1", 34 | 35 | "stringInt" : 1, 36 | "stringDouble" : 1.5, 37 | "stringBool" : true 38 | } -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/PrimitivesTestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrimitivesTestModel.swift 3 | // NOCore 4 | // 5 | // Created by Dominik Hádl on 17/01/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Serpent 11 | 12 | struct PrimitivesTestModel { 13 | var integer: Int = 0 14 | var optionalInteger: Int? 15 | var optionalIntegerWithDefaultValue: Int? = 0 16 | 17 | var negativeInteger: Int = -1 18 | var optionalNegativeInteger: Int? 19 | var optionalNegativeIntegerWithDefaultValue: Int? = -1 20 | 21 | var double: Double = 0.0 22 | var optionalDouble: Double? 23 | var optionalDoubleWithDefaultValue: Double? = 0.0 24 | 25 | var float: Float = 0.0 26 | var optionalFloat: Float? 27 | var optionalFloatWithDefaultValue: Float? = 0.0 28 | 29 | var bool: Bool = false 30 | var optionalBool: Bool? 31 | var optionalBoolWithDefaultValue: Bool? = false 32 | 33 | var char: Character = "A" 34 | var optionalChar: Character? 35 | var optionalCharWithDefaultValue: Character? = "A" 36 | 37 | var string: String = "" 38 | var optionalString: String? 39 | var optionalStringWithDefaultValue: String? = "" 40 | 41 | var intString: Int = 0 42 | var doubleString: Double = 0.0 43 | var boolString: Bool = false 44 | var boolIntString: Bool = false 45 | 46 | var stringDouble: String = "" 47 | var stringBool: String = "" 48 | } 49 | 50 | extension PrimitivesTestModel:Serializable { 51 | init(dictionary: NSDictionary?) { 52 | integer = self.mapped(dictionary, key: "integer") ?? integer 53 | optionalInteger = self.mapped(dictionary, key: "optional_integer") 54 | optionalIntegerWithDefaultValue = self.mapped(dictionary, key: "optional_integer_with_default_value") 55 | negativeInteger = self.mapped(dictionary, key: "negative_integer") ?? negativeInteger 56 | optionalNegativeInteger = self.mapped(dictionary, key: "optional_negative_integer") 57 | optionalNegativeIntegerWithDefaultValue = self.mapped(dictionary, key: "optional_negative_integer_with_default_value") 58 | double = self.mapped(dictionary, key: "double") ?? double 59 | optionalDouble = self.mapped(dictionary, key: "optional_double") 60 | optionalDoubleWithDefaultValue = self.mapped(dictionary, key: "optional_double_with_default_value") 61 | float = self.mapped(dictionary, key: "float") ?? float 62 | optionalFloat = self.mapped(dictionary, key: "optional_float") 63 | optionalFloatWithDefaultValue = self.mapped(dictionary, key: "optional_float_with_default_value") 64 | bool = self.mapped(dictionary, key: "bool") ?? bool 65 | optionalBool = self.mapped(dictionary, key: "optional_bool") 66 | optionalBoolWithDefaultValue = self.mapped(dictionary, key: "optional_bool_with_default_value") 67 | char = self.mapped(dictionary, key: "char") ?? char 68 | optionalChar = self.mapped(dictionary, key: "optional_char") 69 | optionalCharWithDefaultValue = self.mapped(dictionary, key: "optional_char_with_default_value") 70 | string = self.mapped(dictionary, key: "string") ?? string 71 | optionalString = self.mapped(dictionary, key: "optional_string") 72 | optionalStringWithDefaultValue = self.mapped(dictionary, key: "optional_string_with_default_value") 73 | intString = self.mapped(dictionary, key: "intString") ?? intString 74 | doubleString = self.mapped(dictionary, key: "doubleString") ?? doubleString 75 | boolString = self.mapped(dictionary, key: "boolString") ?? boolString 76 | boolIntString = self.mapped(dictionary, key: "boolIntString") ?? boolIntString 77 | stringDouble = self.mapped(dictionary, key: "stringDouble") ?? stringDouble 78 | stringBool = self.mapped(dictionary, key: "stringBool") ?? stringBool 79 | } 80 | 81 | func encodableRepresentation() -> NSCoding { 82 | let dict = NSMutableDictionary() 83 | dict["integer"] = integer 84 | dict["optional_integer"] = optionalInteger 85 | dict["optional_integer_with_default_value"] = optionalIntegerWithDefaultValue 86 | dict["negative_integer"] = negativeInteger 87 | dict["optional_negative_integer"] = optionalNegativeInteger 88 | dict["optional_negative_integer_with_default_value"] = optionalNegativeIntegerWithDefaultValue 89 | dict["double"] = double 90 | dict["optional_double"] = optionalDouble 91 | dict["optional_double_with_default_value"] = optionalDoubleWithDefaultValue 92 | dict["float"] = float 93 | dict["optional_float"] = optionalFloat 94 | dict["optional_float_with_default_value"] = optionalFloatWithDefaultValue 95 | dict["bool"] = bool 96 | dict["optional_bool"] = optionalBool 97 | dict["optional_bool_with_default_value"] = optionalBoolWithDefaultValue 98 | dict["char"] = "\(char)" 99 | dict["optional_char"] = optionalChar != nil ? "\(optionalChar!)" : "\(String(describing: optionalChar))" 100 | dict["optional_char_with_default_value"] = optionalCharWithDefaultValue != nil ? "\(optionalCharWithDefaultValue!)" : "\(String(describing: optionalCharWithDefaultValue))" 101 | dict["string"] = string 102 | dict["optional_string"] = optionalString 103 | dict["optional_string_with_default_value"] = optionalStringWithDefaultValue 104 | dict["intString"] = intString 105 | dict["doubleString"] = doubleString 106 | dict["boolString"] = boolString 107 | dict["boolIntString"] = boolIntString 108 | dict["stringBool"] = stringBool 109 | return dict 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/SerializableEntityTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : 1, 3 | "simple" : 4 | [{ 5 | "id" : 0 6 | }], 7 | "simples" : 8 | [{ 9 | "id" : 0 10 | }, 11 | { 12 | "id" : 1 13 | }], 14 | "strings" : 15 | [ 16 | "one", 17 | "two", 18 | "three" 19 | ], 20 | "ints" : 21 | [ 22 | "one", 23 | "two", 24 | "three" 25 | ], 26 | "stringInts" : 27 | [ 28 | "one", 29 | "two", 30 | "three" 31 | ], 32 | "stringDicts" : 33 | [ 34 | { 35 | "one" : "asdf" 36 | }, 37 | { 38 | "two" : false 39 | }, 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/SerializableEntityTestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerializableEntityTestModel.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 16/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Serpent 11 | 12 | struct SerializableEntityTestModel { 13 | var id = 0 14 | var simple: SimpleModel? 15 | var simples: [SimpleModel]? 16 | var strings: [String] = [] 17 | var ints: [Int] = [] 18 | var stringInts: [SimpleModel] = [] 19 | var stringDicts: [SimpleModel] = [] 20 | } 21 | 22 | extension SerializableEntityTestModel:Serializable { 23 | init(dictionary: NSDictionary?) { 24 | id <== (self, dictionary, "id") 25 | simple <== (self, dictionary, "simple") 26 | simples <== (self, dictionary, "simples") 27 | strings <== (self, dictionary, "strings") 28 | ints <== (self, dictionary, "ints") 29 | stringInts <== (self, dictionary, "stringInts") 30 | stringDicts <== (self, dictionary, "stringDicts") 31 | } 32 | 33 | func encodableRepresentation() -> NSCoding { 34 | let dict = NSMutableDictionary() 35 | (dict, "id") <== id 36 | (dict, "simple") <== simple 37 | (dict, "simples") <== simples 38 | (dict, "strings") <== strings 39 | (dict, "ints") <== ints 40 | (dict, "stringInts") <== stringInts 41 | (dict, "stringDicts") <== stringDicts 42 | return dict 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/SimpleModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : 1 3 | } -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/SimpleModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleModel.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 16/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Serpent 11 | 12 | struct SimpleModel { 13 | var id = 0 14 | } 15 | 16 | extension SimpleModel:Serializable { 17 | init(dictionary: NSDictionary?) { 18 | id <== (self, dictionary, key: "id") 19 | } 20 | 21 | func encodableRepresentation() -> NSCoding { 22 | let dict = NSMutableDictionary() 23 | (dict, "id") <== id 24 | return dict 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/StringInitializableTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "some_url" : "http://www.google.com", 3 | "some_date" : "1974-08-15T15:17:14+00:00", 4 | "some_empty_url" : "", 5 | "some_empty_date" : "", 6 | "some_bad_date" :"WOW_SUCH_DATE_NOT_CORRECT", 7 | "some_custom_date" : "2016-11-15 17:19:04" 8 | } 9 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Models/StringInitializableTestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringInitializableTestModel.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 17/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Serpent 11 | 12 | struct StringInitializableTestModel { 13 | var someUrl: URL? 14 | var someDate: Date? 15 | var someEmptyDate: Date? 16 | var someEmptyURL: URL? //<- some_empty_url 17 | var someBadDate: Date? 18 | var someCustomDate: Date? 19 | } 20 | 21 | extension StringInitializableTestModel: Serializable { 22 | init(dictionary: NSDictionary?) { 23 | someUrl <== (self, dictionary, "some_url") 24 | someDate <== (self, dictionary, "some_date") 25 | someEmptyDate <== (self, dictionary, "some_empty_date") 26 | someEmptyURL <== (self, dictionary, "some_empty_url") 27 | someBadDate <== (self, dictionary, "some_bad_date") 28 | someCustomDate <== (self, dictionary, "some_custom_date") 29 | } 30 | 31 | func encodableRepresentation() -> NSCoding { 32 | let dict = NSMutableDictionary() 33 | (dict, "some_url") <== someUrl 34 | (dict, "some_date") <== someDate 35 | (dict, "some_empty_date") <== someEmptyDate 36 | (dict, "some_empty_url") <== someEmptyURL 37 | (dict, "some_bad_date") <== someBadDate 38 | (dict, "some_custom_date") <== someCustomDate 39 | return dict 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/TestEndpoint/ArrayTest.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": 1, 3 | "name": "Hello", 4 | "text": "some text" 5 | }, 6 | { 7 | "id": 2, 8 | "name": "Hello2", 9 | "text": "some text2" 10 | }, 11 | { 12 | "id": 3, 13 | "name": "Hello3", 14 | "text": "some text3" 15 | }, 16 | { 17 | "id": 4, 18 | "name": "Hello4", 19 | "text": "some text4" 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/TestEndpoint/ArrayTestOneObject.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": 1, 3 | "name": "Hello", 4 | "text": "some text" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/TestEndpoint/BadArrayTest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/SerpentTests/TestEndpoint/BadArrayTest.json -------------------------------------------------------------------------------- /Serpent/SerpentTests/TestEndpoint/Empty.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent/SerpentTests/TestEndpoint/Empty.json -------------------------------------------------------------------------------- /Serpent/SerpentTests/TestEndpoint/NestedArrayTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 1, 5 | "name": "Hello", 6 | "text": "some text" 7 | }, 8 | { 9 | "id": 2, 10 | "name": "Hello2", 11 | "text": "some text2" 12 | }, 13 | { 14 | "id": 3, 15 | "name": "Hello3", 16 | "text": "some text3" 17 | }, 18 | { 19 | "id": 4, 20 | "name": "Hello4", 21 | "text": "some text4" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /Serpent/SerpentTests/TestEndpoint/NetworkModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "origin": "185.118.250.50", 3 | "url": "http://httpbin.org/get" 4 | } 5 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/TestEndpoint/NetworkModelBad.json: -------------------------------------------------------------------------------- 1 | { 2 | "origin": "185.118.250.50", 3 | "url": "http://httpbin.org/get" 4 | Hello! 5 | } 6 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/AlamofireExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlamofireExtensionTests.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 18/03/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Serpent 11 | import Alamofire 12 | 13 | class AlamofireExtensionTests: XCTestCase { 14 | 15 | let timeoutDuration = 2.0 16 | 17 | let manager = SessionManager() 18 | 19 | override func setUp() { 20 | super.setUp() 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | } 23 | 24 | func urlForResource(resource: String) -> URL? { 25 | if let path = Bundle(for: type(of: self)).path(forResource: resource, ofType: "json") { 26 | return URL(fileURLWithPath: path) 27 | } 28 | return nil 29 | } 30 | 31 | func testAlamofireExtension() { 32 | let expectation = self.expectation(description: "Expected network request success") 33 | let handler:(Alamofire.DataResponse) -> Void = { result in 34 | switch result.result { 35 | case .success: 36 | expectation.fulfill() 37 | default: 38 | print(result) 39 | break // Fail 40 | 41 | } 42 | } 43 | if let url = urlForResource(resource: "NetworkModel") { 44 | manager.request(url, method: .get).responseSerializable(handler) 45 | } 46 | waitForExpectations(timeout: timeoutDuration, handler: nil) 47 | } 48 | 49 | func testAlamofireExtensionBadJSON() { 50 | let expectation = self.expectation(description: "Expected bad data from response") 51 | let handler:(Alamofire.DataResponse) -> Void = { result in 52 | switch result.result { 53 | case .failure: 54 | expectation.fulfill() 55 | default: 56 | break 57 | } 58 | } 59 | if let url = urlForResource(resource: "NetworkModelBad") { 60 | manager.request(url, method: .get).responseSerializable(handler) 61 | } 62 | waitForExpectations(timeout: timeoutDuration, handler: nil) 63 | } 64 | 65 | 66 | func testAlamofireExtensionBadJSONObject() { 67 | let expectation = self.expectation(description: "Expected bad object from response") 68 | let handler:(Alamofire.DataResponse<[NetworkTestModel]>) -> Void = { result in 69 | switch result.result { 70 | case .failure: 71 | expectation.fulfill() 72 | default: 73 | break 74 | } 75 | } 76 | if let url = urlForResource(resource: "NetworkModel") { 77 | manager.request(url, method: .get).responseSerializable(handler) 78 | } 79 | waitForExpectations(timeout: timeoutDuration, handler: nil) 80 | } 81 | 82 | func testAlamofireExtensionUnexpectedArrayJSON() { 83 | let expectation = self.expectation(description: "Expected array data to single object from response") 84 | let handler:(Alamofire.DataResponse) -> Void = { result in 85 | switch result.result { 86 | case .failure: 87 | expectation.fulfill() 88 | default: 89 | break 90 | } 91 | } 92 | if let url = urlForResource(resource: "ArrayTest") { 93 | manager.request(url, method: .get).responseSerializable(handler) 94 | } 95 | waitForExpectations(timeout: timeoutDuration, handler: nil) 96 | } 97 | 98 | func testAlamofireExtensionEmptyJSON() { 99 | let expectation = self.expectation(description: "Expected empty response") 100 | let handler:(Alamofire.DataResponse) -> Void = { result in 101 | switch result.result { 102 | case .failure: 103 | expectation.fulfill() 104 | default: 105 | break 106 | } 107 | } 108 | if let url = urlForResource(resource: "Empty") { 109 | manager.request(url, method: .get).responseSerializable(handler) 110 | } 111 | waitForExpectations(timeout: timeoutDuration, handler: nil) 112 | } 113 | func testAlamofireArrayUnwrapper() { 114 | let expectation = self.expectation(description: "Expected unwrapped array response") 115 | let handler:(Alamofire.DataResponse<[DecodableModel]>) -> Void = { result in 116 | switch result.result { 117 | case .success: 118 | expectation.fulfill() 119 | default: 120 | break 121 | } 122 | } 123 | let unwrapper: Parser.Unwrapper = { dict, _ in dict["data"] } 124 | 125 | if let url = urlForResource(resource: "NestedArrayTest") { 126 | manager.request(url, method: .get).responseSerializable(handler, unwrapper: unwrapper) 127 | } 128 | 129 | waitForExpectations(timeout: timeoutDuration, handler: nil) 130 | } 131 | 132 | func testAlamofireArrayNotUnwrapped() { 133 | let expectation = self.expectation(description: "Expected unwrapped array response") 134 | let handler:(Alamofire.DataResponse<[DecodableModel]>) -> Void = { result in 135 | switch result.result { 136 | case .failure: 137 | expectation.fulfill() 138 | default: 139 | break 140 | } 141 | } 142 | 143 | if let url = urlForResource(resource: "NestedArrayTest") { 144 | manager.request(url, method: .get).responseSerializable(handler) 145 | } 146 | waitForExpectations(timeout: timeoutDuration, handler: nil) 147 | } 148 | 149 | func testAlamofireWrongTypeUnwrapper() { 150 | let expectation = self.expectation(description: "Expected unwrapped array response") 151 | let handler:(Alamofire.DataResponse) -> Void = { result in 152 | switch result.result { 153 | case .success: 154 | expectation.fulfill() 155 | default: 156 | break 157 | } 158 | } 159 | let unwrapper: Parser.Unwrapper = { dict, _ in ["data"] } 160 | 161 | if let url = urlForResource(resource: "NestedArrayTest") { 162 | manager.request(url, method: .get).responseSerializable(handler, unwrapper: unwrapper) 163 | } 164 | waitForExpectations(timeout: timeoutDuration, handler: nil) 165 | } 166 | 167 | func testAlamofireExtensionNonSerializable() { 168 | let expectation = self.expectation(description: "Expected empty response") 169 | let handler:(Alamofire.DataResponse) -> Void = { result in 170 | switch result.result { 171 | case .failure: 172 | expectation.fulfill() 173 | default: 174 | break 175 | } 176 | } 177 | if let url = urlForResource(resource: "Empty") { 178 | manager.request(url, method: .get).responseSerializable(handler) 179 | } 180 | waitForExpectations(timeout: timeoutDuration, handler: nil) 181 | } 182 | 183 | func testAlamofireExtensionArrayRootObjectParsingToOne() { 184 | let expectation = self.expectation(description: "Expected valid object") 185 | let handler:(Alamofire.DataResponse) -> Void = { result in 186 | switch result.result { 187 | case .success(let value): 188 | XCTAssertEqual(value.id, 1) 189 | XCTAssertEqual(value.name, "Hello") 190 | expectation.fulfill() 191 | default: 192 | break 193 | } 194 | } 195 | if let url = urlForResource(resource: "ArrayTestOneObject") { 196 | manager.request(url, method: .get).responseSerializable(handler) 197 | } 198 | waitForExpectations(timeout: timeoutDuration, handler: nil) 199 | } 200 | 201 | func testAlamofireExtensionArrayRootObjectParsingToOneWithWrongUnwrapper() { 202 | let expectation = self.expectation(description: "Expected valid object") 203 | let handler:(Alamofire.DataResponse) -> Void = { result in 204 | switch result.result { 205 | case .success(let value): 206 | XCTAssertEqual(value.id, 1) 207 | XCTAssertEqual(value.name, "Hello") 208 | expectation.fulfill() 209 | default: 210 | break 211 | } 212 | } 213 | 214 | let unwrapper: Parser.Unwrapper = { dict, _ in ["nonexistent"] } 215 | 216 | if let url = urlForResource(resource: "ArrayTestOneObject") { 217 | manager.request(url, method: .get).responseSerializable(handler, 218 | unwrapper: unwrapper) 219 | } 220 | waitForExpectations(timeout: timeoutDuration, handler: nil) 221 | } 222 | 223 | func testAlamofireExtensionArrayRootObjectParsingToMultiple() { 224 | let expectation = self.expectation(description: "Expected valid array") 225 | let handler:(Alamofire.DataResponse<[DecodableModel]>) -> Void = { result in 226 | switch result.result { 227 | case .success(let values): 228 | XCTAssertEqual(values.count, 4) 229 | XCTAssertEqual(values[0].id, 1) 230 | XCTAssertEqual(values[0].name, "Hello") 231 | XCTAssertEqual(values[3].id, 4) 232 | XCTAssertEqual(values[3].name, "Hello4") 233 | expectation.fulfill() 234 | default: 235 | break 236 | } 237 | } 238 | if let url = urlForResource(resource: "ArrayTest") { 239 | manager.request(url, method: .get).responseSerializable(handler) 240 | } 241 | waitForExpectations(timeout: timeoutDuration, handler: nil) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/BridgingBoxTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BridgingBoxTests.swift 3 | // Serializable 4 | // 5 | // Created by Chris on 14/05/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Serpent 11 | 12 | class BridgingBoxTests: XCTestCase { 13 | 14 | var simpleModel: SimpleModel! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | 20 | do { 21 | if let path = Bundle(for: type(of: self)).path(forResource: "SimpleModel", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { 22 | let bridgedDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 23 | simpleModel = SimpleModel(dictionary: bridgedDictionary) 24 | } 25 | } catch { 26 | XCTAssert(false, "Failed to prepare bridged dictionary.") 27 | return 28 | } 29 | } 30 | 31 | func testBridgingBoxValue() { 32 | let box = BridgingBox(simpleModel) 33 | let value: SimpleModel? = box.value() 34 | XCTAssertNotNil(value, "Failed to load value from bridging box") 35 | XCTAssertEqual(value!.id, simpleModel.id, "Failed to load value from bridging box") 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/CashierExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CashierExtensionTests.swift 3 | // Serializable 4 | // 5 | // Created by Chris on 14/05/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Cashier 11 | import Serpent 12 | 13 | class CashierExtensionTests: XCTestCase { 14 | 15 | var simpleModel: SimpleModel! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | 21 | do { 22 | if let path = Bundle(for: type(of: self)).path(forResource: "SimpleModel", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { 23 | let bridgedDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 24 | simpleModel = SimpleModel(dictionary: bridgedDictionary) 25 | } 26 | } catch { 27 | XCTAssert(false, "Failed to prepare bridged dictionary.") 28 | return 29 | } 30 | } 31 | 32 | func testSerializableCache() { 33 | 34 | Cashier.defaultCache().setSerializable(simpleModel, forKey: "SimpleModel") 35 | let loadedModel: SimpleModel? = Cashier.defaultCache().serializableForKey("SimpleModel") 36 | 37 | XCTAssertNotNil(loadedModel, "Failed to load model from cache") 38 | XCTAssertEqual(simpleModel.id, loadedModel!.id, "Failed to load correct model from cache") 39 | } 40 | 41 | func testSerializableNilCache() { 42 | 43 | let loadedModel: SimpleModel? = Cashier.defaultCache().serializableForKey("NilSimpleModel") 44 | XCTAssertNil(loadedModel, "Failed to handle nil from cache") 45 | 46 | } 47 | 48 | func testSerializableNilArrayCache() { 49 | 50 | let loadedModel: [SimpleModel]? = Cashier.defaultCache().serializableForKey("NilSimpleModel") 51 | XCTAssertNil(loadedModel, "Failed to handle nil from cache") 52 | 53 | } 54 | 55 | func testSerializableArrayCache() { 56 | 57 | Cashier.defaultCache().setSerializable([simpleModel], forKey: "SimpleModel") 58 | let loadedModel: [SimpleModel]? = Cashier.defaultCache().serializableForKey("SimpleModel") 59 | 60 | XCTAssertNotNil(loadedModel, "Failed to load array model from cache") 61 | XCTAssertEqual(simpleModel.id, loadedModel![0].id, "Failed to load correct array model from cache") 62 | } 63 | 64 | func testClearSerializableCache() { 65 | Cashier.defaultCache().setSerializable(simpleModel, forKey: "SimpleModel") 66 | Cashier.defaultCache().clearAllData(true) 67 | 68 | let loadedModel: SimpleModel? = Cashier.defaultCache().serializableForKey("SimpleModel") 69 | XCTAssertNil(loadedModel, "Failed to clear serializables from cache") 70 | let loadedArrayModel: [SimpleModel]? = Cashier.defaultCache().serializableForKey("SimpleModel") 71 | XCTAssertNil(loadedArrayModel, "Failed to clear serializables from cache") 72 | } 73 | 74 | func testDeleteSerializableCache() { 75 | Cashier.defaultCache().setSerializable(simpleModel, forKey: "SimpleModelDelete") 76 | Cashier.defaultCache().deleteSerializableForKey("SimpleModelDelete") 77 | 78 | let loadedModel: SimpleModel? = Cashier.defaultCache().serializableForKey("SimpleModelDelete") 79 | XCTAssertNil(loadedModel, "Failed to delete model from cache") 80 | } 81 | 82 | #if TEST_MEMORY_WARNING 83 | func testLoadIgnoringMemCaches() { 84 | let box = BridgingBox(simpleModel) 85 | Cashier.defaultCache().setObject(box, forKey: "ManualBox") 86 | NotificationCenter.default.post(name: UIApplication.didReceiveMemoryWarningNotification, object: nil) // clear the Cashier mem cache 87 | let loadedModel: SimpleModel? = Cashier.defaultCache().serializableForKey("ManualBox") 88 | 89 | XCTAssertNotNil(loadedModel, "Failed to load model direct from cache") 90 | XCTAssertEqual(simpleModel.id, loadedModel!.id, "Failed to load correct model direct from cache") 91 | 92 | } 93 | #endif 94 | 95 | func testArrayLoadIgnoringMemCaches() { 96 | let box = BridgingBox(simpleModel) 97 | Cashier.defaultCache().setObject([box], forKey: "ManualBox") 98 | 99 | let loadedModel: [SimpleModel]? = Cashier.defaultCache().serializableForKey("ManualBox") 100 | 101 | XCTAssertNotNil(loadedModel, "Failed to load array model direct from cache") 102 | XCTAssertEqual(simpleModel.id, loadedModel![0].id, "Failed to load correct array model direct from cache") 103 | 104 | } 105 | 106 | func testInvalidObjectInBridgingBox() { 107 | Cashier.defaultCache().setSerializable(simpleModel, forKey: "SimpleModel") 108 | Cashier.defaultCache().deleteObject(forKey: "SimpleModel") 109 | let loadedModel: SimpleModel? = Cashier.defaultCache().serializableForKey("SimpleModel") 110 | 111 | XCTAssertNil(loadedModel, "Failed to return nil model from cache") 112 | } 113 | 114 | func testInvalidArrayObjectInBridgingBox() { 115 | Cashier.defaultCache().setSerializable([simpleModel], forKey: "SimpleModel") 116 | Cashier.defaultCache().deleteObject(forKey: "SimpleModel") 117 | let loadedModel: [SimpleModel]? = Cashier.defaultCache().serializableForKey("SimpleModel") 118 | 119 | XCTAssertNil(loadedModel, "Failed to return nil model from cache") 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/CustomOperatorsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOperatorsTests.swift 3 | // NOCore 4 | // 5 | // Created by Dominik Hádl on 04/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class CustomOperatorsTests: XCTestCase { 12 | 13 | var bridgedDictionary: NSDictionary! 14 | var testModel: CustomOperatorsTestModel! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | 20 | do { 21 | if let path = Bundle(for: type(of: self)).path(forResource: "CustomOperatorsTest", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { 22 | bridgedDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 23 | testModel = CustomOperatorsTestModel(dictionary: bridgedDictionary) 24 | } 25 | } catch { 26 | XCTAssert(false, "Failed to prepare bridged dictionary.") 27 | return 28 | } 29 | } 30 | 31 | func testCustomOperatorsDecode() { 32 | XCTAssertEqual(testModel.string, "success", "String parsing with custom operator failed.") 33 | XCTAssertEqual(testModel.secondString, "haha", "String parsing with custom operator failed.") 34 | XCTAssertNil(testModel.nilString, "Nil string parsing with custom operator failed.") 35 | XCTAssertNotNil(testModel.someDictionary, "Dictionary parsing with custom operator failed.") 36 | } 37 | 38 | func testCustomOperatorsEncode() { 39 | let rep = testModel.encodableRepresentation() as! NSDictionary 40 | let subArray = testModel.someArray 41 | 42 | XCTAssertEqual(rep.value(forKey: "string") as? String, "success", "String encode with custom operator failed.") 43 | XCTAssertEqual(rep.value(forKey: "second_string") as? String, "haha", "String encode with custom operator failed.") 44 | XCTAssertNil(rep.value(forKey: "nil_string"), "Nil string encode with custom operator failed.") 45 | 46 | if let type = rep.value(forKey: "some_enum") as? Int { 47 | XCTAssertEqual(Type(rawValue: type), .second, "Enum encode with custom operator failed.") 48 | } 49 | else { 50 | XCTFail("Enum encode with custom operator failed.") 51 | } 52 | XCTAssertEqual(CustomOperatorsTestNestedModel.array(rep.value(forKey: "some_array") as AnyObject?), subArray, "Array encode with custom operator failed.") 53 | } 54 | 55 | func testCustomOperatorNestedSerializable() { 56 | 57 | XCTAssertNotNil(testModel.otherSerializable, "Parsing a nested optional serializable failed.") 58 | 59 | XCTAssertEqual(testModel.otherSerializable?.optionalInteger, 2, "Optional integer parsing with custom operator in nested optional serializable failed.") 60 | XCTAssertEqual(testModel.otherSerializable?.optionalStringWithDefaultValue, "optional default success", "String (optional, w/ default val) parsing in nested optional serializable with custom operator failed.") 61 | XCTAssertNil(testModel.otherSerializable?.optionalDouble, "Optional double in nested optional serializable expected to be nil.") 62 | 63 | XCTAssertNotNil(testModel.someSerializable, "Parsing a nested serializable failed.") 64 | 65 | XCTAssertEqual(testModel.someSerializable.optionalInteger, 2, "Optional integer parsing with custom operator in nested serializable failed.") 66 | XCTAssertEqual(testModel.someSerializable.optionalStringWithDefaultValue, "optional default success", "String (optional, w/ default val) parsing in nested serializable with custom operator failed.") 67 | XCTAssertNil(testModel.someSerializable.optionalDouble, "Optional double in nested serializable expected to be nil.") 68 | } 69 | 70 | func testCustomOperatorStringInitializable() { 71 | XCTAssertEqual(testModel.someUrl, URL(string: "http://www.google.com"), "StringInitializable parsing failed in custom operator") 72 | } 73 | 74 | func testCustomOperatorDictionaryParsing() { 75 | XCTAssertEqual(testModel.someDictionary?["test"] as? String, "value", "Dictionary doesn't contain string value for key.") 76 | XCTAssertEqual(testModel.someDictionary?["integer"] as? Int, 1, "Dictionary doesn't contain integer value for key.") 77 | XCTAssertEqual(testModel.someDictionary?["float"] as? Double, 1.023, "Dictionary doesn't contain float value for key.") 78 | } 79 | 80 | } 81 | 82 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/DecodableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodableTests.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 16/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class DecodableTests: XCTestCase { 12 | 13 | var bridgedDictionary: NSDictionary! 14 | var testModels: [DecodableModel]! 15 | var testNonArrayModels: [DecodableModel]! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | do { 20 | if let path = Bundle(for: type(of: self)).path(forResource: "DecodableTest", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { 21 | bridgedDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 22 | testModels = DecodableModel.array(bridgedDictionary.object(forKey:"models")) 23 | testNonArrayModels = DecodableModel.array(bridgedDictionary.object(forKey:"model")) 24 | } 25 | } catch { 26 | XCTFail("Failed to prepare bridged dictionary.") 27 | return 28 | } 29 | } 30 | 31 | override func tearDown() { 32 | // Put teardown code here. This method is called after the invocation of each test method in the class. 33 | super.tearDown() 34 | } 35 | 36 | func testArray() { 37 | if testModels.count == 0 { 38 | XCTFail("Failed to parse array model.") 39 | return 40 | } 41 | XCTAssertEqual(testModels.count, 4, "Did not create correct number of models in array") 42 | XCTAssertEqual(testModels[0].id, 1, "First entity model parsing failed") 43 | XCTAssertEqual(testModels[1].id, 2, "Second entity model parsing failed") 44 | } 45 | 46 | func testNonArray() { 47 | XCTAssertNotNil(testNonArrayModels, "Create array from non-array JSON failed") 48 | XCTAssertEqual(testNonArrayModels.count, 0, "Create *empty* array from non-array JSON failed") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/HexInitalizableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HexInitalizableTests.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 10/03/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Serpent 11 | #if os(OSX) 12 | import Cocoa 13 | typealias HexColor = NSColor 14 | #else 15 | import UIKit 16 | typealias HexColor = UIColor 17 | #endif 18 | 19 | class HexInitalizableTests: XCTestCase { 20 | 21 | var testModel: HexInitializableTestModel! 22 | 23 | override func setUp() { 24 | super.setUp() 25 | do { 26 | if let path = Bundle(for: type(of: self)).path(forResource: "HexInitializableTest", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { 27 | let bridgedDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 28 | testModel = HexInitializableTestModel(dictionary: bridgedDictionary) 29 | } 30 | } catch { 31 | XCTFail("Failed to prepare bridged dictionary.") 32 | return 33 | } 34 | } 35 | 36 | func testHexInitializableUIColor() { 37 | let color = HexColor(red: CGFloat(85) / 255.0, green: CGFloat(170) / 255.0, blue: CGFloat(204) / 255.0, alpha: 1.0) 38 | let colorAlpha = HexColor(red: CGFloat(85) / 255.0, green: CGFloat(170) / 255.0, blue: CGFloat(204) / 255.0, alpha: 1.0).withAlphaComponent(0.8) 39 | XCTAssertEqual(testModel.shortColor, color, "Error parsing short form color in HexInitializable") 40 | XCTAssertEqual(testModel.fullColor, color, "Error parsing long form color in HexInitializable") 41 | XCTAssertEqual(testModel.threeColor, color, "Error parsing three hex form color in HexInitializable") 42 | XCTAssertEqual(testModel.alphaRGB, colorAlpha, "Error parsing three hex form color in HexInitializable") 43 | XCTAssertNil(testModel.badColor, "Error returning nil for malformed color hex") 44 | XCTAssertNil(testModel.notColor, "Error returning nil for non hex value") 45 | XCTAssertNil(testModel.invalidHexColor, "Error returning nil for invalid hex value") 46 | } 47 | 48 | func testHexInitializableNilDictionary() { 49 | let nilModel = HexInitializableTestNilModel(dictionary: nil) 50 | XCTAssertEqual(nilModel.someColor, HexColor.red, "Failed to parse nil dictionary into HexInitializable model") 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/SerializableEntityTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerializableEntityTests.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 16/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | 12 | class SerializableEntityTests: XCTestCase { 13 | 14 | var bridgedDictionary: NSDictionary! 15 | var testModel: SerializableEntityTestModel! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | do { 20 | if let path = Bundle(for: type(of: self)).path(forResource: "SerializableEntityTest", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { 21 | bridgedDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 22 | testModel = SerializableEntityTestModel(dictionary: bridgedDictionary) 23 | } 24 | } catch { 25 | XCTFail("Failed to prepare bridged dictionary.") 26 | return 27 | } 28 | 29 | } 30 | 31 | override func tearDown() { 32 | super.tearDown() 33 | } 34 | 35 | func testSerializableParsing() { 36 | XCTAssertNil(testModel.simple, "Failed to return nil when JSON contains array while struct expects Serializable") 37 | XCTAssertNotNil(testModel.simples, "Failed to parse array of Serializables") 38 | XCTAssertNotEqual(testModel.strings.count, 0, "Failed to parse array of primitives") 39 | XCTAssertEqual(testModel.ints.count, 0, "Failed to parse array of primitives") 40 | XCTAssertEqual(testModel.stringInts.count, 0, "Failed to create empty array from non dictionary serializable array type") 41 | XCTAssertEqual(testModel.stringDicts.count, 2, "Failed to create array of empty Serializables from wrong serializable array type") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/SerializableEnumsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerializablePrimitivesTests.swift 3 | // NOCore 4 | // 5 | // Created by Dominik Hádl on 17/01/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | 12 | // TODO: Add tests for explicit Int8, Int16, Int32, Int64 and all UInt variants, Float80 and UnicodeScalar 13 | 14 | class SerializableEnumsTests: XCTestCase { 15 | 16 | var bridgedDictionary: NSDictionary! 17 | var testModel: EnumsTestModel! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | 23 | do { 24 | if let path = Bundle(for: type(of: self)).path(forResource: "EnumsTest", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { 25 | bridgedDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 26 | testModel = EnumsTestModel(dictionary: bridgedDictionary) 27 | } 28 | } catch { 29 | XCTAssert(false, "Failed to prepare bridged dictionary.") 30 | return 31 | } 32 | } 33 | 34 | override func tearDown() { 35 | super.tearDown() 36 | } 37 | 38 | func testStringEnumParsing() { 39 | XCTAssertEqual(testModel.stringEnum, StringEnum.Value1, "String parsing test failed!") 40 | XCTAssertEqual(testModel.optionalStringEnum, StringEnum.DifferentValue, "Optional String parsing test failed!") 41 | XCTAssertEqual(testModel.optionalStringEnumWithDefaultValue, StringEnum.Value1, "Optional String with default value parsing test failed!") 42 | XCTAssertEqual(testModel.nonExistentStringEnum, nil, "Optional String with default value parsing test failed!") 43 | } 44 | 45 | func testStringEnumArrayParsing() { 46 | XCTAssertEqual(testModel.stringEnumArray, [StringEnum.Value1, StringEnum.DifferentValue], "String enum array test failed!") 47 | XCTAssertEqual(testModel.optionalStringEnumArray!, [StringEnum.Value1, StringEnum.DifferentValue], "Optional string enum array test failed!") 48 | XCTAssertEqual(testModel.optionalStringEnumArrayWithDefaultValue!, [StringEnum.Value1, StringEnum.DifferentValue], "Optional string enum array with default value test failed!") 49 | XCTAssertNil(testModel.nonExistentStringEnumArray, "Optional string enum array with non existent value test failed!") 50 | XCTAssertNil(testModel.wrongTypeStringEnumArray, "Wrong type string enum array test failed!") 51 | } 52 | 53 | func testDoubleEnumArrayParsing() { 54 | XCTAssertEqual(testModel.doubleEnumArray, [DoubleEnum.value1, DoubleEnum.differentValue], "Double enum array test failed!") 55 | XCTAssertEqual(testModel.optionalDoubleEnumArray!, [DoubleEnum.value1, DoubleEnum.differentValue], "Optional double enum array test failed!") 56 | XCTAssertEqual(testModel.optionalDoubleEnumArrayWithDefaultValue!, [DoubleEnum.value1, DoubleEnum.differentValue], "Optional double enum array with default value test failed!") 57 | XCTAssertNil(testModel.nonExistentDoubleEnumArray, "Optional double enum array with non existent value test failed!") 58 | XCTAssertNil(testModel.wrongTypeDoubleEnumArray,"Wrong type double enum array test failed!") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/SerializableNilEntitiesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerializableNilEntitiesTests.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 16/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | 12 | 13 | class SerializableNilEntitiesTests: XCTestCase { 14 | var testModel: NilModel! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | testModel = NilModel(dictionary: nil) 19 | } 20 | 21 | func testNilPrimitives() { 22 | XCTAssertEqual(testModel.id, 0, "Guard statement failed for primitive with nil Dictionary") 23 | XCTAssertNil(testModel.optionalId, "Guard statement failed for optional primitive with nil Dictionary") 24 | XCTAssertNotNil(testModel.somePrimitiveArray, "Guard statement failed for Primitive array with nil dictionary") 25 | XCTAssertEqual(testModel.somePrimitiveArray.count, 0, "Guard statement failed for Primitive array with nil dictionary") 26 | XCTAssertNil(testModel.optionalPrimitiveArray, "Guard statement failed for optional Primitive array with nil dictionary") 27 | } 28 | 29 | func testNilSerializables() { 30 | XCTAssertEqual(testModel.name.id, SimpleModel().id, "Guard statement failed for Serializable with nil Dictionary") 31 | XCTAssertNotNil(testModel.names, "Guard statement failed for Serializable array with nil Dictionary") 32 | XCTAssertEqual(testModel.names.count, 0, "Guard statement failed for Serializable array with nil Dictionary") 33 | XCTAssertNil(testModel.optionalName, "Guard statement failed for optional Serializable with nil Dictionary") 34 | XCTAssertNil(testModel.optionalNames, "Guard statement failed for optional Serializable array with nil Dictionary") 35 | 36 | } 37 | 38 | func testNilEnums() { 39 | XCTAssertEqual(testModel.someEnum.rawValue, 0, "Guard statement failed for Enum with nil dictionary") 40 | XCTAssertNotNil(testModel.someEnumArray, "Guard statement failed for Enum array with nil dictionary") 41 | XCTAssertEqual(testModel.someEnumArray.count, 0, "Guard statement failed for Enum array with nil dictionary") 42 | XCTAssertNil(testModel.optionalEnum, "Guard statement failed for optional Enum with nil dictionary") 43 | XCTAssertNil(testModel.optionalEnumArray, "Guard statement failed for optional Enum array with nil dictionary") 44 | } 45 | 46 | func testNilStringInitializables() { 47 | XCTAssertEqual(testModel.url, URL(string: "http://www.google.com"), "Guard statement failed for StringInitializable with nil dictionary") 48 | XCTAssertNil(testModel.optionalUrl, "Guard statement failed for optional StringInitializable with nil dictionary") 49 | } 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/SerializablePerformanceTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerializablePerformanceTest.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 25/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SerializablePerformanceTest: XCTestCase { 12 | 13 | var largeData: NSData! 14 | var smallData: NSData! 15 | 16 | var jsonDict: NSDictionary! 17 | var smallJsonDict: NSDictionary! 18 | 19 | 20 | override func setUp() { 21 | super.setUp() 22 | if let path = Bundle(for: type(of: self)).path(forResource: "PerformanceTest", ofType: "json"), let data = NSData(contentsOfFile: path) { 23 | largeData = data 24 | } 25 | if let path = Bundle(for: type(of: self)).path(forResource: "PerformanceSmallTest", ofType: "json"), let data = NSData(contentsOfFile: path) { 26 | smallData = data 27 | } 28 | } 29 | 30 | 31 | func testBigPerformance() { 32 | self.measure { () -> Void in 33 | do { 34 | self.jsonDict = try JSONSerialization.jsonObject(with: self.largeData as Data, options: .allowFragments) as? NSDictionary 35 | let _ = PerformanceTestModel.array(self.jsonDict.object(forKey: "data")) 36 | } 37 | catch { 38 | print(error) 39 | } 40 | } 41 | } 42 | 43 | func testSmallPerformance() { 44 | self.measure { 45 | do { 46 | self.smallJsonDict = try JSONSerialization.jsonObject(with: self.smallData as Data, options: .allowFragments) as? NSDictionary 47 | let _ = PerformanceTestSmallModel.array(self.smallJsonDict.object(forKey: "data")) 48 | } 49 | catch { 50 | print(error) 51 | } 52 | } 53 | } 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/SerializablePrimitivesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerializablePrimitivesTests.swift 3 | // NOCore 4 | // 5 | // Created by Dominik Hádl on 17/01/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | 12 | // TODO: Add tests for explicit Int8, Int16, Int32, Int64 and all UInt variants, Float80 and UnicodeScalar 13 | 14 | class SerializablePrimitivesTests: XCTestCase { 15 | 16 | var bridgedDictionary: NSDictionary! 17 | var testModel: PrimitivesTestModel! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | 23 | do { 24 | if let path = Bundle(for: type(of: self)).path(forResource: "PrimitivesTest", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { 25 | bridgedDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 26 | testModel = PrimitivesTestModel(dictionary: bridgedDictionary) 27 | } 28 | } catch { 29 | XCTAssert(false, "Failed to prepare bridged dictionary.") 30 | return 31 | } 32 | } 33 | 34 | override func tearDown() { 35 | // Put teardown code here. This method is called after the invocation of each test method in the class. 36 | super.tearDown() 37 | } 38 | 39 | func testIntegerParsing() { 40 | XCTAssertEqual(testModel.integer, 1, "Integer parsing test failed!") 41 | XCTAssertEqual(testModel.optionalInteger, 2, "Optional integer parsing test failed!") 42 | XCTAssertEqual(testModel.optionalIntegerWithDefaultValue, 3, "Optional integer with default value parsing test failed!") 43 | XCTAssertEqual(testModel.intString, 1, "Integer parsing from string failed") 44 | } 45 | 46 | func testNegativeIntegerParsing() { 47 | XCTAssertEqual(testModel.negativeInteger, -10, "Negative integer parsing test failed!") 48 | XCTAssertEqual(testModel.optionalNegativeInteger, -20, "Optional negative integer parsing test failed!") 49 | XCTAssertEqual(testModel.optionalNegativeIntegerWithDefaultValue, -30, "Optional negative integer with default value parsing test failed!") 50 | } 51 | 52 | func testDoubleParsing() { 53 | XCTAssertEqual(testModel.double, 123.1234567, "Double parsing test failed!") 54 | XCTAssertEqual(testModel.optionalDouble, 234.2345678, "Optional double parsing test failed!") 55 | XCTAssertEqual(testModel.optionalDoubleWithDefaultValue, 345.3456789, "Optional double with default value parsing test failed!") 56 | XCTAssertEqual(testModel.doubleString, 1.5, "Double parsing from string failed") 57 | } 58 | 59 | func testFloatParsing() { 60 | XCTAssertEqual(testModel.float, 1.234, "Float parsing test failed!") 61 | XCTAssertEqual(testModel.optionalFloat, 2.345, "Optional float parsing test failed!") 62 | XCTAssertEqual(testModel.optionalFloatWithDefaultValue, 3.456, "Optional float with default value parsing test failed!") 63 | } 64 | 65 | func testBoolParsing() { 66 | XCTAssertTrue(testModel.bool, "Bool parsing test failed!") 67 | XCTAssertTrue(testModel.optionalBool ?? false, "Optional bool parsing test failed!") 68 | XCTAssertTrue(testModel.optionalBoolWithDefaultValue ?? false, "Optional bool with default value parsing test failed!") 69 | XCTAssertEqual(testModel.boolString, true, "Bool parsing from string failed") 70 | XCTAssertEqual(testModel.boolIntString, true, "Bool parsing from integer string failed") 71 | } 72 | 73 | func testCharParsing() { 74 | XCTAssertEqual(testModel.char, "S", "Character parsing test failed!") 75 | XCTAssertEqual(testModel.optionalChar, "O", "Optional character parsing test failed!") 76 | XCTAssertEqual(testModel.optionalCharWithDefaultValue, "D", "Optional character with default value parsing test failed!") 77 | } 78 | 79 | func testStringParsing() { 80 | XCTAssertEqual(testModel.string, "success", "String parsing test failed!") 81 | XCTAssertEqual(testModel.optionalString, "optional success", "Optional string parsing test failed!") 82 | XCTAssertEqual(testModel.optionalStringWithDefaultValue, "optional default success", "Optional string with default value parsing test failed!") 83 | XCTAssertEqual(testModel.stringDouble, "1.5", "String parsing from double failed") 84 | XCTAssertEqual(testModel.stringBool, "1", "String parsing from bool failed") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Serpent/SerpentTests/Tests/StringInitializableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringInitializableTests.swift 3 | // Serializable 4 | // 5 | // Created by Chris Combs on 17/02/16. 6 | // Copyright © 2016 Nodes. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Serpent 11 | 12 | class StringInitializableTests: XCTestCase { 13 | var testModel: StringInitializableTestModel! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | do { 18 | if let path = Bundle(for: type(of: self)).path(forResource: "StringInitializableTest", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { 19 | Date.customDateFormats = ["yyyy-MM-dd HH:mm:ss"] 20 | let bridgedDictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary 21 | testModel = StringInitializableTestModel(dictionary: bridgedDictionary) 22 | } 23 | } catch { 24 | XCTFail("Failed to prepare bridged dictionary.") 25 | return 26 | } 27 | } 28 | 29 | override func tearDown() { 30 | super.tearDown() 31 | Date.customDateFormats = [] 32 | } 33 | 34 | func testNSURL() { 35 | XCTAssertNotNil(testModel.someUrl, "Failed to create NSURL from string") 36 | XCTAssertEqual(testModel.someUrl, URL(string: "http://www.google.com"), "Failed to parse URL") 37 | XCTAssertNil(testModel.someEmptyURL, "Failed to return nil when parsing empty string") 38 | XCTAssertEqual(testModel.someUrl?.stringRepresentation(), "http://www.google.com", "String representation of URL differs.") 39 | } 40 | 41 | func testNSDate() { 42 | XCTAssertEqual(testModel.someDate, Date(timeIntervalSince1970: 145811834), "Failed to parse NSDate") 43 | XCTAssertNil(testModel.someEmptyDate, "Failed to return nil when parsing empty date") 44 | XCTAssertNil(testModel.someBadDate, "Failed to return nil when parsing bad date") 45 | XCTAssertEqual(Date.fromString(testModel.someDate?.stringRepresentation() ?? ""), testModel.someDate, "String representation of URL differs.") 46 | XCTAssertEqual(Date.fromString(testModel.someCustomDate?.stringRepresentation() ?? ""), testModel.someCustomDate, "String representation of custom URL differs.") 47 | } 48 | 49 | func testNSURLEncoding() { 50 | guard let encodedModel = testModel.encodableRepresentation() as? NSDictionary else { XCTFail("encodableRepresentation() Failed"); return } 51 | 52 | if let someUrl = encodedModel["some_url"] as? String { 53 | XCTAssertEqual(someUrl, "http://www.google.com", "Failed to encode NSURL to String") 54 | } else { 55 | XCTFail("Failed to encode NSURL to String") 56 | } 57 | } 58 | 59 | func testNSDateEncoding() { 60 | guard let encodedModel = testModel.encodableRepresentation() as? NSDictionary else { XCTFail("encodableRepresentation() Failed"); return } 61 | 62 | if let someDateString = encodedModel["some_date"] as? String { 63 | let someDate = DateFormatter().date(from: someDateString) 64 | XCTAssertEqual(someDate, DateFormatter().date(from: "1974-08-15T15:17:14+00:00"), "Failed to encode NSDate to String") 65 | } else { 66 | XCTFail("Failed to encode NSDate to String") 67 | } 68 | } 69 | 70 | func testCustomNSDateEncoding() { 71 | guard let encodedModel = testModel.encodableRepresentation() as? NSDictionary else { XCTFail("encodableRepresentation() Failed"); return } 72 | 73 | if let someDateString = encodedModel["some_custom_date"] as? String { 74 | let someDate = DateFormatter().date(from: someDateString) 75 | XCTAssertEqual(someDate, DateFormatter().date(from: "2016-11-15 17:19:04"), "Failed to encode NSDate to String") 76 | } else { 77 | XCTFail("Failed to encode NSDate to String") 78 | } 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /Serpent_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Serpent/212d6d237670cdba48489c27bc67a89da3cbb9b4/Serpent_icon.png -------------------------------------------------------------------------------- /Sources/Serpent/BridgingBox.swift: -------------------------------------------------------------------------------- 1 | ../../Serpent/Serpent/Classes/Other/BridgingBox.swift -------------------------------------------------------------------------------- /Sources/Serpent/Extensions.swift: -------------------------------------------------------------------------------- 1 | ../../Serpent/Serpent/Classes/Extensions/Extensions.swift -------------------------------------------------------------------------------- /Sources/Serpent/Operator.swift: -------------------------------------------------------------------------------- 1 | ../../Serpent/Serpent/Classes/Operators/Operator.swift -------------------------------------------------------------------------------- /Sources/Serpent/Serpent.swift: -------------------------------------------------------------------------------- 1 | ../../Serpent/Serpent/Classes/Serpent.swift -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: false 4 | ignore: 5 | - "Serpent/SerpentTests/.*" 6 | --------------------------------------------------------------------------------