Json开源:Serpent - 基于协议的对象和结构的正、反向序列化 JSON 及转换库

yangjiabi 7年前
   <p style="text-align: center;"><img src="https://simg.open-open.com/show/771759396e0d3fa5f11b5fb89c2f3d93.png"></p>    <p> </p>    <p>Serpent <em>(previously known as Serializable)</em> 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.</p>    <p>It's designed to be used together with our helper app, the Model Boiler , making model creation a breeze.</p>    <p>Serpent is implemented using protocol extensions and static typing.</p>    <h2>:bookmark_tabs: Table of Contents</h2>    <ul>     <li>:snake: Why Serpent?</li>     <li>:memo: Requirements</li>     <li>:package: Installation      <ul>       <li>Carthage</li>       <li>CocoaPods</li>       <li>Swift Package Manager</li>      </ul> </li>     <li>:wrench: Setup</li>     <li>:computer: Usage      <ul>       <li>Getting started</li>       <li>Using Serpent models</li>       <li>More complex examples</li>       <li>Using with Alamofire</li>       <li>Date parsing</li>      </ul> </li>     <li>:busts_in_silhouette: Credits</li>     <li>:page_facing_up: License</li>    </ul>    <h2>:snake: Why Serpent?</h2>    <p>There are plenty of other Encoding and Decoding frameworks available. Why should you use Serpent?</p>    <ul>     <li><a href="/misc/goto?guid=4959742082002395616" rel="nofollow,noindex">Performance</a> . Serpent is fast, up to 4x faster than similar frameworks.</li>     <li><a href="/misc/goto?guid=4959742082092139471" rel="nofollow,noindex">Features</a> . Serpent can parse anything you throw at it. Nested objects, Enums, URLs, UIColor, you name it!</li>     <li><a href="/misc/goto?guid=4959742082174621684" rel="nofollow,noindex">Model Boiler</a> . Every framework of this kind requires tedious boilerplate code that takes forever to write. <a href="/misc/goto?guid=4959742082174621684" rel="nofollow,noindex">Model Boiler</a> generates it for you instantly.</li>     <li><a href="/misc/goto?guid=4959742082279472927" rel="nofollow,noindex">Alamofire Integration</a> . Using the included Alamofire extensions makes implementing an API call returning parsed model data as simple as doing a one-liner!</li>     <li><a href="/misc/goto?guid=4959742082279472927" rel="nofollow,noindex">Expandability</a> . Parsing into other datatypes can easily be added.</li>     <li><a href="/misc/goto?guid=4959742082279472927" rel="nofollow,noindex">Persisting</a> . Combined with our caching framework <a href="/misc/goto?guid=4959742082378809829" rel="nofollow,noindex">Cashier</a> , Serpent objects can be very easily persisted to disk.</li>     <li><img src="https://simg.open-open.com/show/bd0ee0d3195a596ce524c4dc9486446b.png"> makes it easier to create the model files in Xcode.</li>    </ul>    <h2>:memo: Requirements</h2>    <ul>     <li>iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+</li>     <li>Swift 3.0+<br> <em>(Swift 2.2 & Swift 2.3 supported in older versions)</em></li>    </ul>    <h2>:package: Installation</h2>    <h3>Carthage</h3>    <pre>  github "nodes-ios/Serpent" ~> 1.0</pre>    <p>Last versions compatible with lower Swift versions:</p>    <p>Swift 2.3</p>    <p>github "nodes-ios/Serpent" == 0.13.2</p>    <p>Swift 2.2</p>    <p>github "nodes-ios/Serpent" == 0.11.2</p>    <p>NOTE:Serpent was previously known as <strong>Serializable</strong> .</p>    <h3>CocoaPods</h3>    <p>Choose one of the following, add it to your Podfile and run pod install :</p>    <pre>  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</pre>    <p>NOTE:CocoaPods only supports Serpent using Swift version 3.0 and higher.</p>    <h3>Swift Package Manager</h3>    <p>To use Serpent as a <a href="/misc/goto?guid=4958973377050938320" rel="nofollow,noindex">Swift Package Manager</a> package just add the following to your Package.swift file.</p>    <pre>  import PackageDescription    let package = Package(      name: "YourPackage",      dependencies: [          .Package(url: "https://github.com/nodes-ios/Serpent.git", majorVersion: 1)      ]  )</pre>    <h2>:wrench: Setup</h2>    <p>We <strong>highly</strong> recommend you use our <a href="/misc/goto?guid=4959742082174621684" rel="nofollow,noindex">Model Boiler</a> to assist with generating the code needed to conform to Serpent. Instructions for installation and usage can be found at the <a href="/misc/goto?guid=4959742082174621684" rel="nofollow,noindex">Model Boiler GitHub repository</a> .</p>    <h2>:computer: Usage</h2>    <h3>Getting started</h3>    <p>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.</p>    <p>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.</p>    <p>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.</p>    <p>Create your model struct or class:</p>    <pre>  struct Foo {      var id = 0      var name = ""      var address: String?  }</pre>    <p>NOTE:Classes must be marked final .</p>    <p>Add the required methods for Encodable and Decodable :</p>    <pre>  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      }  }</pre>    <p>NOTE:You can add conformance to Serializable which is a type alias for both Encodable and Decodable .</p>    <p>And thats it! If you're using the 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.</p>    <h3>Using Serpent models</h3>    <p>New instances of your model can be created with a dictionary, e.g. from parsed JSON.</p>    <pre>  let dictionary = try JSONSerialization.jsonObject(with: someData, options: .allowFragments) as? NSDictionary  let newModel = Foo(dictionary: dictionary)</pre>    <p>You can generate a dictionary version of your model by calling encodableRepresentation() :</p>    <pre>  let encodedDictionary = newModel.encodableRepresentation()</pre>    <h3>More complex examples</h3>    <p>In this example, we have two models, Student and School.</p>    <pre>  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]?  }</pre>    <p>You can get as complicated as you like, and the syntax will always remain the same. The extensions will be:</p>    <pre>  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      }  }</pre>    <p>Again, the <a href="/misc/goto?guid=4959742082174621684" rel="nofollow,noindex">Model Boiler</a> generates all of this code for you in less than a second!</p>    <h3>Using with Alamofire</h3>    <p>Serpent comes integrated with Alamofire out of the box, through an extension on Alamofire's Request construct, that adds the function responseSerializable(completion:unwrapper)</p>    <p>The extension uses Alamofire's familiar Response type to hold the returned data, and uses its generic associated type to automatically parse the data.</p>    <p>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:</p>    <pre>  func requestSchool(completion: @escaping (DataResponse<School>) -> Void) {      request("http://somewhere.com/school/1", method: .get).responseSerializable(completion)  }</pre>    <p>In the consuming method you use it like this:</p>    <pre>  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      }  }</pre>    <p>For an array of objects, use the same technique:</p>    <pre>  static func requestStudents(completion: @escaping (DataResponse<[Student]>) -> Void) {      request("http://somewhere.com/school/1/students", method: .get).responseSerializable(completion)  }</pre>    <p>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:</p>    <pre>  {      "students" : [          {              "..." : "..."          },          {              "..." : "..."          }      ]  }</pre>    <p>The unwrapper closure has 2 input arguments: The sourceDictionary (the JSON Response Dictionary) and the expectedType (the <em>type</em> of the target Serpent). Return the object that will serve as the input for the Serializable initializer.</p>    <pre>  static func requestStudents(completion: (DataResponse<[Student]>) -> Void) {      request("http://somewhere.com/school/1/students", method: .get).responseSerializable(completion, unwrapper: { $0.0["students"] })  }</pre>    <p>If you need to unwrap the response data in every call, you can install a default unwrapper using</p>    <pre>  Parser.defaultWrapper = { sourceDictionary, expectedType in       // You custom unwrapper here...       return sourceDictionary  }</pre>    <p>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.</p>    <p>See here for an example on how we use this in our projects at Nodes.</p>    <p><em>NOTE:</em> responseSerializable Internally calls validate().responseJSON() on the request, so you don't have to do that.</p>    <h3>Date parsing</h3>    <p>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: :</p>    <pre>  Date.customDateFormats = ["yyyyMMddHHmm", "yyyyMMdd"]    // add the custom date formats you need here</pre>    <p>The custom date formats won't replace the default ones, they will be still supported.</p>    <h2>:busts_in_silhouette: Credits</h2>    <p>Made with :heart: at <a href="/misc/goto?guid=4959742082528885017" rel="nofollow,noindex">Nodes</a> .</p>    <h2>:page_facing_up: License</h2>    <p>Serpentis available under the MIT license. See the <a href="/misc/goto?guid=4959742082615421869" rel="nofollow,noindex">LICENSE</a> file for more info.</p>    <p> </p>    <p> </p>    <p> </p>