Swift中如何将JSON数组属性解码为字典式访问的Decodable结构体?
问题描述
需要将给定的JSON数据映射到指定的ShopCategory结构体,当前实现可正常运行,但希望避免遍历custom_attributes数组获取属性值,转而将其解码为字典类型,通过键直接访问thumbnail、metaTitle等属性。
原始JSON数据
{ "items": [ { "id": 4, "name": "Caffè", "is_active": true, "position": 5, "include_in_menu": true, "custom_attributes": [ { "attribute_code": "meta_title", "value": "Caffè: scopri la linea completa online" }, { "attribute_code": "meta_description", "value": "Scopri il caffè Hausbrandt nello shop ufficiale" } ] }, { "id": 11, "name": "Tè", "is_active": true, "position": 15, "include_in_menu": true, "custom_attributes": [ { "attribute_code": "meta_title", "value": "Acquista le Migliori Selezioni di Tè Online" }, { "attribute_code": "meta_description", "value": "Scopri i Tè più pregiati sullo Shop Online" }, { "attribute_code": "thumbnail", "value": "/shop/media/catalog/category/te01.jpg" } ] }, { "id": 17, "name": "Tazzine", "is_active": true, "position": 14, "include_in_menu": true, "custom_attributes": [ { "attribute_code": "meta_title", "value": "Tazze e Tazzine Caffè: scopri le Collezioni" }, { "attribute_code": "meta_description", "value": "Acquista online le Tazze e Tazzine da Caffè Hausbrandt" }, { "attribute_code": "thumbnail", "value": "/shop/media/catalog/category/tazzine_2.jpg" } ] } ] }
目标结构体
struct ShopCategory: Decodable { let id: Int let name: String let position: Int let isActive: Bool let isIncluded: Bool let thumbnail: String? let metaTitle: String? let metaDescription: String? }
当前实现代码
struct ShopCategory: Decodable { let id: Int let name: String let position: Int let isActive: Bool let isIncluded: Bool let thumbnail: String? let metaTitle: String? let metaDescription: String? } extension ShopCategory { enum CodingKeys: String, CodingKey { case id case name case position case isActive = "is_active" case isIncluded = "include_in_menu" case attributes = "custom_attributes" } init(from decoder: any Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) self.id = try values.decode(Int.self, forKey: .id) self.name = try values.decode(String.self, forKey: .name) self.position = try values.decode(Int.self, forKey: .position) self.isActive = try values.decode(Bool.self, forKey: .isActive) self.isIncluded = try values.decode(Bool.self, forKey: .isIncluded) let attributes = try values.decodeIfPresent([AttributeCode].self, forKey: .attributes) self.thumbnail = attributes?.first(where: { $0.code == "thumbnail" })?.value self.metaTitle = attributes?.first(where: { $0.code == "meta_title" })?.value self.metaDescription = attributes?.first(where: { $0.code == "meta_description" })?.value } } extension ShopCategory: Hashable { } private extension ShopCategory { struct AttributeCode: Decodable { let value: String let code: String enum CodingKeys: String, CodingKey { case code = "attribute_code" case value } } }
解决方案
提供两种实现方式,均能实现将数组解码为字典并直接通过键取值:
方式一:初始化方法内直接转字典
在init(from:)中将解码出的属性数组转换为[String: String]字典,后续直接通过键访问:
struct ShopCategory: Decodable { let id: Int let name: String let position: Int let isActive: Bool let isIncluded: Bool let thumbnail: String? let metaTitle: String? let metaDescription: String? } extension ShopCategory { enum CodingKeys: String, CodingKey { case id, name, position case isActive = "is_active" case isIncluded = "include_in_menu" case customAttributes = "custom_attributes" } init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 解码基础字段 self.id = try container.decode(Int.self, forKey: .id) self.name = try container.decode(String.self, forKey: .name) self.position = try container.decode(Int.self, forKey: .position) self.isActive = try container.decode(Bool.self, forKey: .isActive) self.isIncluded = try container.decode(Bool.self, forKey: .isIncluded) // 将属性数组转换为字典 let attributesArray = try container.decodeIfPresent([[String: String]].self, forKey: .customAttributes) ?? [] let attributesDict = attributesArray.reduce(into: [String: String]()) { dict, item in if let code = item["attribute_code"], let value = item["value"] { dict[code] = value } } // 通过字典键直接取值 self.thumbnail = attributesDict["thumbnail"] self.metaTitle = attributesDict["meta_title"] self.metaDescription = attributesDict["meta_description"] } } extension ShopCategory: Hashable { }
方式二:自定义可解码字典类型(复用性更强)
定义专门处理attribute_code+value结构的可解码类型,后续可在多个场景复用:
// 自定义类型:将属性数组解码为字典 struct AttributeDictionary: Decodable { private let dict: [String: String] subscript(key: String) -> String? { dict[key] } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let attributes = try container.decode([AttributeEntry].self) self.dict = attributes.reduce(into: [:]) { $0[$1.code] = $1.value } } private struct AttributeEntry: Decodable { let code: String let value: String enum CodingKeys: String, CodingKey { case code = "attribute_code" case value } } } // 修改ShopCategory实现 struct ShopCategory: Decodable { let id: Int let name: String let position: Int let isActive: Bool let isIncluded: Bool let thumbnail: String? let metaTitle: String? let metaDescription: String? } extension ShopCategory { enum CodingKeys: String, CodingKey { case id, name, position case isActive = "is_active" case isIncluded = "include_in_menu" case customAttributes = "custom_attributes" } init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(Int.self, forKey: .id) self.name = try container.decode(String.self, forKey: .name) self.position = try container.decode(Int.self, forKey: .position) self.isActive = try container.decode(Bool.self, forKey: .isActive) self.isIncluded = try container.decode(Bool.self, forKey: .isIncluded) // 解码为自定义字典类型 let attributes = try container.decodeIfPresent(AttributeDictionary.self, forKey: .customAttributes) ?? AttributeDictionary(dict: [:]) // 通过下标直接访问属性 self.thumbnail = attributes["thumbnail"] self.metaTitle = attributes["meta_title"] self.metaDescription = attributes["meta_description"] } } extension ShopCategory: Hashable { }
内容的提问来源于stack exchange,提问作者Giorgio
相关产品推荐
相关产品推荐

