使用 iOS 版 Swift,我需要解码 JSON 响应。它包含一个嵌套对象,需要父对象的值 (
type
) 才能进行解码。
JSON结构:
{
"name":"My email",
"type":"Email",
"content":{
"attributes":{
"email":"[email protected]"
}
}
}
或
{
"name":"My phone number",
"type":"PhoneNumber",
"content":{
"attributes":{
"indicator":"+33",
"national_number":"123456789",
}
}
}
attributes
内容取决于 type
。因此 content
嵌套对象需要知道 type
才能解码 attributes
。
结构:
struct Response: Decodable {
let name: String
let type: String
let content: ContentResponse?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
type = try container.decode(String.self, forKey: .type)
// !!!!!!! Here I need to pass the "type" value to content decoding
content = try container.decodeIfPresent(ContentResponse.self, forKey: .content)
}
}
struct ContentResponse: Decodable {
let issued: Date
let attributes: DocumentResponse?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
issued = try container.decode(Date.self, forKey: .issued)
if container.contains(.attributes) {
// !!!!!!! Here I can't access the "type" value from parent object
switch Type.fromString(type: type) {
case .email:
attributes = try container.decode(DocumentEmailResponse.self, forKey: .attributes)
case .phoneNumber:
attributes = try container.decode(DocumentPhoneNumberResponse.self, forKey: .attributes)
default:
// Unsupported type
attributes = nil
}
} else {
attributes = nil
}
}
}
class DocumentResponse: Decodable {}
class DocumentEmailResponse: DocumentResponse {
let email: String
}
class DocumentPhoneNumberResponse: DocumentResponse {
let indicator: String
let nationalNumber: String
}
如您所见,
init(from decoder: Decoder)
的ContentResponse
需要知道type
才能知道使用哪个类来进行attributes
解码。
如何将
type
上解码的 Response
传递给嵌套对象 ContentResponse
进行解码?
我在这里找到了一个解决方案https://www.andyibanez.com/posts/the-mysterious-codablewithconfiguration-protocol/使用
CodableWithConfiguration
和decodeIfPresent(_:forKey:configuration:)
,但它针对iOS 15+,而我针对iOS 13+ .
我可以在
userInfo
中使用 decoder
的 init(from decoder: Decoder)
,但它是只读的:
var userInfo: [CodingUserInfoKey : Any] { get }
我会为此使用枚举,一个用于内容类型,一个用于保存解码的内容。
首先是
type
的枚举
enum ContentType: String, Codable {
case email = "Email"
case phone = "PhoneNumber"
case none
}
然后是
content
enum Content: Codable {
case email(DocumentEmailResponse)
case phone(DocumentPhoneNumberResponse)
case none
}
我将
Content
中使用的类型更改为结构
struct DocumentEmailResponse: Codable {
let email: String
}
struct DocumentPhoneNumberResponse: Codable {
let phoneNumber: String
}
然后所有自定义解码都参与
Response
,其中 Content
值使用嵌套容器进行解码。
struct Response: Codable {
let name: String
let type: ContentType
let content: Content
enum ContentCodingKeys: String, CodingKey {
case attributes
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
type = try container.decode(ContentType.self, forKey: .type)
let contentContainer = try container.nestedContainer(keyedBy: ContentCodingKeys.self, forKey: .content)
switch type {
case .email:
let value = try contentContainer.decode(DocumentEmailResponse.self, forKey: .attributes)
content = .email(value)
case .phone:
let value = try contentContainer.decode(DocumentPhoneNumberResponse.self, forKey: .attributes)
content = .phone(value)
default:
content = .none
}
}
}
这两个枚举都包含
none
情况,但根据可能可选或不可选的内容以及您可能希望在可能的情况下删除它们的个人喜好,我不完全确定哪些值以及何时可以为 nil。