Swift 将 JSON 解析为模型对象

Posted

技术标签:

【中文标题】Swift 将 JSON 解析为模型对象【英文标题】:Swift parse JSON to Model Object 【发布时间】:2018-04-19 20:49:42 【问题描述】:

我在这里有这个方法,它完全按照我的需要返回了我的数据:

func getTestData() -> [Any]?
    
        return [GradingData(lot: "lot", andColumns: "andColumns", SLAIssuedFinalGrading: true, SLAIssuedFinalGradingDate: "SLAIssuedFinalGradingDate", CityApprovalIssued: true, CityCommentReceived: false, GradingRepair: "GradingRepair", CurbStopRepair: "CurbStopRepair", SplashPadDownSpout: "SplashPadDownSpout", RYCBOtherRepairs: "RYCBOtherRepairs", Comments: "Comments", columnCamera: "", DepositReceived: false), GradingData(lot: "lot", andColumns: "andColumns2", SLAIssuedFinalGrading: false, SLAIssuedFinalGradingDate: "SLAIssuedFinalGradingDate", CityApprovalIssued: false, CityCommentReceived: false, GradingRepair: "GradingRepair", CurbStopRepair: "CurbStopRepair", SplashPadDownSpout: "SplashPadDownSpout", RYCBOtherRepairs: "RYCBOtherRepairs", Comments: "Comments", columnCamera: "", DepositReceived: false)]
    

现在我正在尝试调用 API 并返回完全相同的结构:

func GetLandGradingData(_ community: String, completion: @escaping (_ result: [GradingData]) -> Void)
    

        let escapedCommunity = community.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)

        let urlComponents = NSURLComponents(string: webservice + "?community=" + escapedCommunity!);
        urlComponents?.user = appDelegate.username;
        urlComponents?.password = appDelegate.password;

        let url = urlComponents?.url;

        let returnedData = [GradingData]()

        URLSession.shared.dataTask(with: url!, completionHandler: 
            (data, response, error) in

            if(error != nil)

                completion(returnedData)

            else

                do

                    let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [GradingData]

                    OperationQueue.main.addOperation(

                        completion(json)

                    )

                catch let error as NSError

                    print(error)
                    completion(returnedData)

                
            

        ).resume()

    

我的问题是方法返回的结构与 getTestData 方法不同,附上 getTestData 返回的内容和此 api 方法调用返回的内容的屏幕截图。

这是我的全班:

@objcMembers class GradingData : NSObject 

    /**
     Define lot Column String
     */
    var lot: String?
    /**
     Define Address Column String
     */
    var address: String?
    /**
     Define SLA Issued Final Grading Column String
     */
    var SLAIssuedFinalGrading = false
    /**
     Define SLA Issued Final Grading Date Column String
     */
    var SLAIssuedFinalGradingDate: String?
    /**
     Define City Approval Issued Column String
     */
    var CityApprovalIssued = false
    /**
     Define City Comment Received Column String
     */
    var CityCommentReceived = false
    /**
     Define Grading Repair Column String
     */
    var GradingRepair: String?
    /**
     Define Curb Stop Repair Column String
     */
    var CurbStopRepair: String?
    /**
     Define Splash Pad or Down Spout Column String
     */
    var SplashPadDownSpout: String?
    /**
     Define RYCB or Other Repairs Column String
     */
    var RYCBOtherRepairs: String?
    /**
     Define Comments Column String
     */
    var Comments: String?
    /**
     Define Camera Column String
     */
    var columnCamera: String?
    /**
     Define Deposit Received Column String
     */
    var DepositReceived = false


    /**
     Inital call to class
     */
    init(lot: String?, andColumns address: String?, SLAIssuedFinalGrading: Bool?, SLAIssuedFinalGradingDate: String?, CityApprovalIssued: Bool?, CityCommentReceived: Bool?, GradingRepair: String?, CurbStopRepair: String?, SplashPadDownSpout: String?, RYCBOtherRepairs: String?, Comments: String?, columnCamera: String?, DepositReceived: Bool?) 

        super.init()

        //Set lot string

        self.lot = lot

        //Set Address Column string

        self.address = address

        //Set SLA Issued Final Grading Column string

        self.SLAIssuedFinalGrading = SLAIssuedFinalGrading!

        //Set SLA Issued Final Grading Date Column string

        self.SLAIssuedFinalGradingDate = SLAIssuedFinalGradingDate

        //Set City Approval Issued Column string

        self.CityApprovalIssued = CityApprovalIssued!

        //Set City Comment Received Column string

        self.CityCommentReceived = CityCommentReceived!

        //Set Grading Repair Column string

        self.GradingRepair = GradingRepair

        //Set Curb Stop Repair Column string

        self.CurbStopRepair = CurbStopRepair

        //Set Splash Pad or Down Spout Column string

        self.SplashPadDownSpout = SplashPadDownSpout

        //Set RYCB or Other Repairs Column string

        self.RYCBOtherRepairs = RYCBOtherRepairs

        //Set Comments Column string

        self.Comments = Comments

        //Set Camera Column string

        self.columnCamera = columnCamera

        //Set Deposit Received Column string

        self.DepositReceived = DepositReceived!

    

这是我的 API 数据:

<Reports>
<CityApprovalIssued>false</CityApprovalIssued>
<CityCommentReceived>false</CityCommentReceived>
<Comments></Comments>
<CurbStopRepair></CurbStopRepair>
<DepositReceived>false</DepositReceived>
<GradingRepair></GradingRepair>
<RYCBOtherRepairs></RYCBOtherRepairs>
<SLAIssuedFinalGrading>false</SLAIssuedFinalGrading>
<SLAIssuedFinalGradingDate/>
<SplashPadDownSpout></SplashPadDownSpout>
<address>123 Fake Street</address>
<lot>A0001</lot>
</Reports>
<Reports>
<CityApprovalIssued>false</CityApprovalIssued>
<CityCommentReceived>false</CityCommentReceived>
<Comments></Comments>
<CurbStopRepair></CurbStopRepair>
<DepositReceived>false</DepositReceived>
<GradingRepair></GradingRepair>
<RYCBOtherRepairs></RYCBOtherRepairs>
<SLAIssuedFinalGrading>false</SLAIssuedFinalGrading>
<SLAIssuedFinalGradingDate/>
<SplashPadDownSpout></SplashPadDownSpout>
<address>125 Fake Street</address>
<lot>A0002</lot>
</Reports>

这是来自 ASP.NET MVC API 控制器:&lt;ArrayOfReports xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/App.Models"&gt;

这就是我这样做时得到的:

let string = String(data: data!, encoding: .utf8)

print(string as Any)

Optional("[\"lot\":\"A0001\",\"address\”:\”123 Fake Street\”,\”SLAIssuedFinalGrading\":false,\"SLAIssuedFinalGradingDate\":\"\",\"CityApprovalIssued\":false,\"CityCommentReceived\":false,\"GradingRepair\":\"                                                                                                                                                                                                                                                               \",\"CurbStopRepair\":\"                                                                                                                                                                                                                                                               \",\"SplashPadDownSpout\":\"                                                                                                                                                                                                                                                               \",\"RYCBOtherRepairs\":\"                                                                                                                                                                                                                                                               \",\"Comments\":\"                                                                                                                                                                                                                                                               \",\"DepositReceived\":false,\"lot\":\"A0002\",\"address\":\"125 Fake Street\",\"SLAIssuedFinalGrading\":false,\"SLAIssuedFinalGradingDate\":\"\",\"CityApprovalIssued\":false,\"CityCommentReceived\":false,\"GradingRepair\":\"                                                                                                                                                                                                                                                               \",\"CurbStopRepair\":\"                                                                                                                                                                                                                                                               \",\"SplashPadDownSpout\":\"                                                                                                                                                                                                                                                               \",\"RYCBOtherRepairs\":\"                                                                                                                                                                                                                                                               \",\"Comments\":\"                                                                                                                                                                                                                                                               \",\"DepositReceived\":false]")

【问题讨论】:

在哪里解析 json 并创建模型? 模型是另一个文件中的类。 不能使用JSONSerialization 将JSON 直接反序列化为模型(除非GradingData 是JSON 字典或数组的类型别名)。你可以用JSONDecoderDecodable 协议来做到这一点 请举例? 这是 XML,不是 JSON。 【参考方案1】:

首先响应数据确实是JSON,XML输出有点混乱。

您可能不需要从NSObject 继承的类,在大多数情况下,尤其是在 ios 中,结构就足够了。

除了键columnCamera之外的所有键都存在于所有记录中,所以将所有属性声明为不带默认值的非可选属性,将columnCamera声明为可选,如果不是,则将所有属性声明为常量(let)会改变的。

不需要显式的init 方法。

添加了CodingKeys 以将大部分键转换为小写。

struct GradingData : Decodable 

    private enum CodingKeys : String, CodingKey 
        case lot, address, SLAIssuedFinalGrading
        case SLAIssuedFinalGradingDate
        case cityApprovalIssued = "CityApprovalIssued"
        case cityCommentReceived = "CityCommentReceived"
        case gradingRepair = "GradingRepair"
        case curbStopRepair = "CurbStopRepair"
        case splashPadDownSpout = "SplashPadDownSpout"
        case RYCBOtherRepairs, comments = "Comments"
        case columnCamera, depositReceived = "DepositReceived"
    

    let lot: String
    let address: String
    let SLAIssuedFinalGrading : Bool
    let SLAIssuedFinalGradingDate: String
    let cityApprovalIssued : Bool
    let cityCommentReceived : Bool
    let gradingRepair : String
    let curbStopRepair : String
    let splashPadDownSpout : String
    let RYCBOtherRepairs : String
    let comments : String
    let columnCamera : String?
    let depositReceived : Bool


然后解码 JSON

....

var returnedData = [GradingData]()
URLSession.shared.dataTask(with: url!, completionHandler: (data, response, error) in
     if error != nil 
         print(error!)
         completion(returnedData)
      else 
         do 
            returnedData = try JSONDecoder().decode([GradingData].self, from: data!)
            DispatchQueue.main.async 
                completion(returnedData)
            
          catch 
             print(error)
             completion(returnedData)
         
     
 ).resume()

【讨论】:

【参考方案2】:

只需将另一个 init 添加到您的 GradingData 模型类中,如下所示:

convenience init(data: [String : Any]) 
 init(lot: data["lot"] as? String,
 andColumns: data["address"] as? String,
 SLAIssuedFinalGrading: data["SLAIssuedFinalGrading"] as? Bool,
 SLAIssuedFinalGradingDate: data["SLAIssuedFinalGradingDate"] as? String, 
 CityApprovalIssued: data["CityApprovalIssued"] as? Bool,
 CityCommentReceived: data["CityCommentReceived"] as? Bool,
 GradingRepair: data["GradingRepair"] as? String,
 CurbStopRepair: data["CurbStopRepair"] as? String,
 SplashPadDownSpout: data["SplashPadDownSpout"] as? String,
 RYCBOtherRepairs: data["RYCBOtherRepairs"] as? String,
 Comments: data["Comments"] as? String,
 columnCamera: data["columnCamera"] as? String,
 DepositReceived: data["DepositReceived"] as? Bool)

并且在 func GetLandGradingData(...) 方法中用这些替换 do 大括号:

let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [[String : Any]]

                    OperationQueue.main.addOperation(
                       let gradingDatas = json.map  GradingData(data: $0) 

                        completion(gradingDatas)

                    )

【讨论】:

【参考方案3】:

只是因为您要求提供一个示例(但没有提供您的 GradingData,这会使这变得非常抽象):

struct GradingData : Codable 
    // here is the meat, but you did not show ...


...

let grades = JSONDecoder().decode(GradingData.self, from: data)

Swift 是我遇到的第一个能做到这一点的语言。它真的只是归结为实现一个接口(大部分时间实际上没有做任何有趣的事情,同时仍然提供以最少的努力完成所有必要事情的可能性)并获得一个免费 em> 对象的 JSON 解析器(不仅仅是一些哈希垃圾字典)。

(这被称为“进步”:-)

【讨论】:

您的代码出现此错误:在参数类型“GradingData.Type”中,“GradingData”不符合预期的“Decodable”类型 我添加了我的整个班级 你得到的错误告诉你你需要做什么。现在GradingData 不符合Decodable,您需要使GradingData 确实符合Decodable(或Codable,如果您认为您需要发送数据以及接收它)。如果你谷歌“Swift Decodable”,你会得到很多关于你需要做什么的信息。简短版本是将@objcMembers class GradingData : NSObject 更改为@objcMembers class GradingData : NSObject, Codable@objcMembers class GradingData : NSObject, Decodable 如果您使用@vadian 的版本,您的代码将大大简化。 structs 已经生成了适当的初始化程序,如果您在创建成绩后没有必须修改成绩,则应避免使用var 并帮自己一个忙,并尽可能省略选项。这将大大减少出错的可能性(尽管 Swift 非常擅长指出这一点)并改进可能的优化。试试看,它可以就这么简单(这就是 Swift 的重点,尤其是 Codable)。【参考方案4】:

通过自定义类或 nsxmlparser 将 XML 转换为字典。 或者如果你有 Grading 数组,那么

var modelArr = [Grading]()

var model = Grading.init(fromDictionary dictionary: xmlParsedDic) modelArr.append(model)

使用这个模型类

import Foundation

class Grading : NSObject, NSCoding

var cityApprovalIssued : Bool!
var cityCommentReceived : Bool!
var comments : String!
var curbStopRepair : String!
var depositReceived : Bool!
var gradingRepair : String!
var rYCBOtherRepairs : String!
var sLAIssuedFinalGrading : Bool!
var sLAIssuedFinalGradingDate : String!
var splashPadDownSpout : String!
var address : String!
var lot : String!


init(fromDictionary dictionary: [String:Any])
    cityApprovalIssued = dictionary["CityApprovalIssued"] as? Bool
    cityCommentReceived = dictionary["CityCommentReceived"] as? Bool
    comments = dictionary["Comments"] as? String
    curbStopRepair = dictionary["CurbStopRepair"] as? String
    depositReceived = dictionary["DepositReceived"] as? Bool
    gradingRepair = dictionary["GradingRepair"] as? String
    rYCBOtherRepairs = dictionary["RYCBOtherRepairs"] as? String
    sLAIssuedFinalGrading = dictionary["SLAIssuedFinalGrading"] as? Bool
    sLAIssuedFinalGradingDate = dictionary["SLAIssuedFinalGradingDate"] as? String
    splashPadDownSpout = dictionary["SplashPadDownSpout"] as? String
    address = dictionary["address"] as? String
    lot = dictionary["lot"] as? String


func toDictionary() -> [String:Any]

    var dictionary = [String:Any]()
    if cityApprovalIssued != nil
        dictionary["CityApprovalIssued"] = cityApprovalIssued
    
    if cityCommentReceived != nil
        dictionary["CityCommentReceived"] = cityCommentReceived
    
    if comments != nil
        dictionary["Comments"] = comments
    
    if curbStopRepair != nil
        dictionary["CurbStopRepair"] = curbStopRepair
    
    if depositReceived != nil
        dictionary["DepositReceived"] = depositReceived
    
    if gradingRepair != nil
        dictionary["GradingRepair"] = gradingRepair
    
    if rYCBOtherRepairs != nil
        dictionary["RYCBOtherRepairs"] = rYCBOtherRepairs
    
    if sLAIssuedFinalGrading != nil
        dictionary["SLAIssuedFinalGrading"] = sLAIssuedFinalGrading
    
    if sLAIssuedFinalGradingDate != nil
        dictionary["SLAIssuedFinalGradingDate"] = sLAIssuedFinalGradingDate
    
    if splashPadDownSpout != nil
        dictionary["SplashPadDownSpout"] = splashPadDownSpout
    
    if address != nil
        dictionary["address"] = address
    
    if lot != nil
        dictionary["lot"] = lot
    
    return dictionary


@objc required init(coder aDecoder: NSCoder)

     cityApprovalIssued = aDecoder.decodeObject(forKey: "CityApprovalIssued") as? Bool
     cityCommentReceived = aDecoder.decodeObject(forKey: "CityCommentReceived") as? Bool
     comments = aDecoder.decodeObject(forKey: "Comments") as? String
     curbStopRepair = aDecoder.decodeObject(forKey: "CurbStopRepair") as? String
     depositReceived = aDecoder.decodeObject(forKey: "DepositReceived") as? Bool
     gradingRepair = aDecoder.decodeObject(forKey: "GradingRepair") as? String
     rYCBOtherRepairs = aDecoder.decodeObject(forKey: "RYCBOtherRepairs") as? String
     sLAIssuedFinalGrading = aDecoder.decodeObject(forKey: "SLAIssuedFinalGrading") as? Bool
     sLAIssuedFinalGradingDate = aDecoder.decodeObject(forKey: "SLAIssuedFinalGradingDate") as? String
     splashPadDownSpout = aDecoder.decodeObject(forKey: "SplashPadDownSpout") as? String
     address = aDecoder.decodeObject(forKey: "address") as? String
     lot = aDecoder.decodeObject(forKey: "lot") as? String



@objc func encode(with aCoder: NSCoder)

    if cityApprovalIssued != nil
        aCoder.encode(cityApprovalIssued, forKey: "CityApprovalIssued")
    
    if cityCommentReceived != nil
        aCoder.encode(cityCommentReceived, forKey: "CityCommentReceived")
    
    if comments != nil
        aCoder.encode(comments, forKey: "Comments")
    
    if curbStopRepair != nil
        aCoder.encode(curbStopRepair, forKey: "CurbStopRepair")
    
    if depositReceived != nil
        aCoder.encode(depositReceived, forKey: "DepositReceived")
    
    if gradingRepair != nil
        aCoder.encode(gradingRepair, forKey: "GradingRepair")
    
    if rYCBOtherRepairs != nil
        aCoder.encode(rYCBOtherRepairs, forKey: "RYCBOtherRepairs")
    
    if sLAIssuedFinalGrading != nil
        aCoder.encode(sLAIssuedFinalGrading, forKey: "SLAIssuedFinalGrading")
    
    if sLAIssuedFinalGradingDate != nil
        aCoder.encode(sLAIssuedFinalGradingDate, forKey: "SLAIssuedFinalGradingDate")
    
    if splashPadDownSpout != nil
        aCoder.encode(splashPadDownSpout, forKey: "SplashPadDownSpout")
    
    if address != nil
        aCoder.encode(address, forKey: "address")
    
    if lot != nil
        aCoder.encode(lot, forKey: "lot")
    


【讨论】:

以上是关于Swift 将 JSON 解析为模型对象的主要内容,如果未能解决你的问题,请参考以下文章

Swift 将对象解析为 JSON 对象

如何将模型对象转换为 JSON - Swift 4

Swift 4 将字符串解析为 json 对象

在 Swift 3 中将 JSON 对象解析为 NSArray

如何在 xcode (swift) 中从 Json Schema / Json 生成模型对象?

在 SWIFT 中将 JSON 解析为字典对象