为单个对象解码 JSON 与 swift 中的对象数组
Decoding JSON for single object vs array of objects in swift
我对 swift 编程语言还很陌生,并且在过去一周左右的时间里一直在努力让它发挥作用。我正在使用现有的 API returns JSON 数据,其结构会根据返回的场地数量而略有变化。
实际结构稍微复杂一些,但这个例子说明了问题。在一种结果中,我得到一个返回的地点,如:
{
"totalItems": 21,
"pageSize": 2,
"venues": {
"venue":
{
"name": "Venue Name 1"
"location": "Location A",
"showCount": "4"
}
}
}
在另一种结果中,我得到了一组返回的地点:
{
"totalItems": 21,
"pageSize": 2,
"venues": {
"venue":
[{
"name": "Venue Name 1"
"location": "Location A",
"showCount": "4"
},
{
"name": "Venue Name 2"
"location": "Location B",
"showCount": "2"
}]
}
}
是 - 这个 API 的所有者无论如何都应该返回一个数组,但他们没有,而且无法更改。
我能够让它为一系列场地正确解码(或者即使没有场地通过),但当通过单个场地时它会中止(当然由于结构变化)。当我更改代码以适应单个场地时,我的代码也能正常工作,但随后 returns 多个场地中止。
我想做的是将任一变体解码为包含数组的内部结构,而不管我收到哪个变体,从而使我以后以编程方式处理的事情变得更加简单。像这样:
struct Response: Decodable {
let totalItems: Int
let pageSize: Int
let venues: VenueWrapper
struct VenueWrapper: Decodable {
let venue: [Venue] // This might contain 0, 1, or more than one venues
}
struct Venue: Decodable {
let name: String
let location: String
let showCount: Int
}
}
注意:在实际的 JSON 响应中,响应中实际上有几个这样的子结构(例如,单个结构与结构数组),这就是为什么我觉得简单地创建一个替代结构是不是一个好的解决方案。
我希望以前有人遇到过这个问题。提前致谢!
您可以创建自己的解码器,
struct Response: Decodable {
let totalItems: Int
let pageSize: Int
let venues: VenueWrapper
struct VenueWrapper: Decodable {
var venue: [Venue]
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
venue = []
if let singleVenue = try? values.decode(Venue.self, forKey: CodingKeys.venue) {
//if a single venue decoded append it to array
venue.append(singleVenue)
} else if let multiVenue = try? values.decode([Venue].self, forKey: CodingKeys.venue) {
//if a multi venue decoded, set it as venue
venue = multiVenue
}
enum CodingKeys: String, CodingKey { case venue }
}
}
struct Venue: Decodable {
let name: String
let location: String
let showCount: String
}
}
不需要VenueWrapper
。
struct Response {
let totalItems: Int
let pageSize: Int
let venues: [Venue]
struct Venue {
let name: String
let location: String
let showCount: Int
}
}
您需要编写自己的初始化程序。不幸的是,我认为没有办法消除非愚蠢编码属性的样板文件。
即使您永远不需要对其进行编码,使 Response
Codable
,而不仅仅是 Decodable
,也可以让您访问自动生成的 CodingKeys
。
extension Response: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
totalItems = try container.decode(Int.self, forKey: .totalItems)
pageSize = try container.decode(Int.self, forKey: .pageSize)
venues = try container.decode(key: .venues)
}
}
最后一行依赖于一个协议和扩展,您可以将其用于类似 "encoded" 的任何其他类型。
protocol GoofilyEncoded: Codable {
/// Must have exactly one case.
associatedtype GoofyCodingKey: CodingKey
}
extension KeyedDecodingContainer {
func decode<Decodable: GoofilyEncoded>(key: Key) throws -> [Decodable] {
let nestedContainer = try self.nestedContainer(
keyedBy: Decodable.GoofyCodingKey.self,
forKey: key
)
let key = nestedContainer.allKeys.first!
do {
return try nestedContainer.decode([Decodable].self, forKey: key)
}
catch {
return try [nestedContainer.decode(Decodable.self, forKey: key)]
}
}
}
所有可能编码在数组中的类型,或不 ♂️,都需要一个单例枚举,如下所示:
extension Response.Venue: GoofilyEncoded {
enum GoofyCodingKey: CodingKey {
case venue
}
}
我对 swift 编程语言还很陌生,并且在过去一周左右的时间里一直在努力让它发挥作用。我正在使用现有的 API returns JSON 数据,其结构会根据返回的场地数量而略有变化。
实际结构稍微复杂一些,但这个例子说明了问题。在一种结果中,我得到一个返回的地点,如:
{
"totalItems": 21,
"pageSize": 2,
"venues": {
"venue":
{
"name": "Venue Name 1"
"location": "Location A",
"showCount": "4"
}
}
}
在另一种结果中,我得到了一组返回的地点:
{
"totalItems": 21,
"pageSize": 2,
"venues": {
"venue":
[{
"name": "Venue Name 1"
"location": "Location A",
"showCount": "4"
},
{
"name": "Venue Name 2"
"location": "Location B",
"showCount": "2"
}]
}
}
是 - 这个 API 的所有者无论如何都应该返回一个数组,但他们没有,而且无法更改。
我能够让它为一系列场地正确解码(或者即使没有场地通过),但当通过单个场地时它会中止(当然由于结构变化)。当我更改代码以适应单个场地时,我的代码也能正常工作,但随后 returns 多个场地中止。
我想做的是将任一变体解码为包含数组的内部结构,而不管我收到哪个变体,从而使我以后以编程方式处理的事情变得更加简单。像这样:
struct Response: Decodable {
let totalItems: Int
let pageSize: Int
let venues: VenueWrapper
struct VenueWrapper: Decodable {
let venue: [Venue] // This might contain 0, 1, or more than one venues
}
struct Venue: Decodable {
let name: String
let location: String
let showCount: Int
}
}
注意:在实际的 JSON 响应中,响应中实际上有几个这样的子结构(例如,单个结构与结构数组),这就是为什么我觉得简单地创建一个替代结构是不是一个好的解决方案。
我希望以前有人遇到过这个问题。提前致谢!
您可以创建自己的解码器,
struct Response: Decodable {
let totalItems: Int
let pageSize: Int
let venues: VenueWrapper
struct VenueWrapper: Decodable {
var venue: [Venue]
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
venue = []
if let singleVenue = try? values.decode(Venue.self, forKey: CodingKeys.venue) {
//if a single venue decoded append it to array
venue.append(singleVenue)
} else if let multiVenue = try? values.decode([Venue].self, forKey: CodingKeys.venue) {
//if a multi venue decoded, set it as venue
venue = multiVenue
}
enum CodingKeys: String, CodingKey { case venue }
}
}
struct Venue: Decodable {
let name: String
let location: String
let showCount: String
}
}
不需要VenueWrapper
。
struct Response {
let totalItems: Int
let pageSize: Int
let venues: [Venue]
struct Venue {
let name: String
let location: String
let showCount: Int
}
}
您需要编写自己的初始化程序。不幸的是,我认为没有办法消除非愚蠢编码属性的样板文件。
即使您永远不需要对其进行编码,使 Response
Codable
,而不仅仅是 Decodable
,也可以让您访问自动生成的 CodingKeys
。
extension Response: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
totalItems = try container.decode(Int.self, forKey: .totalItems)
pageSize = try container.decode(Int.self, forKey: .pageSize)
venues = try container.decode(key: .venues)
}
}
最后一行依赖于一个协议和扩展,您可以将其用于类似 "encoded" 的任何其他类型。
protocol GoofilyEncoded: Codable {
/// Must have exactly one case.
associatedtype GoofyCodingKey: CodingKey
}
extension KeyedDecodingContainer {
func decode<Decodable: GoofilyEncoded>(key: Key) throws -> [Decodable] {
let nestedContainer = try self.nestedContainer(
keyedBy: Decodable.GoofyCodingKey.self,
forKey: key
)
let key = nestedContainer.allKeys.first!
do {
return try nestedContainer.decode([Decodable].self, forKey: key)
}
catch {
return try [nestedContainer.decode(Decodable.self, forKey: key)]
}
}
}
所有可能编码在数组中的类型,或不 ♂️,都需要一个单例枚举,如下所示:
extension Response.Venue: GoofilyEncoded {
enum GoofyCodingKey: CodingKey {
case venue
}
}