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

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
}

关键实现说明

  1. 反转映射字典:利用已有的mapping字典生成反向映射,方便通过选项值快速查找对应的名称,避免硬编码名称字符串。
  2. 收集选项名称:逐个检查当前AppOptions实例包含的选项,将对应名称加入数组;同时单独处理.all选项,确保输出["all"]而非展开所有单个选项。
  3. 编码为数组:创建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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 15:45:48