如何使用 Codable 和 Swift 解析这个嵌套的 JSON?
Posted
技术标签:
【中文标题】如何使用 Codable 和 Swift 解析这个嵌套的 JSON?【英文标题】:How do I parse this nested JSON using Codable with Swift? 【发布时间】:2019-07-21 09:15:07 【问题描述】:我正在尝试使用 Codable 解析这个 JSON:
"users": [
"id": 1,
"name": "Allen Carslake",
"userName": "acarslake0",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-07-08T00:00:00.000+0000"
,
"id": 2,
"name": "Revkah Antuk",
"userName": "rantuk1",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-07-05T00:00:00.000+0000"
,
"id": 3,
"name": "Mirna Saffrin",
"userName": "msaffrin2",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-05-19T00:00:00.000+0000"
,
"id": 4,
"name": "Haily Eilers",
"userName": "heilers3",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-06-28T00:00:00.000+0000"
,
"id": 5,
"name": "Oralie Polkinhorn",
"userName": "opolkinhorn4",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-06-04T00:00:00.000+0000"
]
我在此处将 URL 保持为私有,但它在上面返回 JSON。到目前为止,这是我的代码:
import UIKit
struct User: Codable
let id: Int
let name: String
let userName: String
let profileImage: String
let createdDate: String
struct Users: Codable
let users: String
let url = URL(string: "")!
URLSession.shared.dataTask(with: url) data, _, _ in
if let data = data
let users = try? JSONDecoder().decode([User].self, from: data)
print(users)
.resume()
我需要能够访问用户属性,但我认为嵌套对我来说很困难。任何帮助都很棒!谢谢!!
【问题讨论】:
【参考方案1】:首先:Catch
总是DecodingError
和print
它。它会准确地告诉您出了什么问题。
发生错误是因为您忽略了根对象Users
。如果你 decode(Users.self
,你的代码就可以工作。
我的建议:
将createdDate
解码为Date
添加适当的日期解码策略。
将profileImage
解码为URL
(免费)。
处理所有错误。
struct Root : Decodable // `Users` and `User` is too confusing
let users: [User]
struct User : Decodable
let id: Int
let name: String
let userName: String
let profileImage: URL
let createdDate: Date
URLSession.shared.dataTask(with: url) data, _, error in
if let error = error print(error); return
do
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let result = try decoder.decode(Root.self, from: data!)
for user in result.users
print(user.userName, user.id, user.createdDate)
catch
print(error)
.resume()
【讨论】:
【参考方案2】:json的根是字典而不是数组,你可以写一个根类但是没用,所以你需要
URLSession.shared.dataTask(with: url) data, _, _ in
do
if let data = data
let res = try JSONSerialization.jsonObject(with: data) as! [String:Any]
let usersData = try JSONSerialization.data(withJSONObject: res["users"])
let users = try JSONDecoder().decode([User].self, from: usersData)
print(users)
catch
print(error)
.resume()
【讨论】:
【参考方案3】:您的Users
结构(我将其重命名为UsersResponse
)应包含[User]
类型的users
属性。然后您可以执行以下操作:
struct UsersResponse: Codable
let users: [User]
URLSession.shared.dataTask(with: url) data, _, _ in
guard let data = data else return
if let users = try? JSONDecoder().decode(Users.self, from: data).users
users.forEach user in
print("A user called \(user.name) with an id of \(user.id).")
.resume()
【讨论】:
写UsersResponse
如果有更多的属性可以访问的话会有意义,但是因为它只是users
键所以不需要它
尽管如此,我更喜欢这个解决方案,而不是你的 JSONSerialization
@Sh_Khan。 :)
工作,谢谢!现在如何访问“id”和“profileImage”等用户属性?
@ReissZurbyk 如果你问这样的问题,那么你需要了解数组和使用循环,我可以建议The Swift Programming Language,它是学习 swift 甚至以后学习的绝佳资源
@JoakimDanielson。我添加了错误处理我的答案请检查。【参考方案4】:
请尝试以下代码。这对我有用。
模型类:
struct UsersResponse: Codable
let users: [User]
struct User: Codable
let id: Int
let name: String
let userName: String
let profileImage: String?
let createdDate: String
网络类:
public enum EndPoints: String
case prod = "ProdURL"
case test = "testURL"
public enum Result<T>
case success(T)
case failure(Error)
final public class Networking: NSObject
// MARK: - Private functions
private static func getData(url: URL,
completion: @escaping (Data?, URLResponse?, Error?) -> ())
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
/// fetchUsersResponse function will fetch the User Response and returns
/// Result<UsersResponse> as completion handler
public static func fetchUsersResponse(shouldFail: Bool = false, completion: @escaping (Result<UsersResponse>) -> Void)
var urlString: String?
if shouldFail
urlString = EndPoints.test.rawValue
else
urlString = EndPoints.prod.rawValue
guard let mainUrlString = urlString, let url = URL(string: mainUrlString) else return
Networking.getData(url: url) (data, response, error) in
if let error = error
completion(.failure(error))
return
guard let data = data, error == nil else return
do
let decoder = JSONDecoder()
//decoder.dateDecodingStrategy = .millisecondsSince1970
decoder.dateDecodingStrategy = .formatted(setDateFormat())
let json = try decoder.decode(UsersResponse.self, from: data)
completion(.success(json))
catch let error
completion(.failure(error))
func setDateFormat() -> DateFormatter
let dateFormat = DateFormatter()
dateFormat.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return dateFormat
【讨论】:
你的答案看起来有点过头了,但我想知道两件事,首先你为什么要让结构中的每个属性都是可选的,只有在你知道它们是可选的时候才让它们成为可选的,其次是什么哎呀是decoder.dateDecodingStrategy = .millisecondsSince1970
?
感谢@JoakimDanielson 的通知,我使用了我的模板代码之一。所以在上面的 JSON 结构中,没有日期格式,所以我可以删除 dateDecodingStrategy。对于结构中的可选属性,我曾经遵循这种做法来确保代码行为安全,以防 null/nil 值来自服务器或服务器响应中缺少任何键,然后它不会中断。
如果缺少强制密钥,那么它应该会中断,否则您以后可能会遇到无法预料的行为。我们都有自己的喜好,我认为让所有东西都可选是一个坏习惯。
@Animesh 实际上有是日期格式,但不是.millisecondsSince1970
。关于安全性,可选的DecodingError
和捕获的DecodingError
之间根本没有区别。在这两种情况下,解码过程都会失败,但在非可选情况下,您会收到一条全面的错误消息,您可以立即修复代码。
感谢@vadian 提供意见。你能告诉我应该用什么代替 .millisecondsSince1970以上是关于如何使用 Codable 和 Swift 解析这个嵌套的 JSON?的主要内容,如果未能解决你的问题,请参考以下文章