Swiftの下の辞書(json)とモデルの変換

11989 ワード

一、swiftの下でOCフレームを使用する
OCの下で辞書とモデルの変換は非常に使いやすいサードパーティのフレームワーク(YYMode,MJExtensionなど)がありますが、もちろんSwiftはこの2つのフレームワークを参考にすることもできますが、モデルを作成する際にNSObjectから継承し、キーワード@objcMembersを加えるだけでいいです.
@objcMembers class CDBaseModel: NSObject, YYModel {
    //swift   YYModel
    var name:String = ""
    var age:Int = 0
    var uid:String = ""
    
    static func modelCustomPropertyMapper() -> [String : Any]? {
        return [
            "uid":"id",
        ]
    }
}

swift原生Codableプロトコルの使用
1、オリジナルのCodableプロトコルを使用する場合、現在のクラス継承プロトコルCodableが必要であり、swiftの列挙、構造体、クラスのみがこのプロトコルを継承することができる.これっぽっちのことはOCではできません.
enum Gender:String, Decodable {
    case male
    case female
    case other
}

struct BookModel:Codable {
    
    var name:String = ""
    var price:Float = 0
    
}

class PersonModel: NSObject, Codable {
    
    var name:String = ""
    
}

2.Codableプロトコルを使用する場合は、モデルと元のjsonデータとの間のkey値の一貫性を確保する必要があります.json辞書には以下のようなものが入っています(ここで私が使っている辞書)
private var dictionary:[String:Any] = [:]

        self.dictionary["name"] = "  "
        self.dictionary["age"] = 9
        self.dictionary["sex"] = true

モデルは次のように実現する必要があります(もちろんこれは理想的な状態です)
struct UserModel:Codable {
    var name:   String = ""
    var age:    Int = 0
    var sex:    Bool = false
}

3、モデルに(name_op)のようなフィールドがあり、元のjsonデータに値がない場合、モデルは次のようになります.
struct UserModel:Codable {
    var name:   String = ""
    var age:    Int = 0
    var sex:    Bool = false
    var name_op:    String = ""
}

モデルに解析すると失敗します.不確定な値をオプションタイプとして定義する必要があります.元のjsonデータがこのフィールドを持っているかどうかにかかわらず、解析に成功します.もしそうでなければ、この属性はnilです.
struct UserModel:Codable {
    var name:   String = ""
    var age:    Int = 0
    var sex:    Bool = false
    var name_op:    String?
}

4、モデルが設計されている場合、元のjsonデータの中にフィールドとあなたのフィールドが衝突している場合(属性がnickで元のデータがnick_nameである場合)、この値は解析に成功しません.オプションであれば、少なくとも成功します.そうしないと失敗します.解決策はenum CodingKeys:String,CodingKey{}というマッピング関係を実現することである.
struct UserModel:Codable {
    var name:   String = ""
    var age:    Int = 0
    var sex:    Bool = false
    var name_op:    String?
    var nick: String?
      enum CodingKeys: String, CodingKey {
        case name
        case age
        case sex
        case name_op 
        case nick = "nick_name" 
    }
}

5、もしあなたのモデルの中にネスト関係があるならば、例えばあなたのモデルの中に他のモデルあるいはモデルの配列があるならば、ネストしたモデルの中で依然として対応するプロトコルを実現していることを保証して、現在のモデルと同じようにすればいいです.
struct UserModel:Codable {
    var name:   String = ""
    var age:    Int = 0
    var sex:    Bool = false
    var name_op:    String?
    var nick: String?
    //    (             Codable    )
    var animo_op:   AnimalModel?
    var books_op:   [BookModel]?

      enum CodingKeys: String, CodingKey {
        case name
        case age
        case sex
        case name_op 
        case nick = "nick_name" 
    }
}

6、モデルの中と元のデータの中のタイプが統一されていないと、この解析も失敗に終わります.この場合、現在のプロパティに適したクラスをカスタマイズする必要があります.最も一般的なのは(BoolとInt,IntとString)これらがバックグラウンドの若型言語で区別されていないことです.だからどんなタイプなのか分からないし、勝手に定義したら失敗します.BoolまたはIntのいずれかのタイプを定義します.
struct TIntBool:Codable { 
    var int:Int {
        didSet {
            if int == 0 { self.bool = false
            } else { self.bool = true }
        }
    } 
    var bool:Bool {
        didSet {
            if bool { self.int = 1
            } else { self.int = 0 }
        }
    } 
    //     (          )
    init(from decoder: Decoder) throws {
        let singleValueContainer = try decoder.singleValueContainer()
        if let intValue = try? singleValueContainer.decode(Int.self) {
            self.int = intValue
            self.bool = (intValue != 0)
        } else if let boolValue = try? singleValueContainer.decode(Bool.self) {
            self.bool = boolValue
            if boolValue { self.int = 1
            } else { self.int = 0 }
        } else {
            self.bool = false
            self.int = 0
        }
    } 
}

以下はIntまたはStringタイプの
struct TStrInt: Codable {
    var int:Int {
        didSet {
            let stringValue = String(int)
            if  stringValue != string {
                string = stringValue
            }
        }
    } 
    var string:String {
        didSet {
            if let intValue = Int(string), intValue != int {
                int = intValue
            }
        }
    } 
    //     (          )
    init(from decoder: Decoder) throws {
        let singleValueContainer = try decoder.singleValueContainer() 
        if let stringValue = try? singleValueContainer.decode(String.self)
        {
            string = stringValue
            int = Int(stringValue) ?? 0
            
        } else if let intValue = try? singleValueContainer.decode(Int.self)
        {
            int = intValue
            string = String(intValue);
        } else
        {
            int = 0
            string = ""
        }
    }
}

モデルを設計する際、以下のように値を付けます.
struct UserModel:Codable {
    var name:   String = ""
    var age:    Int = 0
    var sex:    Bool = false
    var name_op:    String?
    var nick: String?
    //    (             Codable    )
    var animo_op:   AnimalModel?
    var books_op:   [BookModel]?
  
    //       (       Int,      String,                         、   、        )
    var stringInt:  TStrInt?
    var boolInt:    TIntBool?

      enum CodingKeys: String, CodingKey {
        case name
        case age
        case sex
        case name_op 
        case nick = "nick_name" 
        case  animo_op
        case  animo_op
        case  stringInt
        case boolInt
    }
}

上記の手順を完了すれば、辞書モデルの変換の大きな部分の問題を基本的に解決することができます.私がデザインしたモデルを貼り付けます.
struct UserModel:Codable {
    
    //1、   (                        ,      )
    var name:   String = ""
    var age:    Int = 0
    var sex:    Bool = false
    var weight: Double = 0
    var height: Float = 0
    var animo:  AnimalModel = AnimalModel.init()
    var books:  [BookModel] = []
    
    //2、   (               ,         ,        )
    var name_op:    String?
    var age_op:     Int?
    var sex_op:     Bool?
    var weight_op:  Double?
    var height_op:  Float?
    var animo_op:   AnimalModel?
    var books_op:   [BookModel]?
    
    //3、key      (          key   ,          ‘CodingKeys’       )
    var nick:       String = ""     //       nick_name
    var nick_op:    String?         //       nick_optional
    
    //4、       (       Int,      String,                         、   、        )
    var birthday:   TStrInt?
    var stringInt:  TStrInt?
    var birthday_op:TStrDou?
    var stringDou:  TStrDou?
    var boolInt:    TIntBool?
    var intBool:    TIntBool?
    
    
    //   key     。
    enum CodingKeys: String, CodingKey {
        case name
        case age
        case sex
        case weight
        case height
        case animo
        case books
        
        case name_op
        case age_op
        case sex_op
        case weight_op
        case height_op
        case animo_op
        case books_op
        
        case nick = "nick_name"
        case nick_op = "nick_optional"
         
        case birthday
        case stringInt
        case birthday_op
        case stringDou
        case boolInt
        case intBool
        
    }
     
}

struct AnimalModel:Codable {
    
    var name:String = ""
    
}

struct BookModel:Codable {
    
    var name:String = ""
    var price:Float = 0
    
}

作成された辞書
    func setupData() {
        self.dictionary["name"] = "  "
        self.dictionary["age"] = 9
        self.dictionary["sex"] = true
        self.dictionary["weight"] = 177.7
        self.dictionary["height"] = 77.7
        self.dictionary["animo"] = ["name":"  "]
        self.dictionary["books"] = [["name":"  ", "price":22],["name":"  ", "price":33]]
        
        self.dictionary["name_op"] = "tiangwang"
        self.dictionary["age_op"] = 11
        self.dictionary["sex_op"] = false
        self.dictionary["weight_op"] = 22.2
        self.dictionary["height_op"] = 22.2
        self.dictionary["animo_op"] = ["name":"  "]
        self.dictionary["books_op"] = [["name":"  ", "price":11.5],["name":"  ", "price":11.6]]
        
        self.dictionary["nick_name"] = "      "
        self.dictionary["nick_optional"] = "      "
        
        self.dictionary["birthday"] = 20000102
        self.dictionary["stringInt"] = "123"
        self.dictionary["birthday_op"] = 123456.5
        self.dictionary["stringDou"] = "321"
        self.dictionary["boolInt"] = true
        self.dictionary["intBool"] = 1
    }

三、モデル変換用JSOnDecoder、JSOnDecoderはDataタイプが必要なので、まずjsonをDataに変換する.
guard let data = try? JSONSerialization.data(withJSONObject: self.dictionary, options: JSONSerialization.WritingOptions.init()) else {
            print("  JSON  ")
            return
        }
        guard let model = try? JSONDecoder.init().decode(UserModel.self, from: data)  else {
            print("model    ,)")
            return
        }
        print("model = \(model)")

四、共通するために、辞書モデルを変換するツールクラスを設計する.
struct CDModel {
    
    //     (json)   
    static public func jsonToModel(type:T.Type, json:Any) -> T? where T:Codable {
        
        guard let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []) else {
            return nil
        }
        guard let model = try? JSONDecoder.init().decode(type, from: jsonData) else {
            return nil
        }
        return model
    }
    
    //json       
    static public func jsonToModel(type:T.Type, array:[[String:Any]]) -> [T]? where T:Codable {
        
        guard let jsonData = try? JSONSerialization.data(withJSONObject: array, options: []) else {
            return nil
        }
        guard let result = try? JSONDecoder.init().decode([T].self, from: jsonData) else {
            return nil
        }
        return result
    }
    
    //     json   
    public static func modelToJson(toString model:T) -> String? where T:Encodable {

        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        guard let data = try? encoder.encode(model)else{
            return nil
        }
        guard let jsonStr = String(data: data, encoding: .utf8)else{
            return nil
        }
        return jsonStr
    }
    
    //     json  
    public static func modelToJson(toDictionary model:T) -> [String:Any]? where T:Encodable{

        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        guard let data = try? encoder.encode(model) else {
            return nil
        }
        guard let dict = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves)as? [String:Any] else {
            return nil
        }
        return dict
    }

}

使用
        guard let model = CDModel.jsonToModel(type: UserModel.self, json: self.dictionary) else {
            return
        }
        print("model = \(model)")
        
        let array = CDModel.jsonToModel(type: UserModel.self, array: [self.dictionary])
        print("array = \(array ?? [])")