【问题标题】:How do I parse this nested JSON using Codable with Swift?如何使用 Codable 和 Swift 解析这个嵌套的 JSON?
【发布时间】: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()

我需要能够访问用户属性,但我认为嵌套对我来说很困难。任何帮助都很棒!谢谢!!

【问题讨论】:

    标签: ios json swift codable


    【解决方案1】:

    首先:Catch 总是DecodingErrorprint 它。它会准确地告诉您出了什么问题

    发生错误是因为您忽略了根对象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
        猜你喜欢
        • 2018-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多