【问题标题】:Decode in Swift a JSON response to an array of struct using top level data使用顶级数据在 Swift 中解码对结构数组的 JSON 响应
【发布时间】:2021-07-16 13:14:02
【问题描述】:

我有一个来自 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 或语言的方法,因为它位于顶层

【问题讨论】:

  • json 格式是否正确??
  • 1.数据结构无效。 2. 键名不正确。 3. 你有一本字典,而不是字典的数组。
  • @EkramulHoque 感谢您的回复!不抱歉,它只是从我的手机快速输入的,所以可能不正确,但我使用的原始文件是正确的! “数据”应该是一个字典,而“书籍”只是一个字典数组。我正在尝试将其转换为对象数组,但也使用 Book 结构中的顶级数据。

标签: arrays json swift nested codable


【解决方案1】:

您也许可以使用自定义 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: $0.book_id, name: $0.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: $0.book_id, name: $0.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 类,你从 Root 初始化。因为明天,JSON 可能会更改(更改服务器等)。然后用于您的视图的模型(如何显示它们)也可能不同...... 分离你的层,无论你想使用 MVC、MVVM、VIPER 等。

编辑:

您可以覆盖init(decoder:),但它会使代码更清晰吗?我发现它比以前的版本更难编写(意思是更难调试/修改?)

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
}

【讨论】:

  • 感谢您提供的所有详细信息,这对您有很大帮助!是的,我希望能够使用 init(decoder:) 和使用 Codable 的自定义编码来做到这一点,但也许它毕竟不可能..
  • @Johhn432 我提供了一种自定义init(decoder:) 的方法,但正如我所建议的,以后调试可能会更困难。
猜你喜欢
  • 1970-01-01
  • 2021-05-19
  • 1970-01-01
  • 2018-12-15
  • 1970-01-01
  • 2020-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多