使用顶级数据在 Swift 中解码对结构数组的 JSON 响应

Decode in Swift a JSON response to an array of struct using top level data

我收到 API 的 JSON 回复,看起来像这样:

{ 
"data": {
    "author_id": "wxyz",
    "author_name": "Will",
    "language": "English",
    "books": [
        {"book_id":"abc1", "book_name":"BookA"},
        {"book_id":"def2", "book_name":"BookB"},
        {"book_id":"ghi3", "book_name":"BookC"}
    ]
  }
}

目前我的 Book 结构如下所示:

struct Book: Codable {
    let book_id: String
    let book_name: String
}

但我想要这样的 Book(来自顶级的数据):

struct Book: Codable {
    let book_id: String
    let book_name: String
    let author: Author
    let language: String
}

使用 Codable(/解码自定义类型),如何将上面的 JSON 响应直接转换为图书列表(而一些数据来自顶级对象)?

当我使用 decode 时,我可以自动将 books 数组解码为 Book 数组:

let books = try decoder.decode([Book].self, from: jsonData)

但是我找不到传递作者姓名和id的方法,或者语言,因为它在顶层

您可以使用自定义 init(decoder:) 来实现,但另一种方法是隐藏您坚持使用 JSON 模型的内部实现,并使用 lazy var 或计算得到。 Lazy var只会加载一次,这取决于你是否保留根。

struct Root: Codable {
    //Hidde,
    private let data: RootData

    //Hidden
    struct RootData: Codable {
        let author_id: String
        let author_name: String

        let language: String

        let books: [RootBook]
    }
    //Hidden
    struct RootBook: Codable {
        let book_id: String
        let book_name: String
    }

    lazy var books: [Book] = {
        let author = Author(id: data.author_id, name: data.author_name)
        return data.books.map {
            Book(id: [=10=].book_id, name: [=10=].book_name, author: author, language: data.language)
        }
    }()

    var books2: [Book] {
        let author = Author(id: data.author_id, name: data.author_name)
        return data.books.map {
            Book(id: [=10=].book_id, name: [=10=].book_name, author: author, language: data.language)
        }
    }
}

//Visible
struct Book: Codable {
    let id: String
    let name: String
    let author: Author
    let language: String
}
//Visible
struct Author: Codable {
    let id: String
    let name: String
}

使用:

do {
    var root = try JSONDecoder().decode(Root.self, from: jsonData)
    print(root)
    print("Books: \(root.books)") //Since it's a lazy var, it need to be mutable
} catch {
    print("Error: \(error)")
}

do {
    let root = try JSONDecoder().decode(Root.self, from: jsonData)
    print(root)
    print("Books: \(root.books2)")
} catch {
    print("Error: \(error)")
}

旁注:最简单的方法确实是坚持 JSON 模型。现在,拥有内部模型可能也很有趣,也就是说,你有自己的 Book Class,你从 Root 初始化。因为明天,JSON 可能会改变(服务器改变等)。然后用于您的视图的模型(如何显示它们)也可能不同...... 分离你的层,无论你想使用 MVC、MVVM、VIPER 等

编辑:

您可以覆盖 init(decoder:),但这是否会使代码更清晰?我发现它比以前的版本更难写(意思是,更难debug/modify?)

struct Root2: Codable {
    let books: [Book2]

    private enum TopLevelKeys: String, CodingKey {
        case data
    }
    private enum SubLevelKeys: String, CodingKey {
        case books
        case authorId = "author_id"
        case authorName = "author_name"
        case language
    }
    private enum BoooKeys: String, CodingKey {
        case id = "book_id"
        case name = "book_name"
    }
    init(from decoder: Decoder) throws {
        let topContainer = try decoder.container(keyedBy: TopLevelKeys.self)
        let subcontainer = try topContainer.nestedContainer(keyedBy: SubLevelKeys.self, forKey: .data)
        var bookContainer = try subcontainer.nestedUnkeyedContainer(forKey: .books)
        var books: [Book2] = []
        let authorName = try subcontainer.decode(String.self, forKey: .authorName)
        let authorid = try subcontainer.decode(String.self, forKey: .authorId)
        let author = Author(id: authorid, name: authorName)
        let language = try subcontainer.decode(String.self, forKey: .language)
        while !bookContainer.isAtEnd {
            let booksubcontainer = try bookContainer.nestedContainer(keyedBy: BoooKeys.self)
            let bookName = try booksubcontainer.decode(String.self, forKey: .name)
            let bookId = try booksubcontainer.decode(String.self, forKey: .id)
            books.append(Book2(book_id: bookId, book_name: bookName, author: author, language: language))
        }
        self.books = books
    }
}
struct Book2: Codable {
    let book_id: String
    let book_name: String
    let author: Author
    let language: String
}