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

如何为Swift枚举单独实现自定义KeyEncodingStrategy转大写下划线?

解决方案:让Swift枚举自动编码为UPPER_SNAKE_CASE

针对服务要求枚举采用UPPER_SNAKE_CASE格式编码、且不想给每个枚举手动声明CodingKeys的需求,我在@rudedog的帮助下找到了一个优雅的实现方案——仅对枚举生效,完全不影响结构体其他属性的编码逻辑。

核心思路

通过自定义协议+编码容器扩展的方式,让需要转换的枚举自动遵循协议,编码器会自动将其原始String值转换为UPPER_SNAKE_CASE格式,无需修改全局编码策略,也不用逐个枚举处理。

完整实现代码

import Foundation

public protocol UpperSnakeCaseRepresentable: Encodable {
    var upperSnakeCaseValue: String { get }
}

extension UpperSnakeCaseRepresentable where Self: RawRepresentable, Self.RawValue == String {
    var upperSnakeCaseValue: String {
        return _upperSnakeCaseValue(rawValue)
    }
}

extension KeyedEncodingContainer {
    mutating func encode(_ value: UpperSnakeCaseRepresentable, forKey key: KeyedEncodingContainer<K>.Key) throws {
        try encode(value.upperSnakeCaseValue, forKey: key)
    }
}

// 基于Apple官方驼峰转SnakeCase逻辑修改,最终转为大写格式
fileprivate func _upperSnakeCaseValue(_ stringKey: String) -> String {
    guard !stringKey.isEmpty else { return stringKey }
    
    var words : [Range<String.Index>] = []
    var wordStart = stringKey.startIndex
    var searchRange = stringKey.index(after: wordStart)..<stringKey.endIndex
    
    while let upperCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.uppercaseLetters, options: [], range: searchRange) {
        let untilUpperCase = wordStart..<upperCaseRange.lowerBound
        words.append(untilUpperCase)
        
        searchRange = upperCaseRange.lowerBound..<searchRange.upperBound
        guard let lowerCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.lowercaseLetters, options: [], range: searchRange) else {
            wordStart = searchRange.lowerBound
            break
        }
        
        let nextCharacterAfterCapital = stringKey.index(after: upperCaseRange.lowerBound)
        if lowerCaseRange.lowerBound == nextCharacterAfterCapital {
            wordStart = upperCaseRange.lowerBound
        } else {
            let beforeLowerIndex = stringKey.index(before: lowerCaseRange.lowerBound)
            words.append(upperCaseRange.lowerBound..<beforeLowerIndex)
            wordStart = beforeLowerIndex
        }
        
        searchRange = lowerCaseRange.upperBound..<searchRange.upperBound
    }
    
    words.append(wordStart..<searchRange.upperBound)
    
    let result = words.map({ (range) in
        return stringKey[range].lowercased()
    }).joined(separator: "_")
    return result.uppercased()
}

// 测试用枚举
enum Snake: String, UpperSnakeCaseRepresentable, Encodable {
    case blackAdder
    case mamba
}

// 测试用结构体
struct Test: Encodable {
    let testKey: String?
    let snake: Snake
}

// 验证编码效果
let test = Test(testKey: "testValue", snake: .mamba)
let enumData = try! JSONEncoder().encode(test)
let json = String(data: enumData, encoding: .utf8)!
print(json) // 输出: {"testKey":"testValue","snake":"MAMBA"}

代码说明

  1. UpperSnakeCaseRepresentable协议:标记需要应用UPPER_SNAKE_CASE转换的枚举,针对RawValue为String的枚举提供了默认实现,自动调用转换函数处理原始值。
  2. KeyedEncodingContainer扩展:重写了针对UpperSnakeCaseRepresentable类型的编码方法,让编码器自动使用转换后的字符串值进行编码,完全透明化处理。
  3. _upperSnakeCaseValue函数:基于Apple官方的驼峰转SnakeCase逻辑修改,最后将结果转为大写,确保输出符合UPPER_SNAKE_CASE格式要求。

适配你的场景

只需要让你原有的枚举同时遵循RawRepresentable(String类型)、Encodable和UpperSnakeCaseRepresentable协议即可:

public struct Request: Encodable {
    public let foo: Bool?
    public let barId: BarIdType
    public enum BarIdType: String, UpperSnakeCaseRepresentable, Encodable {
        case test
        case testGroup
    }
}

编码Request(foo: true, barId: .testGroup)后会得到预期结果:

{"foo": true, "barId": "TEST_GROUP"}

内容的提问来源于stack exchange,提问作者David Nedrow

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:28:05