You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 18:35:10