如何为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"}
代码说明
UpperSnakeCaseRepresentable协议:标记需要应用UPPER_SNAKE_CASE转换的枚举,针对RawValue为String的枚举提供了默认实现,自动调用转换函数处理原始值。KeyedEncodingContainer扩展:重写了针对UpperSnakeCaseRepresentable类型的编码方法,让编码器自动使用转换后的字符串值进行编码,完全透明化处理。_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
相关产品推荐
相关产品推荐

