我有回应,看起来像JSON
。问题是我有动态密钥是哈希。我很遗憾为这个响应创建了一个Decodable
结构。
我尝试了下面的代码,但由于最近引入了planName
,它因数据不匹配而失败。
struct ProductDescriptionResponse {
let disclaimersHtml: String?
let additionalProperties: [String: ProductDescription]?
}
struct ProductDescription {
var disclaimersHtml: String?
var hasPlanDetailsV2: Bool?
let planDescription: String
let serviceDescriptions: [ServiceDescriptions]
}
struct ServiceDescriptions {
let name: String
let subTitle: String?
let icon: String?
let description: String?
let upgradeText: String?
let featureDisclaimer: String?
}
func productDescriptions() -> Observable<ProductDescriptionResponse> {
return APIClient.sharedInstance.rx.response(apiURL: APIURL.productDescription, requestType: .get, httpBody: nil, header: .auth).map({ (responseData) -> ProductDescriptionResponse in
var parsedData = try JSONSerialization.jsonObject(with: responseData) as? [String:Any]
// Remove disclaimersHtml key from responseData & hold it to pass
// in ProductDescriptionResponse constructor during return.
let disclaimersHtml = parsedData?.removeValue(forKey: "disclaimersHtml") as? String
// Product descriptions.
var productDescriptions: [String: ProductDescription]? = nil
if let parsedData = parsedData {
// Json data without disclaimersHtml.
let jsonData = try JSONSerialization.data(withJSONObject: parsedData, options: .prettyPrinted)
productDescriptions = try JSONDecoder().decode([String: ProductDescription].self, from: jsonData)
}
// ProductDescriptionResponse with disclaimersHtml & productDescriptions.
return ProductDescriptionResponse(disclaimersHtml: disclaimersHtml,
additionalProperties: productDescriptions)
})
}
JSON响应:
{
"disclaimersHtml": "",
"planName": "",
“abc456753234”: {
"planDescription": "",
"hasPlanDetailsV2": false,
"serviceDescriptions": [
{
"name": "",
"subTitle": "",
"icon": "",
"hasTile": "",
"tileTitle": "",
"description": "",
"featureDisclaimer": "",
"upgradeText": ""
},
{
"name": "",
"subTitle": "",
"icon": "",
"hasTile": "",
"tileTitle": "",
"description": "",
"featureDisclaimer": "",
"upgradeText": ""
}
]
},
“xyz123456789”: {
"planDescription": "",
"hasPlanDetailsV2": true,
"serviceDescriptions": [
{
"name": "",
"subTitle": "",
"icon": "",
"hasTile": "",
"tileTitle": "",
"description": "",
"featureDisclaimer": "",
"upgradeText": ""
}
]
}
}
如果我在下面做,它的确有效。但是不想继续这样的硬编码:
let _ = parsedData?.removeValue(forKey: "planName") as? String
这种类型的JSON有没有更好的工作?我不想硬编码并继续剥离值以获得ProductDescriptionResponse
,因为将来可以添加此字段。
我会使用CustomCodingKeys解决这个问题:
struct ProductDescriptionResponse: Decodable {
let disclaimersHtml: String?
let additionalProperties: [String: ProductDescription]?
var disclaimersHtml: String? = nil
var additionalProperties: [String: ProductDescription]? = nil
private struct CustomCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
self.additionalProperties = [String: ProductDescription]()
for key in container.allKeys {
do {
if let keyValue = CustomCodingKeys(stringValue: key.stringValue) {
if keyValue.stringValue == "disclaimersHtml" {
self.disclaimersHtml = try container.decodeIfPresent(String.self, forKey: keyValue)
}
self.additionalProperties?[keyValue.stringValue] = try container.decodeIfPresent(ProductDescription.self, forKey: keyValue)
}
} catch {
// Silently ignore the error
}
}
}
}
func productDescriptions() -> Observable<ProductDescriptionResponse> {
return APIClient.sharedInstance.rx.response(memberAPIURL: MemberAPIURL.productDescription, requestType: .get, httpBody: nil, header: .auth).map({ (response) -> ProductDescriptionResponse in
return try JSONDecoder().decode(ProductDescriptionResponse.self, from: response)
})
}
我解决的方法是迭代序列化JSON的(key, value)
。
检查value
的类型为Dictionary<AnyHashable,Any>
并仅在匹配时解码,否则忽略。
func productDescriptions() -> Observable<ProductDescriptionResponse> {
return APIClient.sharedInstance.rx.response(memberAPIURL: MemberAPIURL.productDescription, requestType: .get, httpBody: nil, header: .auth).map({ (responseData) -> ProductDescriptionResponse in
var productDescriptionResponse = ProductDescriptionResponse(disclaimersHtml: nil, additionalProperties: nil)
var additionalParams: [String: ProductDescription] = [:]
do {
productDescriptionResponse = try JSONDecoder().decode(ProductDescriptionResponse.self, from: responseData)
if let jsonObject = try JSONSerialization.jsonObject(with: responseData, options: .mutableLeaves) as? [String : Any] {
for (key,value) in jsonObject {
if value is Dictionary<AnyHashable,Any> {
let jsonData = try JSONSerialization.data(withJSONObject: value, options: JSONSerialization.WritingOptions.prettyPrinted)
let productDescription = try JSONDecoder().decode(ProductDescription.self, from: jsonData)
additionalParams[key] = productDescription
}
}
productDescriptionResponse.additionalProperties = additionalParams
}
} catch {
// handle error
}
return productDescriptionResponse
})
}