Swift中为OptionSet实现自定义Encodable编码器生成友好JSON
实现OptionSet的自定义JSON编码,输出选项名称数组
当前使用OptionSet配合Encodable时,默认会将选项序列化为原始整数值(比如示例中的5),但我们需要将其序列化为可读性更强的选项名称数组(如["optA","optC"])。目前已经完成了解码逻辑,以下是补充自定义encode(to:)方法的解决方案:
完整实现代码
struct AppOptions: OptionSet { let rawValue: Int static let optA = AppOptions(rawValue: 1 << 0) static let optB = AppOptions(rawValue: 1 << 1) static let optC = AppOptions(rawValue: 1 << 2) static let all: AppOptions = [.optA, .optB, .optC] } extension AppOptions: Codable { init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() var result: AppOptions = [] while !container.isAtEnd { let optionName = try container.decode(String.self) guard let opt = AppOptions.mapping[optionName] else { let context = DecodingError.Context( codingPath: decoder.codingPath, debugDescription: "Option not recognised: \(optionName)") throw DecodingError.typeMismatch(String.self, context) } result.insert(opt) } self = result } func encode(to encoder: Encoder) throws { // 反转现有mapping字典,得到「选项值→名称」的映射 let reverseMapping = AppOptions.mapping.reduce(into: [AppOptions: String]()) { dict, pair in dict[pair.value] = pair.key } // 收集当前实例包含的所有选项名称 var optionNames = [String]() if contains(.optA) { optionNames.append(reverseMapping[.optA]!) } if contains(.optB) { optionNames.append(reverseMapping[.optB]!) } if contains(.optC) { optionNames.append(reverseMapping[.optC]!) } // 特殊处理all选项:直接使用"all"而非展开所有单个选项 if self == .all { optionNames = ["all"] } // 将名称数组编码为JSON数组 var container = encoder.unkeyedContainer() try optionNames.forEach { name in try container.encode(name) } } private static let mapping: [String: AppOptions] = [ "optA" : .optA, "optB" : .optB, "optC" : .optC, "all" : .all ] } struct AppConfig: Codable { var configName: String var options: AppOptions }
关键实现说明
- 反转映射字典:利用已有的
mapping字典生成反向映射,方便通过选项值快速查找对应的名称,避免硬编码名称字符串。 - 收集选项名称:逐个检查当前
AppOptions实例包含的选项,将对应名称加入数组;同时单独处理.all选项,确保输出["all"]而非展开所有单个选项。 - 编码为数组:创建
unkeyedContainer,将收集到的名称逐个编码,最终生成符合预期的JSON数组格式。
测试验证
执行以下测试代码:
let appCfg = AppConfig(configName: "SomeConfig", options: [.optA, .optC]) let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] let data = try encoder.encode(appCfg) print(String(decoding: data, as: UTF8.self))
将得到预期的输出:
{ "configName" : "SomeConfig", "options" : [ "optA", "optC" ] }
若测试.all选项:
let appCfgAll = AppConfig(configName: "FullConfig", options: .all) let dataAll = try encoder.encode(appCfgAll) print(String(decoding: dataAll, as: UTF8.self))
输出结果:
{ "configName" : "FullConfig", "options" : [ "all" ] }
内容的提问来源于stack exchange,提问作者KieranC
相关产品推荐
相关产品推荐

