Swift-转模型HandyJSON

  • 一 简介

  • 二 特性

  • 三 安装使用以及封装

  • 四 使用示例

  • 五 项目使用示例

一 简介

HandyJSON是一个用于Swift语言中的JSON序列化/反序列化库。

与其他流行的Swift JSON库相比,HandyJSON的特点是,它支持纯swift类,使用也简单。它反序列化时(把JSON转换为Model)不要求ModelNSObject继承(因为它不是基于KVC机制),也不要求你为Model定义一个Mapping函数。只要你定义好Model类,声明它服从HandyJSON协议,HandyJSON就能自行以各个属性的属性名为Key,从JSON串中解析值。

HandyJSON目前依赖于从Swift Runtime源码中推断的内存规则,任何变动我们将随时跟进。

二 特性

  • 序列化ModelJSON、从JSON反序列化Model
  • 自然地以Model的属性名称作为解析JSONKey,不需要额外指定
  • 支持Swift中大部分类型
  • 支持classstruct定义的Model
  • 支持自定义解析规则
  • 类型自适应,如JSON中是一个Int,但对应ModelString字段,会自动完成转化

三 安装使用以及封装

3.1 安装
我使用的是cocopod进行包引入管理,修改Prodfie文件,添加如下代码:

pod 'HandyJSON'

3.2 封装
为了方便我们项目中的使用,我们一般都会在做一层封装,方便库的以后升级替换,以及方便日常业务逻辑的处理

JsonUtil.swift
 
import UIKit
import HandyJSON
class JsonUtil: NSObject {
    /**
     *  Json转对象
     */
    static func jsonToModel(_ jsonStr:String,_ modelType:HandyJSON.Type) ->BaseModel {
        if jsonStr == "" || jsonStr.count == 0 {
            #if DEBUG
                print("jsonoModel:字符串为空")
            #endif
            return BaseModel()
        }
        return modelType.deserialize(from: jsonStr)  as! BaseModel
        
    }
    
    /**
     *  Json转数组对象
     */
    static func jsonArrayToModel(_ jsonArrayStr:String, _ modelType:HandyJSON.Type) ->[BaseModel] {
        if jsonArrayStr == "" || jsonArrayStr.count == 0 {
            #if DEBUG
                print("jsonToModelArray:字符串为空")
            #endif
            return []
        }
        var modelArray:[BaseModel] = []
        let data = jsonArrayStr.data(using: String.Encoding.utf8)
        let peoplesArray = try! JSONSerialization.jsonObject(with:data!, options: JSONSerialization.ReadingOptions()) as? [AnyObject]
        for people in peoplesArray! {
            modelArray.append(dictionaryToModel(people as! [String : Any], modelType))
        }
        return modelArray
        
    }
    
    /**
     *  字典转对象
     */
    static func dictionaryToModel(_ dictionStr:[String:Any],_ modelType:HandyJSON.Type) -> BaseModel {
        if dictionStr.count == 0 {
            #if DEBUG
                print("dictionaryToModel:字符串为空")
            #endif
            return BaseModel()
        }
        return modelType.deserialize(from: dictionStr) as! BaseModel
    }
    
    /**
     *  对象转JSON
     */
    static func modelToJson(_ model:BaseModel?) -> String {
        if model == nil {
            #if DEBUG
                print("modelToJson:model为空")
            #endif
             return ""
        }
        return (model?.toJSONString())!
    }
    
    /**
     *  对象转字典
     */
    static func modelToDictionary(_ model:BaseModel?) -> [String:Any] {
        if model == nil {
            #if DEBUG
                print("modelToJson:model为空")
            #endif
            return [:]
        }
        return (model?.toJSON())!
    }
    
}

说明:这里我封装了5个方法,Json转对象,Json数组对象字典对象对象JSON对象字典,基本覆盖了我们日常开发的常用操作,与这个工具类相对应的,还有一个公共的基础module

BaseModel.swift
 
import UIKit
import HandyJSON
class BaseModel: HandyJSON {
//    var date: Date?
//    var decimal: NSDecimalNumber?
//    var url: URL?
//    var data: Data?
//    var color: UIColor?
    
    required init() {}
    
    func mapping(mapper: HelpingMapper) {   //自定义解析规则,日期数字颜色,如果要指定解析格式,子类实现重写此方法即可
//        mapper <<<
//            date <-- CustomDateFormatTransform(formatString: "yyyy-MM-dd")
//
//        mapper <<<
//            decimal <-- NSDecimalNumberTransform()
//
//        mapper <<<
//            url <-- URLTransform(shouldEncodeURLString: false)
//
//        mapper <<<
//            data <-- DataTransform()
//
//        mapper <<<
//            color <-- HexColorTransform()
      }
}

说明:封装的基础model类。开发中,我们自定义的model继承此model即可省去了每次都要引入 “import HandyJSON”,以及每次都要实现“required init() {}”方法,子类如果要自定义解析规则,重写mapping方法即可

四 使用示例

基于以上封装的类我们实现几个示例做具体说明,主要有以下几个示例:

  • Json转模型 (常用)
  • Json数组转模型 (常用)
  • 字典转模型
  • 模型转Json
  • 模型转字典
  • json与嵌套的模型相互转换特殊类型字段转换,日期类型,数字类型,颜色
1. Json转模型 (常用)
JsonToModel.swift
 
import UIKit
class JsonToModel: BaseModel {
    var id :Int?
    var color:String?
    var name:String?
}
 fileprivate func jsonTomodel(){
        let jsonString = "{\"id\":12345,\"color\":\"black\",\"name\":\"cat\"}"
        let model:JsonToModel = JsonUtil.jsonToModel(jsonString,JsonToModel.self) as! JsonToModel
        print(model.name as Any)
        print(model.color as Any)
        print(model.id as Any)
    }

说明:调用jsonToModel(_ jsonStr:String,_ modelType:HandyJSON.Type),传入两个参数,第一个参数是要转换的Json字符串,第二个参数是要转换的model类的class,因为公共类中统一返回的都是BaseModel类型,所以这里要调用 as!转换成具体的子类类型。

2. Json数组转模型 (常用)
JsonArrayToModel.swift
 
import UIKit
class JsonArrayToModel: BaseModel {
    var name:String?
    var id :String?
}
//json数组转模型
    fileprivate func jsonArrayTomodel() {
        let jsonArrayString: String = "[{\"name\":\"Bob\",\"id\":\"1\"}, {\"name\":\"Lily\",\"id\":\"2\"}, {\"name\":\"Lucy\",\"id\":\"3\"}]"
        let cats = JsonUtil.jsonArrayToModel(jsonArrayString, JsonArrayToModel.self) as! [JsonArrayToModel]
        for model:JsonArrayToModel  in cats {
            print(model.name as Any)
        }
    }

说明:调用jsonArrayToModel(_ jsonArrayStr:String, _ modelType:HandyJSON.Type)传入两个参数,第一个参数是要转换的数组型Json字符串,第二个参数是要转换的model类的class,返回值是一个数组,因为公共类中统一返回的都是BaseModel类型,所以这里要调用as!转换成具体的子类类型数组。

3 字典转模型
import UIKit
class JsonToModel: BaseModel {
    var id :Int!
    var color:String?
    var name:String?
}
//字典转模型
    fileprivate func dicToModel() {
        var dict = [String: Any]()
        dict["id"] = 1.1
        dict["color"] = "hello"
        dict["name"] = "李四"
        let model:JsonToModel = JsonUtil.dictionaryToModel(dict,JsonToModel.self) as! JsonToModel
        print(model.name as Any)
        print(model.color as Any)
        print(model.id as Any)
    }

说明:调用dictionaryToModel(_ dictionStr:[String:Any],_ modelType:HandyJSON.Type),传入两个参数,第一个参数是要转换的字典对象,第二个参数是要转换的model类的class,因为公共类中统一返回的都是BaseModel类型,所以这里要调用as!转换成具体的子类类型。

4. 模型转Json
JsonToModel.swift
 
import UIKit
class JsonToModel: BaseModel {
    var id :Int!
    var color:String?
    var name:String?
}
//模型转json
    fileprivate func modelToJson() {
        let model:JsonToModel = JsonToModel()
        model.color = "red"
        model.id    = 100
        model.name  = "李四真"
        let modelTostring = JsonUtil.modelToJson(model)
        print(modelTostring)
    }

说明:调用modelToJson(_ model:BaseModel?),传入一个参数,传入一个module对象,返回值是一个JSON字符串

5. 模型转字典
JsonToModel.swift

import UIKit
class JsonToModel: BaseModel {
    var id :Int!
    var color:String?
    var name:String?
}
 //模型转字典
    fileprivate func modelTodiction() {
        let model:JsonToModel = JsonToModel()
        model.color = "red"
        model.id    = 100
        model.name  = "李四"
        let modelTostring = JsonUtil.modelToDictionary(model)
        print(modelTostring["name"] as Any)
        
    }

说明:调用modelToDictionary(_ model:BaseModel?)传入一个参数,传入一个module对象,返回值是一个字典对象,对于日常开发中,有时候后台只返回一个字段,比如返回一个成功信息字段,直接把返回的json串转换成一个字典即可,没必要再构建一个model去转换。

6. json与嵌套的模型相互转换
CombineModel.swift
 
import UIKit
class Composition:  BaseModel {
    var aInt:Int?
    var aString:String?
}
 
class CombineModel: BaseModel {
    var aInt:Int?
    var comp1:Composition?
    var comp2:[Composition] = []
    
}
//json与嵌套的模型相互转换
    fileprivate func jsonTocombilModel() {
        let model:CombineModel = CombineModel()
        model.aInt = 1001
        let posModel1 = Composition()
        posModel1.aInt = 1
        posModel1.aString = "赵六1"
        
        let posModel2 = Composition()
        posModel2.aInt = 2
        posModel2.aString = "赵六2"
        
        let posModel3 = Composition()
        posModel3.aInt = 3
        posModel3.aString = "赵六3"
        
        model.comp1 = posModel1
        model.comp2.append(posModel2)
        model.comp2.append(posModel3)
        
        let modeString = JsonUtil.modelToJson(model)
        print(modeString)
        
        let model2 = JsonUtil.jsonToModel(modeString, CombineModel.self)
        print(model2)
    }

说明:还是调用Json转模型,模型转Json的方法,本例子演示的是对象嵌套,对象里面可以嵌套对象,可以嵌套对象数组,平时我们开发经常遇到这种结构的Json串

7. 特殊类型字段转换,日期类型,数字类型,颜色
SpacialTypeModel.swift
 
import UIKit
import HandyJSON
class SpacialTypeModel: BaseModel {
    var date: Date?
    var decimal: NSDecimalNumber?
    var url: URL?
    var data: Data?
    var color: UIColor?
    
    override func mapping(mapper: HelpingMapper) {
        mapper <<< 
            date <-- CustomDateFormatTransform(formatString: "yyyy-MM-dd")
 
        mapper <<<
            decimal <-- NSDecimalNumberTransform()
 
        mapper <<<
            url <-- URLTransform(shouldEncodeURLString: false)
 
        mapper <<<
            data <-- DataTransform()
 
        mapper <<<
            color <-- HexColorTransform()
    }
 
}
// 特殊类型字段转换,日期类型,数字类型,颜色
    fileprivate func jsonToSpecialModel () {
        
        let object = SpacialTypeModel()
        object.date = Date()
        object.decimal = NSDecimalNumber(string: "1.23423414371298437124391243")
        object.url = URL(string: "https://www.aliyun.com")
        object.data = Data(base64Encoded: "aGVsbG8sIHdvcmxkIQ==")
        object.color = UIColor.blue
        
        let specailModelString = JsonUtil.modelToJson(object)
        print(object.toJSONString()!)
        // it prints:
        // {"date":"2017-09-11","decimal":"1.23423414371298437124391243","url":"https:\/\/www.aliyun.com","data":"aGVsbG8sIHdvcmxkIQ==","color":"0000FF"}
        
        let mappedObject:SpacialTypeModel = JsonUtil.jsonToModel(specailModelString, SpacialTypeModel.self) as! SpacialTypeModel
        print(mappedObject.date as Any)
    }

说明:本例演示的是对于对象里含有特殊类型字段的转换方法,主要注意点在构建model类里,我们要重写父类mapping方法还要引入HandyJSON头文件

五 项目使用示例


// MARK: - 基类模型转模型为返回类型为[String:Any]
public struct DailyStudyBaseModel<T>:HandyJSON {
    
    public var code: Int?
    public var msg: String?
    public var msgDetail: String?
    public var data: T?
    
    public init() {
        
    }
    
}
// MARK: - 基类模型转模型为返回类型为[[String:Any]]
public struct DailyStudyBaseListModel<T>:HandyJSON {
    
    public var code: Int?
    public var msg: String?
    public var msgDetail: String?
    public var data: [T]?
    
    public init() {
        
    }
}
// MARK: - 基类模型转模型为返回类型为bool 或者是 其他的 
public struct DailyStudyBaseResultModel:HandyJSON {
    
    public var code: Int?
    public var msg: String?
    public var msgDetail: String?
    public var data: Any?
    
    public init() {
        
    }
}


// MARK: - 处理分页查询返回的模型
public struct SDBasePageModel<T:HandyJSON>: HandyJSON {
    
    public var pageNum: Int?
    public var hasNextPage: Bool?
    public var list :[T]?
    
    public init() {
        
    }
}
import Foundation
import SDBasicProject

let width = (SDJG_ScreenWidth - 20 - 18 - 18 - 40) / 3

class JHAdressModel: HandyJSON {
    var title : String  = ""
    var list: [JHAdressCityModel] = []
    required init() {
    }
}

class JHAdressCityModel: HandyJSON {
    var c_province : String  = ""
    var c_pinyin : String  = ""
    var c_code : String  = ""
    var selected: Bool = false
//    var label : String  = ""
    var value : String  = ""
    
    var height: CGFloat = 0.0
    required init() {
        
    }
    
    var label: String? {
        didSet{
            let liveNameHeight = self.label?.extGetHeightByWidth(with: self.label ?? "" , width: width, attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: SDAuRate(value: 17), weight: .medium)])
            self.height =  8 + (liveNameHeight ?? 0) + 8
        }
    }
}


//赋值
    
    public var model: JHAdressCityModel? {
        didSet{
            nameLabel.text = model?.label ?? ""
            if model?.label?.isEmpty == true{
                self.layer.borderColor = UIColor.clear.cgColor
                self.backgroundColor = .clear
                self.isUserInteractionEnabled = false
            }else{
                self.isUserInteractionEnabled = true
                if model?.selected == true {
                    nameLabel.textColor = .extColorWithHex("#E72026")
                    self.backgroundColor = .extColorWithHex("#FFE3E1")
                    self.layer.borderColor = UIColor.extColorWithHex("#E72227").cgColor
                }else{
                    nameLabel.textColor = .extColorWithHex("#333333")
                    self.backgroundColor = .extColorWithHex("#F7F7F7")
                    self.layer.borderColor = UIColor.extColorWithHex("#F7F7F7").cgColor
                }
            }
         
        }
    }
    
//
//  JHGoldMoldel.swift
//  SDHuaYangFriendProject
//
//  Created by JH on 2023/1/10.
//

import Foundation
import SDBasicProject

class JHGoldMoldel: HandyJSON {
    var creditNum:  String? //当前金币数量
    // 列表数据
    var productList: [JHGoldProductMoldel] = []
    required init() {
    }
}

class JHGoldProductMoldel: HandyJSON {
    var productSkuId: String? //商品ID
    var availableCredit: String?//可获得金币
    var salePrice:String? //商品价格
    
    var selected: Bool = false
    required init() {
    }
}

class JHGoldOrderMoldel: HandyJSON {
    var orderNo: String? //商品ID
    required init() {
    }
}

class JHGoldDesMoldel: HandyJSON {
    var amount: String?
    var createTime: String?
    var eventName:String?
    var remark: String?
    var type: String?
    var totalAmount: String?
    
    required init() {
    }
}

//
//  StationModel.swift
//  SDPatternStationProject
//
//  Created by lanlan on 2022/3/9.
//

import SDBasicProject

// MARK: - 站长列表
class StationMasterList :HandyJSON {
    ///小站名称
    var siteName:String = ""
    ///站长名称
    var masterName:String = ""
    ///站长头像
    var headImgUrl:String = ""
    ///站长主键
    var id:Int = 0
    ///是否有直播标记 0: 没有 1:有
    var  liveFlag:Bool =  false
    ///站长等级 0: 金牌站长
    var level:Int = 0
    ///个人简介
    var personalProfile:String = ""
    /// 关注数量
    var attentionNum:Int = 0
    
    /// 关注数量格式化
    var formatAttentionNum:String = ""
    ///  点赞数量
    var thumbNum:Int = 0
    ///格式化点赞数量
    var formatThumbNum:String = ""
    ///0: 未置顶  1:置顶
    var stickieType:Bool = false
    
    ///0:未关注 1:已关注
    var attentionType:Bool = false
    
    //默认选中
    var isSeleted = false
    
    required init() {
        
    }
    
    func didFinishMapping() {
        
        formatAttentionNum = formatDecimals(num: attentionNum, separate: 10000)
        
        formatThumbNum = formatDecimals(num: thumbNum, separate: 10000)
       
    }
    
    public func formatDecimals(num:Int,separate:Int) -> String {
        if num < 10000 {
            return "\(num)"
        }
        let intVal  = num / separate
        let doubleVal = num % separate
        let suffixValue = doubleVal / 1000
        if suffixValue == 0 {
            return "\(intVal)w+"
        }
        return "\(intVal).\(suffixValue)w+"
    }
}


struct StationMasterDetailModel :HandyJSON {
    
    var course:StationMasterDetailCourseModel?
    var groupInfo:StationMasterDetailGroupInfoModel?
    
    //站长信息 本地带的
    var siteInfo:StationMasterList?
    //关注人数
    var memberNum:Int = 0
    ///关注状态
    var attentionType:Bool = false
}

struct StationMasterDetailGroupInfoModel :HandyJSON {
    
    var itemImGroup:[StationMasterDetailGroupModel]?
    var publicImGroup:StationMasterDetailGroupModel?
}

struct StationMasterDetailCourseModel :HandyJSON {
    
    var courseFlag:Int = 0
    var courseId:String = ""
    var coverPic:String = ""
    var headImgUrl:String = ""
    var lecturerName:String = ""
    var liveId:Int = 0
    var liveName:String = ""
    var liveStartTime:Int = 0
    var liveStatus:Int = 0
    var siteId:Int = 0
    var skuId:Int = 0
    var skuName:String = ""
    var videoType:Int = 0
    
}

struct StationMasterDetailGroupModel :HandyJSON {
    
    var imGroupId:String = ""
    var imGroupName:String = ""
    //0:加锁  1:解锁
    var lockStatus:Int = 0
    
    ///商城需要的id
    var productSpuId:Int = 0
    var productSkuId:Int = 0
    var siteId:String = ""
    var subBrandId:String = ""
    var distributeSubBrandId:String = ""
    
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容