在 Swift 4 中使用 Decodable 和 CodingKeys 解析 JSON

Posted

技术标签:

【中文标题】在 Swift 4 中使用 Decodable 和 CodingKeys 解析 JSON【英文标题】:Parsing JSON using Decodable and CodingKeys in Swift 4 【发布时间】:2017-10-18 22:40:18 【问题描述】:

我正在尝试解析从 URL 检索的大型 JSON 字符串。我用来测试的 JSON 如下:

let json = """

"feed": 
    "title": "Harry Potter",
    "test": "I dont want this value",
    "results": [
        
        "author": "JK Rowling",
        "artworkURL": "A url",
        "genres": [
            
                "name": "Fantasy"
            ,
            
                "name": "Scifi"
            
        ],
            "name": "Goblet of Fire",
            "releaseDate": "2000-07-08"
        ,
        
        "author": "JK Rowling",
        "artworkURL": "A url",
        "genres": [
            
                "name": "Fantasy"
            ,
            
                "name": "Scifi"
            
            ],
            "name": "Half Blood Prince",
            "releaseDate": "2009-07-15"
            
        ]
    

""".data(using: .utf8)!

我有几个数据结构可以将数据放入:

struct Genre: Decodable 
    let name: String


struct Book: Decodable 
    let author: String
    let artworkURL: URL
    let genres: [Genre]
    let name: String
    let releaseDate: String


struct BookCollection 
    let title: String
    let books: [Book]

    enum CodingKeys: String, CodingKey 
        case feed
    

    enum FeedKeys: String, CodingKey 
        case title, results
    

    enum ResultKeys: String, CodingKey 
        case author, artworkURL, genres, name, releaseDate
    

    enum GenreKeys: String, CodingKey 
        case name
    


extension BookCollection: Decodable 
    init(from decoder: Decoder) throws 
        let values = try decoder.container(keyedBy: CodingKeys.self)

        let feed = try values.nestedContainer(keyedBy: FeedKeys.self, 
    forKey: .feed)
        self.title = try feed.decode(String.self, forKey: .title)
        self.books = try feed.decode([Track].self, forKey: .results)
    

然后我打印出这样的信息:

do 
    let response = try JSONDecoder().decode(BookCollection.self, from: json)
    for book in response.books 
        print(book.genres)
    
 catch 
    print(error)

它可以成功打印除类型之外的所有信息。这给了我一系列流派,但我做不到 book.genres.name 访问名称。我必须使用: book.genres[0] 它只给了我第一个索引的结果。

有没有一种方法可以完善我的 BookCollection 扩展中的 JSON 解码,然后使用 book.genres.name

谢谢

【问题讨论】:

明确一点,book.genres.name 应该返回什么值? @PauloMattos 它应该为每本书返回一个数组。第一本书是FantasyScifi @DominicPilla 您可以将只读计算属性添加到您的 Book 结构 extension Book var allGenres: [String] return genres.map$0.name 并使用 print(book.allGenres) 【参考方案1】:

如果你真的需要那个额外的 name 属性,你可以在一个新的扩展中这样做:

extension Array where Element == Genre 
    var name: [String] 
        return self.map  $0.name 
    

这会将上述name 属性添加到每个 [Genre] 值,包括由您的Book 类型定义的值。请确保这确实是您所追求的(如果将此扩展名声明为 private,那么它将在相应的 swift 文件中可用)。

【讨论】:

太棒了!那行得通。但是要确认在我的 BookCollection 扩展中没有办法做到这一点?我在那个扩展之外添加了这个,它工作得很好。 @DominicPilla 不,这是不可能的。你真的需要扩展[Genre] 来实现你的目标;) 我尝试为每个数据结构创建extensions,但没有成功。你是说如果实施得当,它会起作用吗? Swift 扩展机制相当灵活,应该可以!如果您有特定的案例,我建议您发布另一个问题。顺便说一句,如果有机会,请不要忘记将我的答案标记为已接受;)【参考方案2】:

为了消除使用许多枚举编码键和手动解码您的类型的需要,您可以将数据结构更改为映射 JSON 结构格式。注意下面的代码,不需要嵌套结构,你也可以把它并行。此代码已使用您的编码 JSON 数据进行测试

public struct HarryPotterFeed: Codable 
  public let feed: BookCollection

  public struct BookCollection: Codable 
    public let title: String
    public let books: [Book]

    // map properties books to json's "results"
    enum CodingKeys: String, CodingKey 
      case title  // needs to come along 
      case books = "results"
    

    public struct Book: Codable 
      public let author, name, artworkURL, releaseDate : String
      public let genres: [Genre]

      public struct Genre: Codable 
        public let name: String
      
    
  


// Decode JSON 

do 
  let response = try JSONDecoder().decode(HarryPotterFeed.self, from: json)
  for book in response.feed.books 
    for name in book.genres 
      print(name)
    
  
 catch 
  print("PROBLEM DECODING JSON \(error)")

【讨论】:

感谢您的评论。 JSON 数据结构有多个不必要的键,并且会使我的数据结构充满无用的值!

以上是关于在 Swift 4 中使用 Decodable 和 CodingKeys 解析 JSON的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift 4 中使用 Decodable 解析 JSON

处理包含多种类型的 JSON 数组 - Swift 4 Decodable

使用 Swift 4 的 Decodable 解码 Void

Swift 4 Decodable - 没有与键 CodingKeys 关联的值 [重复]

使用 Swift 4 Decodable 将字符串 JSON 响应转换为布尔值

如何在 Swift 4 中将 Encodable 或 Decodable 作为参数传递?