用 Swift 解码 JSON 包含不同类型的字典
Decoding JSON with Swift containing dictionary with different types
我有一个 JSON 格式,我试图用 JSON 解码器解析,但由于 JSON 的结构方式,我不知道如何做吧。
这是 JSON 的格式。为了简洁起见,我将省略一些内容。
{
"name":"John Smith",
"addresses":[
{
"home":{
"street":"123 Main St",
"state":"CA"
}
},
{
"work":{
"street":"345 Oak St",
"state":"CA"
}
},
{
"favorites":[
{
"street":"456 Green St.",
"state":"CA"
},
{
"street":"987 Tambor Rd",
"state":"CA"
}
]
}
]
}
我不知道如何定义我随后可以解码的 Decodable 结构。 addresses
是字典数组。 home
和 work
各包含一个地址,而 favorites
包含一个地址数组。我不能将地址定义为 [Dictionary<String, Address]
,因为 favorites
是一个地址数组。我无法将其定义为 [Dictionary<String, Any>]
,因为那样我会收到 Type 'UserProfile' does not conform to protocol 'Encodeable'
错误。
有人知道我该如何解析吗?如何解析值根据键而变化的字典?
谢谢。
一个可能的解决方案,使用 enum
,可以是工作、家庭或最爱:
struct Top: Decodable {
let name: String
let addresses: [AddressType]
enum CodingKeys: String, CodingKey {
case name
case addresses
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.addresses = try container.decode([AddressType].self, forKey: .addresses)
}
}
enum AddressType: Decodable {
case home(Address)
case work(Address)
case favorites([Address])
enum CodingKeys: String, CodingKey {
case home
case work
case favorites
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let home = try container.decodeIfPresent(Address.self, forKey: .home) {
self = AddressType.home(home)
} else if let work = try container.decodeIfPresent(Address.self, forKey: .work) {
self = AddressType.work(work)
} else {
let favorites = try container.decodeIfPresent([Address].self, forKey: .favorites)
self = AddressType.favorites(favorites ?? [])
}
}
}
struct Address: Decodable {
let street: String
let state: String
}
正在测试(我猜你的 JSON 有修复):
let jsonStr = """
{
"name": "John Smith",
"addresses": [{
"home": {
"street": "123 Main St",
"state": "CA"
}
}, {
"work": {
"street": "345 Oak St",
"state": "CA"
}
}, {
"favorites": [{
"street": "456 Green St.",
"state": "CA"
}, {
"street": "987 Tambor Rd",
"state": "CA"
}]
}]
}
"""
let jsonData = jsonStr.data(using: .utf8)!
do {
let top = try JSONDecoder().decode(Top.self, from: jsonData)
print("Top.name: \(top.name)")
top.addresses.forEach {
switch [=11=] {
case .home(let address):
print("It's a home address:\n\t\(address)")
case .work(let address):
print("It's a work address:\n\t\(address)")
case .favorites(let addresses):
print("It's a favorites addresses:")
addresses.forEach{ aSubAddress in
print("\t\(aSubAddress)")
}
}
}
} catch {
print("Error: \(error)")
}
输出:
$>Top.name: John Smith
$>It's a home address:
Address(street: "123 Main St", state: "CA")
$>It's a work address:
Address(street: "345 Oak St", state: "CA")
$>It's a favorites addresses:
Address(street: "456 Green St.", state: "CA")
Address(street: "987 Tambor Rd", state: "CA")
注意:
之后,您应该可以根据需要在 Top
上设置惰性变量:
lazy var homeAddress: Address? = {
return self.addresses.compactMap {
if case AddressType.home(let address) = [=13=] {
return address
}
return nil
}.first
}()
我假设你的 JSON 是这样的:
{
"name": "John Smith",
"addresses": [
{
"home": {
"street": "123 Main St",
"state": "CA"
}
},
{
"work": {
"street": "345 Oak St",
"state": "CA"
}
},
{
"favorites": [
{
"street": "456 Green St.",
"state": "CA"
},
{
"street": "987 Tambor Rd",
"state": "CA"
}
]
}
]
}
我必须进行一些更改才能成为有效的 JSON。
您可以使用以下结构始终将地址 属性 映射到 [[String: [Address]]]
:
struct Response: Decodable {
let name: String
let addresses: [[String: [Address]]]
enum CodingKeys: String, CodingKey {
case name
case addresses
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
var unkeyedContainer = try container.nestedUnkeyedContainer(forKey: .addresses)
var addresses = [[String: [Address]]]()
while !unkeyedContainer.isAtEnd {
do {
let sindleAddress = try unkeyedContainer.decode([String: Address].self)
addresses.append(sindleAddress.mapValues { [[=11=]] } )
} catch DecodingError.typeMismatch {
addresses.append(try unkeyedContainer.decode([String: [Address]].self))
}
}
self.addresses = addresses
}
}
struct Address: Decodable {
let street: String
let state: String
}
基本上,在 init(from:)
的自定义实现中,我们尝试将 addresses
属性 解码为 [String: Address]
,如果成功,则类型为 [=18= 的新字典] 是用值数组中的一个元素创建的。如果失败,那么我们将 addresses
属性 解码为 [String: [Address]]
.
更新: 我更愿意添加另一个结构:
struct AddressType {
let label: String
let addressList: [Address]
}
并将Response
的addresses
属性修改为[AddressType]
:
struct Response: Decodable {
let name: String
let addresses: [AddressType]
enum CodingKeys: String, CodingKey {
case name
case addresses
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
var unkeyedContainer = try container.nestedUnkeyedContainer(forKey: .addresses)
var addresses = [AddressType]()
while !unkeyedContainer.isAtEnd {
let addressTypes: [AddressType]
do {
addressTypes = try unkeyedContainer.decode([String: Address].self).map {
AddressType(label: [=13=].key, addressList: [[=13=].value])
}
} catch DecodingError.typeMismatch {
addressTypes = try unkeyedContainer.decode([String: [Address]].self).map {
AddressType(label: [=13=].key, addressList: [=13=].value)
}
}
addresses.append(contentsOf: addressTypes)
}
self.addresses = addresses
}
}
我有一个 JSON 格式,我试图用 JSON 解码器解析,但由于 JSON 的结构方式,我不知道如何做吧。
这是 JSON 的格式。为了简洁起见,我将省略一些内容。
{
"name":"John Smith",
"addresses":[
{
"home":{
"street":"123 Main St",
"state":"CA"
}
},
{
"work":{
"street":"345 Oak St",
"state":"CA"
}
},
{
"favorites":[
{
"street":"456 Green St.",
"state":"CA"
},
{
"street":"987 Tambor Rd",
"state":"CA"
}
]
}
]
}
我不知道如何定义我随后可以解码的 Decodable 结构。 addresses
是字典数组。 home
和 work
各包含一个地址,而 favorites
包含一个地址数组。我不能将地址定义为 [Dictionary<String, Address]
,因为 favorites
是一个地址数组。我无法将其定义为 [Dictionary<String, Any>]
,因为那样我会收到 Type 'UserProfile' does not conform to protocol 'Encodeable'
错误。
有人知道我该如何解析吗?如何解析值根据键而变化的字典?
谢谢。
一个可能的解决方案,使用 enum
,可以是工作、家庭或最爱:
struct Top: Decodable {
let name: String
let addresses: [AddressType]
enum CodingKeys: String, CodingKey {
case name
case addresses
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.addresses = try container.decode([AddressType].self, forKey: .addresses)
}
}
enum AddressType: Decodable {
case home(Address)
case work(Address)
case favorites([Address])
enum CodingKeys: String, CodingKey {
case home
case work
case favorites
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let home = try container.decodeIfPresent(Address.self, forKey: .home) {
self = AddressType.home(home)
} else if let work = try container.decodeIfPresent(Address.self, forKey: .work) {
self = AddressType.work(work)
} else {
let favorites = try container.decodeIfPresent([Address].self, forKey: .favorites)
self = AddressType.favorites(favorites ?? [])
}
}
}
struct Address: Decodable {
let street: String
let state: String
}
正在测试(我猜你的 JSON 有修复):
let jsonStr = """
{
"name": "John Smith",
"addresses": [{
"home": {
"street": "123 Main St",
"state": "CA"
}
}, {
"work": {
"street": "345 Oak St",
"state": "CA"
}
}, {
"favorites": [{
"street": "456 Green St.",
"state": "CA"
}, {
"street": "987 Tambor Rd",
"state": "CA"
}]
}]
}
"""
let jsonData = jsonStr.data(using: .utf8)!
do {
let top = try JSONDecoder().decode(Top.self, from: jsonData)
print("Top.name: \(top.name)")
top.addresses.forEach {
switch [=11=] {
case .home(let address):
print("It's a home address:\n\t\(address)")
case .work(let address):
print("It's a work address:\n\t\(address)")
case .favorites(let addresses):
print("It's a favorites addresses:")
addresses.forEach{ aSubAddress in
print("\t\(aSubAddress)")
}
}
}
} catch {
print("Error: \(error)")
}
输出:
$>Top.name: John Smith
$>It's a home address:
Address(street: "123 Main St", state: "CA")
$>It's a work address:
Address(street: "345 Oak St", state: "CA")
$>It's a favorites addresses:
Address(street: "456 Green St.", state: "CA")
Address(street: "987 Tambor Rd", state: "CA")
注意:
之后,您应该可以根据需要在 Top
上设置惰性变量:
lazy var homeAddress: Address? = {
return self.addresses.compactMap {
if case AddressType.home(let address) = [=13=] {
return address
}
return nil
}.first
}()
我假设你的 JSON 是这样的:
{
"name": "John Smith",
"addresses": [
{
"home": {
"street": "123 Main St",
"state": "CA"
}
},
{
"work": {
"street": "345 Oak St",
"state": "CA"
}
},
{
"favorites": [
{
"street": "456 Green St.",
"state": "CA"
},
{
"street": "987 Tambor Rd",
"state": "CA"
}
]
}
]
}
我必须进行一些更改才能成为有效的 JSON。
您可以使用以下结构始终将地址 属性 映射到 [[String: [Address]]]
:
struct Response: Decodable {
let name: String
let addresses: [[String: [Address]]]
enum CodingKeys: String, CodingKey {
case name
case addresses
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
var unkeyedContainer = try container.nestedUnkeyedContainer(forKey: .addresses)
var addresses = [[String: [Address]]]()
while !unkeyedContainer.isAtEnd {
do {
let sindleAddress = try unkeyedContainer.decode([String: Address].self)
addresses.append(sindleAddress.mapValues { [[=11=]] } )
} catch DecodingError.typeMismatch {
addresses.append(try unkeyedContainer.decode([String: [Address]].self))
}
}
self.addresses = addresses
}
}
struct Address: Decodable {
let street: String
let state: String
}
基本上,在 init(from:)
的自定义实现中,我们尝试将 addresses
属性 解码为 [String: Address]
,如果成功,则类型为 [=18= 的新字典] 是用值数组中的一个元素创建的。如果失败,那么我们将 addresses
属性 解码为 [String: [Address]]
.
更新: 我更愿意添加另一个结构:
struct AddressType {
let label: String
let addressList: [Address]
}
并将Response
的addresses
属性修改为[AddressType]
:
struct Response: Decodable {
let name: String
let addresses: [AddressType]
enum CodingKeys: String, CodingKey {
case name
case addresses
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
var unkeyedContainer = try container.nestedUnkeyedContainer(forKey: .addresses)
var addresses = [AddressType]()
while !unkeyedContainer.isAtEnd {
let addressTypes: [AddressType]
do {
addressTypes = try unkeyedContainer.decode([String: Address].self).map {
AddressType(label: [=13=].key, addressList: [[=13=].value])
}
} catch DecodingError.typeMismatch {
addressTypes = try unkeyedContainer.decode([String: [Address]].self).map {
AddressType(label: [=13=].key, addressList: [=13=].value)
}
}
addresses.append(contentsOf: addressTypes)
}
self.addresses = addresses
}
}