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.
It's designed to be used together with our helper app, the 
Serpent is implemented using protocol extensions and static typing.
There are plenty of other Encoding and Decoding frameworks available. Why should you use Serpent?
- Performance. Serpent is fast, up to 4x faster than similar frameworks.
- Features. Serpent can parse anything you throw at it. Nested objects, Enums, URLs, UIColor, you name it!
Model Boiler. Every framework of this kind requires tedious boilerplate code that takes forever to write.
Model Boiler generates it for you instantly.
- Alamofire Integration. Using the included Alamofire extensions makes implementing an API call returning parsed model data as simple as doing a one-liner!
- Expandability. Parsing into other datatypes can easily be added.
- Persisting. Combined with our caching framework Cashier, Serpent objects can be very easily persisted to disk.
Serpent Xcode File Template makes it easier to create the model files in Xcode.
- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
- Swift 3.0+
(Swift 2.2 & Swift 2.3 supported in older versions)
github "nodes-ios/Serpent" ~> 1.0Last versions compatible with lower Swift versions:
Swift 2.3
github "nodes-ios/Serpent" == 0.13.2Swift 2.2
github "nodes-ios/Serpent" == 0.11.2NOTE: Serpent was previously known as Serializable.
Choose one of the following, add it to your Podfile and run pod install:
pod 'Serpent', '~> 1.0' # Just core
pod 'Serpent/Extensions', '~> 1.0' # Includes core and all extensions
pod 'Serpent/AlamofireExtension', '~> 1.0' # Includes core and Alamofire extension
pod 'Serpent/CashierExtension', '~> 1.0' # Includes core and Cashier extensionNOTE: CocoaPods only supports Serpent using Swift version 3.0 and higher.
To use Serpent as a Swift Package Manager package just add the following to your Package.swift file.
import PackageDescription
let package = Package(
name: "YourPackage",
dependencies: [
.Package(url: "https://github.com/nodes-ios/Serpent.git", majorVersion: 1)
]
)We highly recommend you use our 
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.
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.
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.
struct Foo {
var id = 0
var name = ""
var address: String?
}NOTE: Classes must be marked
final.
extension Foo: Serializable {
init(dictionary: NSDictionary?) {
id <== (self, dictionary, "id")
name <== (self, dictionary, "name")
address <== (self, dictionary, "address")
}
func encodableRepresentation() -> NSCoding {
let dict = NSMutableDictionary()
(dict, "id") <== id
(dict, "name") <== name
(dict, "address") <== address
return dict
}
}NOTE: You can add conformance to
Serializablewhich is a type alias for bothEncodableandDecodable.
And thats it! If you're using the 
New instances of your model can be created with a dictionary, e.g. from parsed JSON.
let dictionary = try JSONSerialization.jsonObject(with: someData, options: .allowFragments) as? NSDictionary
let newModel = Foo(dictionary: dictionary)You can generate a dictionary version of your model by calling encodableRepresentation():
let encodedDictionary = newModel.encodableRepresentation()In this example, we have two models, Student and School.
struct Student {
enum Gender: String {
case male = "male"
case female = "female"
case unspecified = "unspecified"
}
var name = ""
var age: Int = 0
var gender: Gender?
}
struct School {
enum Sport: Int {
case football
case basketball
case tennis
case swimming
}
var name = ""
var location = ""
var website: URL?
var students: [Student] = []
var sports: [Sport]?
}You can get as complicated as you like, and the syntax will always remain the same. The extensions will be:
extension Student: Serializable {
init(dictionary: NSDictionary?) {
name <== (self, dictionary, "name")
age <== (self, dictionary, "age")
gender <== (self, dictionary, "gender")
}
func encodableRepresentation() -> NSCoding {
let dict = NSMutableDictionary()
(dict, "name") <== name
(dict, "age") <== age
(dict, "gender") <== gender
return dict
}
}
extension School: Serializable {
init(dictionary: NSDictionary?) {
name <== (self, dictionary, "name")
location <== (self, dictionary, "location")
website <== (self, dictionary, "website")
students <== (self, dictionary, "students")
sports <== (self, dictionary, "sports")
}
func encodableRepresentation() -> NSCoding {
let dict = NSMutableDictionary()
(dict, "name") <== name
(dict, "location") <== location
(dict, "website") <== website
(dict, "students") <== students
(dict, "sports") <== sports
return dict
}
}Again, the 
Serpent comes integrated with Alamofire out of the box, through an extension on Alamofire's Request construct, that adds the function responseSerializable(completion:unwrapper)
The extension uses Alamofire's familiar Response type to hold the returned data, and uses its generic associated type to automatically parse the data.
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:
func requestSchool(completion: @escaping (DataResponse<School>) -> Void) {
request("http://somewhere.com/school/1", method: .get).responseSerializable(completion)
}In the consuming method you use it like this:
requestSchool() { (response) in
switch response.result {
case .success(let school):
//Use your new school object!
case .failure(let error):
//Handle the error object, or check your Response for more detail
}
}For an array of objects, use the same technique:
static func requestStudents(completion: @escaping (DataResponse<[Student]>) -> Void) {
request("http://somewhere.com/school/1/students", method: .get).responseSerializable(completion)
}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:
{
"students" : [
{
"..." : "..."
},
{
"..." : "..."
}
]
}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.
static func requestStudents(completion: (DataResponse<[Student]>) -> Void) {
request("http://somewhere.com/school/1/students", method: .get).responseSerializable(completion, unwrapper: { $0.0["students"] })
}If you need to unwrap the response data in every call, you can install a default unwrapper using
Parser.defaultWrapper = { sourceDictionary, expectedType in
// You custom unwrapper here...
return sourceDictionary
}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.
See here for an example on how we use this in our projects at Nodes.
NOTE: responseSerializable Internally calls validate().responseJSON() on the request, so you don't have to do that.
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::
Date.customDateFormats = ["yyyyMMddHHmm", "yyyyMMdd"] // add the custom date formats you need hereThe custom date formats won't replace the default ones, they will be still supported.
Made with ❤️ at Nodes.
Serpent is available under the MIT license. See the LICENSE file for more info.