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 控制器:<ArrayOfReports xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/App.Models">
这就是我这样做时得到的:
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 字典或数组的类型别名)。你可以用JSONDecoder
和Decodable
协议来做到这一点
请举例?
这是 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 3 中将 JSON 对象解析为 NSArray