handyjson_swift-handyjson - CSDN
  • 自从开始使用Swift做项目,一直都在使用HandyJSON,不可否认,HandyJSON在Swift4.0以前是个好东西,也尝试过其它json转mode的工具,最终发现还是HandyJSON最好用. 去年Swift4.0发布之后,一个最有趣的变化就是Codable协议....

    自从开始使用Swift做项目,一直都在使用HandyJSON,不可否认,HandyJSON在Swift4.0以前是个好东西,也尝试过其它json转mode的工具,最终发现还是HandyJSON最好用. 去年Swift4.0发布之后,一个最有趣的变化就是Codable协议. 一直都知道Codable来实现json转model,不但效率高,并且简单易用, 但是一直拖到最近才简单封装个小工具,为什么呢? ?!!!

    在工具的封装上,参考了HandyJSON的部分代码,在使用HandyJSON过程中,一直都觉得designatedPath是一个很牛逼的存在,开发效率提升了不是一个量级,于是这里也参考HandyJSON中designatedPath的实现代码,并根据Codable的需要改造成fileprivate func getInnerObject(inside jsonData: Data?, by designatedPath: String?) -> Data?方法. 在这里感谢HandyJSON开发组.

    Codable扩展全部代码如下所示.100余行的代码前后花费一天多的时间. 当了解到google工程师日均一百多行的代码量,我觉得这速度还可以吧. 在代码质量和阅读质量上不敢说有多好,我只是按照自己认为最好的方式来做,如果有哪里不当或者可以用更好方法解决的地方,烦请各位告知,彼此交流学习.

    Codable协议扩展实现代码如下所示:

    //
    //  CodableHelper.swift
    //  CodableDemo
    //
    //  Created by Walden on 2018/5/7.
    //  Copyright © 2018年 Walden. All rights reserved.
    //
    
    import Foundation
    
    //扩展Encodable协议,添加编码的方法
    public extension Encodable {
        //1.遵守Codable协议的对象转json字符串
        public func toJSONString() -> String? {
            guard let data = try? JSONEncoder().encode(self) else {
                return nil
            }
            return String(data: data, encoding: .utf8)
        }
        
        //2.对象转换成jsonObject
        public func toJSONObject() -> Any? {
            guard let data = try? JSONEncoder().encode(self) else {
                return nil
            }
            return try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
        }
    }
    
    //扩展Decodable协议,添加解码的方法
    public extension Decodable {
        //3.json字符串转对象&数组
        public static func decodeJSON(from string: String?, designatedPath: String? = nil) -> Self? {
            
            guard let data = string?.data(using: .utf8),
                let jsonData = getInnerObject(inside: data, by: designatedPath) else {
                    return nil
            }
            return try? JSONDecoder().decode(Self.self, from: jsonData)
        }
        
        //4.jsonObject转换对象或者数组
        public static func decodeJSON(from jsonObject: Any?, designatedPath: String? = nil) -> Self? {
            
            guard let jsonObject = jsonObject,
                JSONSerialization.isValidJSONObject(jsonObject),
                let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: []),
                let jsonData = getInnerObject(inside: data, by: designatedPath)  else {
                    return nil
            }
            return try? JSONDecoder().decode(Self.self, from: jsonData)
        }
    }
    
    //扩展Array,添加将jsonString或者jsonObject解码到对应对象数组的方法
    public extension Array where Element: Codable {
        
        public static func decodeJSON(from jsonString: String?, designatedPath: String? = nil) -> [Element?]? {
            guard let data = jsonString?.data(using: .utf8),
                let jsonData = getInnerObject(inside: data, by: designatedPath),
                let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [Any] else {
                return nil
            }
            return Array.decodeJSON(from: jsonObject)
        }
        
        public static func decodeJSON(from array: [Any]?) -> [Element?]? {
            return array?.map({ (item) -> Element? in
                return Element.decodeJSON(from: item)
            })
        }
    }
    
    
    /// 借鉴HandyJSON中方法,根据designatedPath获取object中数据
    ///
    /// - Parameters:
    ///   - jsonData: json data
    ///   - designatedPath: 获取json object中指定路径
    /// - Returns: 可能是json object
    fileprivate func getInnerObject(inside jsonData: Data?, by designatedPath: String?) -> Data? {
    
        //保证jsonData不为空,designatedPath有效
        guard let _jsonData = jsonData,
            let paths = designatedPath?.components(separatedBy: "."),
            paths.count > 0 else {
            return jsonData
        }
        //从jsonObject中取出designatedPath指定的jsonObject
        let jsonObject = try? JSONSerialization.jsonObject(with: _jsonData, options: .allowFragments)
        var result: Any? = jsonObject
        var abort = false
        var next = jsonObject as? [String: Any]
        paths.forEach({ (seg) in
            if seg.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "" || abort {
                return
            }
            if let _next = next?[seg] {
                result = _next
                next = _next as? [String: Any]
            } else {
                abort = true
            }
        })
        //判断条件保证返回正确结果,保证没有流产,保证jsonObject转换成了Data类型
        guard abort == false,
            let resultJsonObject = result,
            let data = try? JSONSerialization.data(withJSONObject: resultJsonObject, options: []) else {
            return nil
        }
        return data
    }

    CodableHelper工具的使用也是非常简单的,代码如下所示:

    //首先定义一个结构体Person用来表示数据Model
    struct Person: Codable {
        var name: String?
        var age: Int?
        var sex: String?
    }
    
    
    //1.jsonString中获取数据封装成Model
    let p1String = "{\"name\":\"walden\",\"age\":30,\"sex\":\"man\"}"
    let p1 = Person.decodeJSON(from: p1String)
    
    //2.jsonString中获取数据封装成Array
    let personString = "{\"haha\":[{\"name\":\"walden\",\"age\":30,\"sex\":\"man\"},{\"name\":\"healer\",\"age\":20,\"sex\":\"female\"}]}"
    let persons = [Person].decodeJSON(from: personString, designatedPath: "haha")
    
    //3.对象转jsonString
    let jsonString = p1?.toJSONString()
    
    //4.对象转jsonObject
    let jsonObject = p1?.toJSONObject()

    目前存在问题:

    1. Model中定义的数据类型和jsonString中数据类型不对应时候会导致解析失败;
    • 转载请注明出处

    转载于:https://www.cnblogs.com/dev-walden/p/9008866.html

    展开全文
  • HandyJSON + RealmSwift 坑

    2019-08-06 17:49:03
    最近要新搞一个项目, 然后选定了数据库用 RealmSwift, Model 和 JSON 互转用 HandyJSON 这两个合起来用( 准确来说, 只是HandyJSON本身的问题 ), 引发了一个问题, JSON 转 Model 的时候, Model 值一直不出来 后来, ...

    前言

    最近要新搞一个项目, 然后选定了数据库用 RealmSwift, Model 和 JSON 互转用 HandyJSON
    这两个合起来用( 准确来说, 只是HandyJSON本身的问题 ), 引发了一个问题, JSON 转 Model 的时候, Model 值一直不出来
    后来, 经过一系列排查, 才找到问题所在( 表面问题, 没去看源码 ), 然后也算半解决状态了吧

    HandyJSON 版本: 5.0.0
    Swift 版本: 5.0

    我目前的解决方法,有两个, 都不是很好, 不过抛转引玉, 肯定会有更好的方法的 ( 如广大网友, 有更好的方法, 可以留言 )

    示例 JSON 为

    {
    	"Name":"老王"
    }
    

    先放一开始的代码

    // 继承 RealmSwift 的 Object, 并且遵守 HandyJSON 协议
    class XQTestModel: Object, HandyJSON {
    
        @objc dynamic var xq_name: String?
    
        func mapping(mapper: HelpingMapper) {
        	// Name 映射为 xq_name
            mapper <<<
                self.xq_name <-- "Name"
        }
        
    }
    

    很简单的代码, 就是把 Name 映射为 xq_name, 但转换时, 却为空 =_=

    排查

    有问题, 那肯定有解决办法. 那么, 就进入排查阶段. ( 说一下思路,不喜的, 直接跳过 )

    RealmSwift 和 HandyJson 一起用的时候, 转换不了. 平常就不会, 那么, 我们从用了 RealmSwift 之后, 会有那些不一样的地方开始排查

    1. Object
      类 , 不用 RealmSwift 的 Object, 换成系统的 NSObject, 结果不行, 排除 ( 这里就可以看出, 其实跟 RealmSwift 无太大关系了 )

    2. @objc
      删除之后, 还是不行, 排除

    3. dynamic
      删除之后, 可以了. 那么可以先缩小范围在 dynamic 上

    4. mapping
      保持存有 dynamic 字段, 把 xq_name 改回和 JSON 一致 Name, 然后移除 mapping 内容.
      结果是可以的.

    那么先知道了, 映射字段 和 dynamic 这两个问题所在, 就能离答案更进一步了. 起码现在知道, 单纯只是 HandyJSON 自己不支持 dynamic 和 mapping 混用. 不要错怪了 RealmSwift

    最后想来想去, 就想到以下两个方法解决.( 感觉算不上解决方案 ?, 其实最好还是去看 mapping 源码, 估计是这一块出了问题 )

    ps: GitHub 上也有人提了, 不过 HandyJSON 的大佬一直没去解决 ㄟ( ▔, ▔ )ㄏ

    解决办法1: 直接用后台传来的 Key

    如果你并不介意直接用后台传来的key, 那么就可以直接声明

    @objc dynamic Name: String?
    

    这样就算有 dynamic, 也是可以用的. 当然, 如果你愿意这样, 那么也不会来寻求答案, 那么可以看看第二种

    解决办法1: 利用 willSet

    代码如下

    class XQTestModel: NSObject, HandyJSON {
        
        required override init() {
            super.init()
        }
        
        @objc private var Name: String?
        {
            willSet {
                xq_name = newValue
            }
        }
        
        @objc dynamic var xq_name: String?
        
    }
    

    这个方法虽然有点蠢, 主要代码量太大,但是起码能解决目前的问题.

    ps: 如有更好的方法, 请告诉我, 因为我也不想再用这么蠢的方法了 (╯﹏╰)

    参考文章

    Realm使用中碰到的问题(坑)及解决方案

    展开全文
  • HandyJSON库简介

    2018-10-12 14:15:18
    背景 JSON是移动开发中常用的应用层数据交换协议。最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本到具体的Model,再把对应数据展现到页面上。...

    背景

    JSON是移动开发中常用的应用层数据交换协议。最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本到具体的Model,再把对应数据展现到页面上。

    但在编程的时候,处理JSON是一件麻烦事。在iOS开发中,在不引入任何轮子的情况下,通常需要先把JSON转为Dictionary,然后还要记住每个数据对应的Key,用这个Key在Dictionary中取出对应的Value来使用。而在手动解析的过程中,经常会犯很多的低价错误,比如Key拼写错误,类型错误,key的空值判断等。

    为了解决这些问题,很多处理JSON的开源库应运而生。通过对比可以发现,这些开源库基本都需要具有两个主要的功能:

    1. 保持JSON语义,直接解析JSON,但通过封装使调用方式更优雅、更安全;
    2. 预定义Model类,将JSON反序列化为类实例,再使用这些实例;

    其实,上面所说的两点也是移动开发中JSON解析框架必须具备的两点功能。而具备上面两点的第三方库通常有SwiftyJSON、ObjectMapper、JSONNeverDie、HandyJSON 等,而我们今天要讲的HandyJSON 是一款在Swift开发中使用的比较多的进行Model和JSON间的互相转换的开源库,该库由阿里巴巴技术团队研发,,已经过了大量的实战积累。

    HandyJSON的优势

    在HandyJSON出现以前,在Swift中把JSON反序列化到Model类主要有两种方式:

    1. 让Model类继承自NSObject,然后class_copyPropertyList()方法获取属性名作为Key,从JSON中取得Value,再通过Objective-C runtime支持的KVC机制为类属性赋值;如JSONNeverDie;
    2. 对于纯Swift编写的项目,可以实现Mapping函数,使用重载的运算符进行赋值,如ObjectMapper;

    对于上面两种方式来说,有以下两点明显的缺陷:前者要求Model继承自NSObject,非常不优雅,且直接否定了用struct来定义Model的方式;后者的Mapping函数要求开发者自定义,在其中指明每个属性对应的JSON字段名,代码侵入大,且仍然容易发生拼写错误、维护困难等问题。

    HandyJSON独辟蹊径,采用Swift反射+内存赋值的方式来构造Model实例,规避了上述两个方案遇到的问题。不过HandyJSON也并非完美无缺,如经常造成的内存泄露,兼容性差等问题。

    HandyJSON使用

    HandyJSON需要以下本地环境具备以下条件:

    • iOS 8.0+/OSX 10.9+/watchOS 2.0+/tvOS 9.0+
    • Swift 3.0+ / Swift 4.0+

    同时,针对不同的IDE环境和Swift版本,HandyJSON的版本也不一样,可以参考下表。

    Xcode Swift HandyJSON
    Xcode 10 Swift 4.2 4.2.0
    Xcode 9.4.1以下 Swift 4 >= 4.1.1
    Xcode 8.3以上 Swift 3.x >= 1.8.0

    HandyJSON安装

    对于第三方库,一般有两种依赖方式,一种是framwork依赖,一种是源码依赖。使用Cocoapods安装依赖的配置脚本如下:

    pod 'HandyJSON', '~> 4.2.0'
    

    然后再执行“pod install”命令来按照HandyJSON库。当然,我们还可以使用Carthage来管理第三方框架和依赖。

    github "alibaba/HandyJSON" ~> 4.2.0
    

    当然,我们还可以将HandyJSON库下载下来再使用源码的方式依赖,下面的地址为:https://github.com/alibaba/HandyJSON.git。

    JSON转Model

    基础类型

    假设我们从服务端拿到JSON文本是这样的:

    {
    	"name": "cat",
    	"id": "12345",
    	"num": 180
    }
    

    此时,如果我们想要使用HandyJSON来反序列化,只需要定义如下一个Model类即可。

    if let animal = JSONDeserializer<Animal>.deserializeFrom(json: jsonString) {
        print(animal.name)
        print(animal.id)
        print(animal.num)
    }
    

    复杂类型

    HandyJSON支持在类定义里使用各种形式的基本属性,包括可选(?),隐式解包可选(!),数组(Array),字典(Dictionary),Objective-C基本类型(NSString、NSNumber),各种类型的嵌套([Int]?、[String]?、[Int]!、…)等等。例如,有下面一个复杂的数据结构:

    struct Cat: HandyJSON {
        var id: Int64!
        var name: String!
        var friend: [String]?
        var weight: Double?
        var alive: Bool = true
        var color: NSString?
    }
    

    假如后台返回的数据内容如下:

    {
    	"id": 1234567,
    	"name": "Kitty",
    	"friend": ["Tom", "Jack", "Lily", "Black"],
    	"weight": 15.34,
    	"alive": false,
    	"color": "white"
    }
    

    如果要将上面的JSON数据转换为上面定义的Model类,只需要一句话即可。

    let jsonString = "{"id":1234567,"name":"Kitty","friend":["Tom","Jack","Lily","Black"],"weight":15.34,"alive":false,"color":"white"}"
    
    if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) {
        print(cat.xxx)
    }
    

    Model嵌套

    如果Model类中的某个属性是另一个自定义的Model类,那么只要那个Model类也实现了HandyJSON协议,就可以完成转换。

    struct Component: HandyJSON {
        var aInt: Int?
        var aString: String?
    }
    
    struct Composition: HandyJSON {
        var aInt: Int?
        var comp1: Component?
        var comp2: Component?
    }
    
    let jsonString = "{"num":12345,"comp1":{"aInt":1,"aString":"aaaaa"},"comp2":{"aInt":2,"aString":"bbbbb"}}"
    
    if let composition = JSONDeserializer<Composition>.deserializeFrom(json: jsonString) {
        print(composition)
    }
    

    指定JSON中某个节点

    有时候服务端返回给我们的JSON文本包含了大量的状态信息,比如statusCode,debugMessage等,这些信息通常和Model是无关的。或者说,我们想要解析JSON中的某个指定节点的数据,对于这种情况,HandyJSON也是支持的。

    struct Cat: HandyJSON {
        var id: Int64!
        var name: String!
    }
    
    // 服务端返回的JSON,我们想解析的只有data里的cat
    let jsonString = "{"code":200,"msg":"success","data":{"cat":{"id":12345,"name":"Kitty"}}}"
    
    // 指定解析 "data.cat"节点数据
    if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString, designatedPath: "data.cat") {
        print(cat.name)
    }
    

    解析继承关系的Model

    如果某个Model类继承自另一个Model类,只需要父Model类实现HandyJSON协议即可。

    class Animal: HandyJSON {
        var id: Int?
        var color: String?
    
        required init() {}
    }
    
    
    class Cat: Animal {
        var name: String?
    
        required init() {}
    }
    
    let jsonString = "{"id":12345,"color":"black","name":"cat"}"
    
    if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) {
        print(cat)
    }
    

    自定义解析

    当然,HandyJSON还支持某些方面的自定义扩展,也即是说HandyJSON允许自行定义Model类某个字段的解析Key、解析方式。或许,在JSON解析中,你经常会碰到下面这样的场景:

    • 在定义某个Model时,我们不想使用和服务端约定的key作为属性名,想自己定一个;
    • 有些类型如enum、tuple是无法直接从JSON中解析出来;

    对于这些情况,我们可以根据HandyJSON协议提供的mapping()函数来实现自定义JSON解析。例如,有一个Model类和一个服务端返回的JSON串是下面这样的:

    class Cat: HandyJSON {
        var id: Int64!
        var name: String!
        var parent: (String, String)?
    
        required init() {}
    }
    
    let jsonString = "{"cat_id":12345,"name":"Kitty","parent":"Tom/Lily"}"
    

    可以看到,Cat类的id属性和JSON文本中的Key是对应不上的;而对于parent这个属性来说,它是一个元组,做不到从JSON中的"Tom/Lily"解析出来,所以我们可以使用Mapping函数来自定义支持。此时,Model类如下:

    class Cat: HandyJSON {
        var id: Int64!
        var name: String!
        var parent: (String, String)?
    
        required init() {}
    
        func mapping(mapper: HelpingMapper) {
            // 指定 id 字段用 "cat_id" 去解析
            mapper.specify(property: &id, name: "cat_id")
    
            // 指定 parent 字段用这个方法去解析
            mapper.specify(property: &parent) { (rawString) -> (String, String) in
                let parentNames = rawString.characters.split{$0 == "/"}.map(String.init)
                return (parentNames[0], parentNames[1])
            }
        }
    }
    

    Model转JSON

    对于Model转JSON,则相对简单,和Android开发中Model转JSON一样。

    基本类型

    如果只需要进行序列化,那么在定义Model类时,不需要做任何特殊的改动。任何一个类的实例,直接调用HandyJSON的序列化方法去序列化,就能得到JSON字符串。例如:

    class Animal {
        var name: String?
        var height: Int?
    
        init(name: String, height: Int) {
            self.name = name
            self.height = height
        }
    }
    
    let cat = Animal(name: "cat", height: 30)
    print(JSONSerializer.serializeToJSON(object: cat)!)
    print(JSONSerializer.serializeToJSON(object: cat, prettify: true)!)
    

    当然,我们也可以通过prettify参数来指定获得的是否是格式化后的JSON串。

    复杂Model

    对于复杂的Model,例如Model嵌套Model的情况,我们也可以HandyJSON的序列化函数来完成序列化。

    enum Gender: String {
        case Male = "male"
        case Female = "Female"
    }
    
    struct Subject {
        var id: Int64?
        var name: String?
    
        init(id: Int64, name: String) {
            self.id = id
            self.name = name
        }
    }
    
    class Student {
        var name: String?
        var gender: Gender?
        var subjects: [Subject]?
    }
    
    let student = Student()
    student.name = "Jack"
    student.gender = .Female
    student.subjects = [Subject(id: 1, name: "math"), Subject(id: 2, name: "English"), Subject(id: 3, name: "Philosophy")]
    
    print(JSONSerializer.serializeToJSON(object: student)!)
    print(JSONSerializer.serializeToJSON(object: student, prettify: true)!)
    

    Codable

    Codable 简介

    在WWDC2017大会上, Swift4.0的发布新增了一个重要的功能:Codable。Codable是一个协议,其作用类似于NSPropertyListSerialization 和 NSJSONSerialization,主要用于完成 JSON 和Model之间的转换。例如:

    typealias Codable = Decodable & Encodable
    
    public protocol Decodable {
        public init(from decoder: Decoder) throws
    }
    public protocol Encodable {
        public func encode(to encoder: Encoder) throws
    }
    

    关于Codable更多的知识可以参考官方的文档介绍。由上面的例子可以发现,Codable并不少单独存在的,它其实是Decodable 和 Encodable的融合。

    编码器与解码器

    Encoder 和 Decoder 的基本概念跟 NSCoder 类似,对象接受一个编码器,然后调用自己的方法来完成编码或者解码。NSCoder 的API 是很直接的。NSCoder 有一系列像是 encodeObject:forKey 还有encodeInteger:forKey的方法,对象调用他们来完成具体的编码。

    Swift 的 API 就没那么直接了, Encoder 不提供编码方法而是提供容器,由容器来完成编码工作。因为容器这个设计, Encoder 和Decoder 这两个协议就非常实用,只需要少量的信息就可以获取容器的方法。例如,下面是一个封装的类CodableHelper.swift

    rotocol Encoder {
      var codingPath: [CodingKey?] { get }
      public var userInfo: [CodingUserInfoKey : Any] { get }
    
      func container<Key>(keyedBy type: Key.Type)
              -> KeyedEncodingContainer<Key> where Key : CodingKey
      func unkeyedContainer() -> UnkeyedEncodingContainer
      func singleValueContainer() -> SingleValueEncodingContainer
    }
    
    protocol Decoder {
      var codingPath: [CodingKey?] { get }
      var userInfo: [CodingUserInfoKey : Any] { get }
    
      func container<Key>(keyedBy type: Key.Type) throws
              -> KeyedDecodingContainer<Key> where Key : CodingKey
      func unkeyedContainer() throws -> UnkeyedDecodingContainer
      func singleValueContainer() throws -> SingleValueDecodingContainer
    }
    

    实例

    使用Codable解析JSON主要会用到JSONEncoder和JSONDecoder两个函数,其中JSONEncoder用于编码,JSONDecoder用于解析。

    let data = try! JSONEncoder().encode([1: 3])
    let dict = try! JSONDecoder().decode([Int: Int].self, from: data)
    print(dict)
    

    基本类型

    Swift的Enum,Struct和Class等基本类型都支持Codable,下面是一个具体的实例。

    enum Level: String, Codable {
        case large
        case medium
        case small
    }
    
    struct Location: Codable {
        let latitude: Double
        let longitude: Double
    }
    
    // CustomDebugStringConvertible只是为了更好打印
    class City: Codable, CustomDebugStringConvertible {
        
        let name: String
        let pop: UInt
        let level: Level
        let location: Location
        
        var debugDescription: String {
            return """
            {
            "name": \(name),
            "pop": \(pop),
            "level": \(level.rawValue),
            "location": {
            "latitude": \(location.latitude),
            "longitude": \(location.longitude)
            }
            }
            """
        }
    }
    
    let jsonData = """
            {
            "name": "Shanghai",
            "pop": 21000000,
            "level": "large",
            "location": {
              "latitude": 30.40,
              "longitude": 120.51
            }
            }
            """.data(using: .utf8)!
    do {
        let city = try JSONDecoder().decode(City.self, from: jsonData)
        print("city:", city)
    } catch {
        print(error.localizedDescription)
    }
    

    上述实例展示了三种基本类型的基本用法,需要注意的是所有存储属性的类型都需遵循Codable才可以推断,计算属性不受此限制。如有存储属性不遵循Codable,需要自行实现本文开头协议中的方法。

    自定义key

    由于Codable的key是直接用属性名匹配的,所以当key不匹配时需要我们自定义并实现协议方法。比如将上述的的name字段变成了short_name。
    此时我们需要这么做:定义一个枚举遵循CodingKey协议且原始值为String,且实现Decodable的协议方法。

    let jsonData = """
            {
            "short_name": "Shanghai",  // 这里的key与model不再吻合
            "pop": 21000000,
            "level": "large",
            "location": {
              "latitude": "30.40",
              "longitude": 120.51
            }
            }
            """.data(using: .utf8)!
    
    class City: Codable, CustomDebugStringConvertible {
       //...其余代码与上例一致
        
        enum CodingKeys: String, CodingKey {
            case name = "short_name"
            case pop
            case level
            case location
        }
        
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            name = try container.decode(String.self, forKey: .name)
            pop = try container.decode(UInt.self, forKey: .pop)
            level = try container.decode(Level.self, forKey: .level)
            location = try container.decode(Location.self, forKey: .location)
        }
    }
    

    泛型

    如果模型定义的比较好,其实大部分属性是可以复用的,我们可以通过泛型来实现模型的部分复用。

    struct Resource<Attributes>: Codable where Attributes: Codable {
        let name: String
        let url: URL
        let attributes: Attributes
    }
    
    struct ImageAttributes: Codable {
        let size: CGSize
        let format: String
    }
    
    Resource<ImageAttributes>
    

    有时候JSON中的格式并非我们实际所需要的,比如String格式的浮点型数字,我们希望直接在转model时转换为Double类型。那么Codable也是支持这样的操作的。

    struct StringToDoubleConverter: Codable { 
        let value: Double?
        
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            let string = try container.decode(String.self)
            value = Double(string)
        }
    }
    
    

    二次封装

    虽然使用Codable可以很方便的完成JSON的转换,但是对于我们项目开发来说仍然不够完美,我们可以使用二次封装。

    import Foundation
    
    
    public extension Encodable {
        //对象转json字符串
        public func toJSONString() -> String? {
            guard let data = try? JSONEncoder().encode(self) else {
                return nil
            }
            return String(data: data, encoding: .utf8)
        }
        
        //对象转jsonObject
        public func toJSONObject() -> Any? {
            guard let data = try? JSONEncoder().encode(self) else {
                return nil
            }
            return try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
        }
    }
    
    
    public extension Decodable {
        //json字符串转对象&数组
        public static func decodeJSON(from string: String?, designatedPath: String? = nil) -> Self? {
            
            guard let data = string?.data(using: .utf8),
                let jsonData = getInnerObject(inside: data, by: designatedPath) else {
                    return nil
            }
            return try? JSONDecoder().decode(Self.self, from: jsonData)
        }
        
        //jsonObject转换对象或者数组
        public static func decodeJSON(from jsonObject: Any?, designatedPath: String? = nil) -> Self? {
            
            guard let jsonObject = jsonObject,
                JSONSerialization.isValidJSONObject(jsonObject),
                let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: []),
                let jsonData = getInnerObject(inside: data, by: designatedPath)  else {
                    return nil
            }
            return try? JSONDecoder().decode(Self.self, from: jsonData)
        }
    }
    
    
    public extension Array where Element: Codable {
        
        public static func decodeJSON(from jsonString: String?, designatedPath: String? = nil) -> [Element?]? {
            guard let data = jsonString?.data(using: .utf8),
                let jsonData = getInnerObject(inside: data, by: designatedPath),
                let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [Any] else {
                return nil
            }
            return Array.decodeJSON(from: jsonObject)
        }
        
        public static func decodeJSON(from array: [Any]?) -> [Element?]? {
            return array?.map({ (item) -> Element? in
                return Element.decodeJSON(from: item)
            })
        }
    }
    
    
    //根据designatedPath获取object中数据
    fileprivate func getInnerObject(inside jsonData: Data?, by designatedPath: String?) -> Data? {
        guard let _jsonData = jsonData,
            let paths = designatedPath?.components(separatedBy: "."),
            paths.count > 0 else {
            return jsonData
        }
        //从jsonObject中取出designatedPath指定的jsonObject
        let jsonObject = try? JSONSerialization.jsonObject(with: _jsonData, options: .allowFragments)
        var result: Any? = jsonObject
        var abort = false
        var next = jsonObject as? [String: Any]
        paths.forEach({ (seg) in
            if seg.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "" || abort {
                return
            }
            if let _next = next?[seg] {
                result = _next
                next = _next as? [String: Any]
            } else {
                abort = true
            }
        })
        //判断条件保证返回正确结果,保证没有流产,保证jsonObject转换成了Data类型
        guard abort == false,
            let resultJsonObject = result,
            let data = try? JSONSerialization.data(withJSONObject: resultJsonObject, options: []) else {
            return nil
        }
        return data
    }
    

    CodableHelper的使用也非常简单,有点面向对象变成的感觉,下面是具体的使用例子。

    struct Person: Codable {
        var name: String?
        var age: Int?
        var sex: String?
    }
    
    
    //jsonString中获取数据封装成Model
    let p1String = "{"name":"walden","age":30,"sex":"man"}"
    let p1 = Person.decodeJSON(from: p1String)
    
    //jsonString中获取数据封装成Array
    let personString = "{"haha":[{"name":"walden","age":30,"sex":"man"},{"name":"healer","age":20,"sex":"female"}]}"
    let persons = [Person].decodeJSON(from: personString, designatedPath: "haha")
    
    //对象转jsonString
    let jsonString = p1?.toJSONString()
    
    //对象转jsonObject
    let jsonObject = p1?.toJSONObject()
    

    附:https://swift.ctolib.com/HandyJSON.html

    展开全文
  • HandyJSON代码阅读

    2019-01-13 06:46:58
    功能:model = modelType.transform(rawdata)   使用分析: 使用机制:继承+实现配置+使用; 需要自己实现什么?   设计分析: 工具模块?机制模块?...T: HandyJSON&...HandyJSON: _ExtendCustomMod...

    功能:model = modelType.transform(rawdata)

     

    使用分析:

    使用机制:继承+实现配置+使用;

    需要自己实现什么?

     

    设计分析:

    工具模块?机制模块?model基类?

    生成机制?

    如何组织?

    接口:通过继承使用接口;

     

    主干类:

    JSONDeserializer<T: HandyJSON>

     

    HandyJSON: _ExtendCustomModelType: _Transformable: _Measurable

     

    _ExtendCustomModelType

    mutating func mapping(mapper: HelpingMapper)

    mutating func didFinishMapping()

     

    _Transformable

    transform(from object: Any) -> Self?

     

    _Measurable

    headPointerOfStruct() -> UnsafeMutablePointer<Byte>

     

     

    问题分解:

    1、遍历rawdata对model赋值;

     

    2、赋值解决方案,前提条件:无法获取成员变量的内存引用;

    解决方案:kvc赋值(不支持)、根据内存对齐规则直接对内存赋值;

     

    3、对象内存模型信息提取;

     

    4、复杂赋值要处理的情况:

    (1)rawdata引用与目标变量名称不一致;

    (2)rawdata引用与目标变量路径不一致;

    (3)rawdata引用与目标变量类型不一致;

     

    static func _transform(dict: [String: Any], to instance: inout Self) {

            guard let properties = getProperties(forType: Self.self) else {

                InternalLogger.logDebug("Failed when try to get properties from type: \(type(of: Self.self))")

                return

            }

     

            // do user-specified mapping first

            let mapper = HelpingMapper()

            instance.mapping(mapper: mapper)

     

            // get head addr

            let rawPointer = instance.headPointer()

            InternalLogger.logVerbose("instance start at: ", Int(bitPattern: rawPointer))

     

            // process dictionary

            let _dict = convertKeyIfNeeded(dict: dict)

     

            let instanceIsNsObject = instance.isNSObjectType()

            let bridgedPropertyList = instance.getBridgedPropertyList()

     

            for property in properties {

                let isBridgedProperty = instanceIsNsObject && bridgedPropertyList.contains(property.key)

     

                let propAddr = rawPointer.advanced(by: property.offset)

                InternalLogger.logVerbose(property.key, "address at: ", Int(bitPattern: propAddr))

                if mapper.propertyExcluded(key: Int(bitPattern: propAddr)) {

                    InternalLogger.logDebug("Exclude property: \(property.key)")

                    continue

                }

     

                let propertyDetail = PropertyInfo(key: property.key, type: property.type, address: propAddr, bridged: isBridgedProperty)

                InternalLogger.logVerbose("field: ", property.key, "  offset: ", property.offset, "  isBridgeProperty: ", isBridgedProperty)

     

                if let rawValue = getRawValueFrom(dict: _dict, property: propertyDetail, mapper: mapper) {

                    if let convertedValue = convertValue(rawValue: rawValue, property: propertyDetail, mapper: mapper) {

                        assignProperty(convertedValue: convertedValue, instance: instance, property: propertyDetail)

                        continue

                    }

                }

                InternalLogger.logDebug("Property: \(property.key) hasn't been written in")

            }

        }

     

    展开全文
  • HandyJSON

    2020-03-12 11:26:13
    HandyJSON 用法 https://www.cnblogs.com/yajunLi/p/7121950.html 原理 https://www.jianshu.com/p/da0ccff0b531 json和object转化 class BasicTypes: HandyJSON { var int: Int = 2 var doubleOptional: Double? ...
  • HandyJSON阅读笔记

    2019-06-16 21:10:18
    2019独角兽企业重金招聘Python工程师标准>>> ...
  • HandyJson 是一个用于Swift语言中的JSON序列化/反序列化库 HandyJson的特点是,它支持纯swift类,使用简单。反序列化时(把JSON转化为Model)不要求Model从NSObject 继承(因为他不是基于KVC机制),也不要求为...
  • 背景 JSON是移动端开发常用的应用层数据交换协议。最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本,再把对应数据展现到页面上。 但在编程的时候,处理JSON是...
  • 使用HandyJSON导致的内存泄漏问题相关解决方法 在移动开发中,与服务器打交道是不可避免的,从服务器拿到的接口数据最终都会被我们解析成模型,现在比较常见的数据传输格式是json格式,对json格式的解析可以使用...
  • swift中网络请求库的封装(Alamofire+HandyJSON) 我们用swift语言来写iOS的程序,会用Alamofire+HandyJSON来取代AFNetworking+MJExtension,怎么样封装成正确姿势来发起网络请求,我个人封装一个,有待优化的地方,还...
  • HandyJSON实现方案浅析

    2019-02-28 00:41:42
    近日,由于Swift升级,导致一段时间HandyJSON无法使用,借这个机会将HandyJSON好好学习了一下~ 然后从另一种方式实现了类似OC的KVC效果的一个小Demo,通过本文记录一些自己的收获,旨在抛砖引玉,有错误的地方还请多多指正...
  • HandyJSON是swift开发中常用的SON解析框架。该库由阿里巴巴技术团队研发,已经过了大量的实战积累。不再赘述。 地址:https://github.com/alibaba/HandyJSON 版本参考 针对不同的IDE环境和Swift版本,HandyJSON的...
  • 升级到swift5之后,第三方库HandyJSON报了一堆错误,主要原因是现在主分支上的不支持swift5。需要切换到其他分支 具体做法是 pod 'HandyJSON',:git => 'https://github.com/alibaba/HandyJSON.git', :branch =&...
  • 升级最新的xcode以后,运行项目的时候HandyJSON出现了numberOfFields的错误: 解决方法如下,到对应位置修改:
  • 一、错误提示 1、更新Xcode10.2,Swift5.0出现错误提示 Undefined symbols for architecture x86_64: "_swift_getFieldAt", referenced from: HandyJSON.Metadata.Class._... ([HandyJSON...
  • 一直想不到方法做这个事 然后在HandyJSON中发现了它自带的值类型转换方法 mutating func mapping(mapper: HelpingMapper) { mapper &lt;&lt;&lt; 要转化的字段 &lt;-- TransformOf&lt;A...
  • iOS封装CoreData+HandyJson实现本地缓存 准备工作 选中Tagets->BuildPhases->LinkBinaryWithLibraries 添加CoreData.framework 使用CocoaPods工具Pod需要使用的相关框架 pod 'AlecrimCoreData' pod '...
  • 本文衔接 如何将JavaScript转化成Swift?...(如何将一段Json字符串自动生成HandyJson格式的Model类) 将JavaScript的解析JSON代码翻译到Swift JSON解析 { "error":0, "msg":"success", "data":[ ...
  • Xcode10.2, swift5.0 Undefined symbols for architecture x86_64: "_swift_getFieldAt", referenced from: HandyJSON.Metadata.Class._propertyDescriptionsAndStartPoint() -&... ([HandyJSON.Property.Descriptio...
  • HandyJSON (阿里巴巴开源框架json->model) 项目地址:https://github.com/alibaba/handyjsonJSON数据是一种轻量级的数据交互格式,JSON数据常用来服务器,客户端的交流; 在我们手机端: 从服务器收到 json 格式的...
1 2 3 4 5 ... 20
收藏数 386
精华内容 154
热门标签
关键字:

handyjson