Giter Club home page Giter Club logo

serpent's Introduction

This library has been deprecated and the repo has been archived.

The code is still here and you can still clone it, however the library will not receive any more updates or support.

Serpent

CircleCI Codecov codebeat badge Carthage Compatible CocoaPods
Plaforms GitHub license

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 ModelBoiler Model Boiler, making model creation a breeze.

Serpent is implemented using protocol extensions and static typing.

๐Ÿ“‘ Table of Contents

๐Ÿ Why Serpent?

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!
  • ModelBoiler Model Boiler. Every framework of this kind requires tedious boilerplate code that takes forever to write. ModelBoiler 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.

๐Ÿ“ Requirements

  • 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)

๐Ÿ“ฆ Installation

Carthage

github "nodes-ios/Serpent" ~> 1.0

Last versions compatible with lower Swift versions:

Swift 2.3
github "nodes-ios/Serpent" == 0.13.2

Swift 2.2
github "nodes-ios/Serpent" == 0.11.2

NOTE: Serpent was previously known as Serializable.

CocoaPods

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 extension

NOTE: CocoaPods only supports Serpent using Swift version 3.0 and higher.

Swift Package Manager

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)
    ]
)

๐Ÿ”ง Setup

We highly recommend you use our ModelBoiler Model Boiler 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.

๐Ÿ’ป Usage

Getting started

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.

Create your model struct or class:

struct Foo {
	var id = 0
	var name = ""
	var address: String?
}

NOTE: Classes must be marked final.

Add the required methods for Encodable and Decodable:

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 Serializable which is a type alias for both Encodable and Decodable.

And thats it! If you're using the ModelBoiler Model Boiler, this extension will be generated for you, so that you don't need to type it all out for every model you have.

Using Serpent models

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()

More complex examples

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 ModelBoiler Model Boiler generates all of this code for you in less than a second!

Using with Alamofire

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.

Date parsing

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 here

The custom date formats won't replace the default ones, they will be still supported.

๐Ÿ‘ฅ Credits

Made with โค๏ธ at Nodes.

๐Ÿ“„ License

Serpent is available under the MIT license. See the LICENSE file for more info.

serpent's People

Contributors

bucekjiri avatar cglarsen avatar chriscombs avatar kasperwelner avatar mariusc avatar narciszait avatar nickskull avatar pbodsk avatar ppacie avatar readmecritic avatar tc-sgupta avatar tobrnodes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

serpent's Issues

Allow the developer to fail the request in implementation even if it returned 200

There are some bad APIs that return 200, but in the response JSON they return a status code that indicates an error.

image

It would be nice to have a way of manually checking for that and go to failure if necessary. In the example above, if ReturnCode is different than 0, the Failure case would be called and not the Success one (in the current implementation, it goes to Success).

Maybe returning nil in the Unwrapper should make it go to Failure instead of Success with the empty model.

Linux support

Currently, we do not support Linux but it would not require many changes to do so and might be a vital addition to help us when doing server side Swift and also to keep up with the competition.

Things that need to be changed to support Linux:

  • get rid of Bridging Box on Linux
  • get rid of UIKit related extensions (UIColor)
  • make NSDictionary's setValue function available on Linux
  • make tests compile and run on Linux properly

Remove static defaultUnwrapper on Parser

This will force people to specify an unwrapper manually and prevent issues with a global "magic" unwrapper.

To imagine an example where this is problematic take Module A, where responseSerializable(completion) is called with no unwrapper and expects to receive whatever was passed in. Then Module B changes the Parser.defaultUnwrapper to something needed for itself. Module A does same action with the responseSerializable and now fails because the Parser was changed without it's knowledge.

Only workaround this issue for now was to manully specify the unwrapper everywhere, which in turn eliminates the need for the global unwrapper in the first place. This is a great example of why you should not use a global variable.

One final master/dev tag and make swift-3.0 new master

I'm not sure if we're ready to break everyone's cartfiles yet, but we should coordinate across the other frameworks as well.

The master/dev branches do not have any features that swift-3.0 doesn't; the most recent commits to those are just updating to swift 2.3 and fixing travis. swift-3.0 has newer fixes and updates.

Encoding optional NSURL

When using Cashier the optional NSURL was not being decoded as NSRL even it was there before encoding after serialising from JSON.

Readme update

  • mention that it was previously known as Serializable
  • update the requirements to mention macOS/tvOS/watchOS
  • update the code examples in the README to be Swift 3
  • maybe mention older versions support in requirements (like in Installation - Carthage)

New comment style, indentation issues

Serpent is still using the old comment style, which is no longer recommended by Apple, so we should switch to the new standard (/// on each line).

There's also some indentation issues in the project, which should be fixed. (maybe we could implement SwiftLint?)

Improve code documentation

I ran Jazzy locally on Serpent, and it looks like the documentation percentage is not very high. This is about documentation for functions that the users don't actually access, but still, for completeness and quality, we should document those too.

Fix travis

Will get back to this in a bit, leaving it as a reminder

Provide a way to override the default `serializer`

Right now the Alamofire Extension uses a serializer that we hardcoded to only work with JSON. If we can provide a custom serializer (or even just a custom ResponseSerializerType) then we can easily support XML or anything we want. As it is now, we have to copy-paste a lot of code into custom extensions to support XML

Update podspec

CocoaPods still sees Serpent as 1.0.1, because we haven't updated the podspec since.

Feature request to mark properties as required

The basic idea is that on the model level, you can mark one or more properties to be required so if in the incoming raw response they are not present stop the decoding and return empty struct/dictionary.

Rename to Serpent

This will hopefully make the framework name more unique and won't be as confusing.

JSONSerialization Error

Hi,

Im using Serpent to save a Struct as JSON, when I try to convert the JSON back to the struct I get this error:

"Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.

Im using this to get the JSON:

let json = myStruct.encodableRepresentation()

And I'm using this to Convert the JSON back to a struct:

let jsonData = NSKeyedArchiver.archivedData(withRootObject: json) let dictionary = try! JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? NSDictionary

Where am I going wrong??

Merge performance tests, move comparison tests to separate repository

All Serializable related performance tests should be merged into develop and master.

Tests for comparing other frameworks against Serializable should be moved to a different repository, so that this one isn't cluttered.


This will also make sure that performance tests are run in each travis build, which should make it easier for us to spot regressions or improvements.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.