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

使用枚举类型作为字典键进行JSON解码失败的问题咨询

使用枚举类型作为字典键进行JSON解码失败的问题咨询

我最近在尝试用枚举类型作为字典的键,然后从JSON中加载数据时碰到了个头疼的问题——用String类型当字典键的时候,JSON解码完全没问题,但换成带String原始值的枚举类型后就直接失败了。想问问大家这是得加什么额外配置,还是这种场景本来就没法直接用?

先给大家看看我的测试代码:

import Foundation

var jsonString = """
{
  "key": {
    "enumKey1": "value",
    "enumKey2": "value"
  }
}
"""

struct UntypedObject: Codable {
    let key: [String: String]
}

struct TypedObject: Codable {
    let key: [EnumKey: String]
}

enum EnumKey: String, Codable {
    case enumKey1
    case enumKey2
}

do {
    let data = try Data(jsonString.utf8)
    let decodedUntyped = try JSONDecoder().decode(UntypedObject.self, from: data)
    let decodedTyped = try JSONDecoder().decode(TypedObject.self, from: data)
} catch {
    print("Failed to decode json: \(error.localizedDescription)")
}

解码UntypedObject一切正常,但解码TypedObject时就会抛出错误:

Failed to decode json: The data couldn't be read because it isn't in the correct format.

问题原因

其实这个坑在于:虽然你的枚举已经遵循了Codable和String协议,但JSONDecoder默认的字典键解码逻辑并不会自动把字符串键转换成枚举值——它只处理了普通的枚举值解码,没考虑字典键这种特殊场景。

两种解决思路

思路一:给TypedObject自定义解码逻辑

直接在TypedObject里手动实现init(from:)方法,先把字典解码成[String: String],再手动转换成枚举键的字典:

struct TypedObject: Codable {
    let key: [EnumKey: String]
    
    // 定义编码键
    enum CodingKeys: String, CodingKey {
        case key
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // 先解码成字符串键的字典
        let stringKeyDict = try container.decode([String: String].self, forKey: .key)
        
        var enumKeyDict = [EnumKey: String]()
        for (rawKey, value) in stringKeyDict {
            // 把字符串转换成枚举值,遇到无效键直接抛错
            guard let enumKey = EnumKey(rawValue: rawKey) else {
                throw DecodingError.dataCorruptedError(
                    forKey: .key,
                    in: container,
                    debugDescription: "发现无效的字典键:\(rawKey)"
                )
            }
            enumKeyDict[enumKey] = value
        }
        
        self.key = enumKeyDict
    }
    
    // 如果需要编码(把TypedObject转成JSON),还要实现encode方法
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        // 把枚举键的字典转换成字符串键的字典再编码
        let stringKeyDict = key.reduce(into: [String: String]()) { result, entry in
            result[entry.key.rawValue] = entry.value
        }
        try container.encode(stringKeyDict, forKey: .key)
    }
}

思路二:扩展解码器,通用处理枚举键的字典

如果你的项目里有很多地方需要用枚举当字典键,可以扩展KeyedDecodingContainer,写一个通用的解码方法,这样所有类似的结构体都不用重复写解码逻辑:

extension KeyedDecodingContainer {
    // 通用方法:解码字符串键的字典并转换成枚举键的字典
    func decode<T, U>(_ type: [T: U].Type, forKey key: Key) throws -> [T: U] 
    where T: RawRepresentable, T.RawValue == String, U: Decodable {
        
        let stringDict = try decode([String: U].self, forKey: key)
        return stringDict.reduce(into: [T: U]()) { result, entry in
            guard let enumKey = T(rawValue: entry.key) else {
                // 这里可以根据需求选择抛错或者忽略无效键
                fatalError("无效的枚举键:\(entry.key)")
            }
            result[enumKey] = entry.value
        }
    }
}

这样TypedObject就可以保持最开始的简洁写法,不用自定义init(from:)了,解码器会自动调用我们扩展的方法来处理枚举键的转换。

试一下上面的任意一种方法,你就能成功解码TypedObject了~

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 09:48:05