Swift,OptionSet,Encodable:需要帮助为OptionSet编写自定义编码器来生成用户友好的JSON



我有一个应用程序,它存储了一些配置选项,我想将这些选项写入JSON文件。

我的应用程序的配置/选项结构和JSON编码的简化版本。。。

struct AppOptions: OptionSet, Encodable {
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]
}
struct AppConfig: Encodable {
var configName: String
var options: AppOptions
}
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" : 5
//  }

因此,尽管这是有效的,但生成的JSON文件并不是特别用户友好的,因为options只是保存为变量原始值->CCD_ 2。

我更喜欢编码器生成更用户友好的JSON,这样选项就更像元素数组,如下所示。。。

{
"configName" : "SomeConfig",
"options" : [ "optA", "optC" ]
}

我在想如何创建实现这一目标所需的自定义encode(to: )时有点走投无路,请提供建议或解决方案。


只是为了一些额外的上下文,我已经为解码部分找到了一个解决方案,可以将配置从JSON文件读取到我的应用程序中,这只是我需要的JSON文件的编码。

代码,包括解码部分。。。

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 {
//    // What to do here?
//  }
private static let mapping: [String: AppOptions] = [
"optA" : .optA,
"optB" : .optB,
"optC" : .optC,
"all"   : .all
]
}

struct AppConfig: Codable {
var configName: String
var options: AppOptions
}
var json = """
{
"configName": "SomeConfig",
"options": ["optA", "optC"]
}
"""
let decoder = JSONDecoder()
var appCfg = try decoder.decode(AppConfig.self, from: Data(json.utf8))
print(appCfg)
//Correct ->  AppConfig(configName: "SomeConfig", options: __lldb_expr_115.AppOptions(rawValue: 5))
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(appCfg)
print(String(decoding: data, as: UTF8.self) )
//  {
//    "configName" : "SomeConfig",
//    "options" : 5
//  }
//  needs to be...
//  {
//    "configName" : "SomeConfig",
//    "options" : [ "optA", "optC" ]
//  }

你可以这样做,但我不确定你是否按照预期的方式使用OptionSet。

func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
let optionsRaw: [String]
if self == .all {
optionsRaw = ["all"]
} else {
optionsRaw = Self.mapping
.filter { $0.key != "all" }
.compactMap { self.contains($0.value) ? $0.key : nil }
.sorted() // if sorting is important
}
try container.encode(contentsOf:  optionsRaw)
}

这以只对["all"]进行编码的方式处理all,并且在它是子集的情况下对密钥进行排序。

最新更新