如何在 Swift 中编写更精简的 Codable 结构

How to write leaner Codable Structs in Swift

我的问题是关于结构的。

现状:

我有一个从文件系统读取的 JSON 对象。此数据有 3 个方面。

  1. 根级密钥,即 notConcerned0nenotConcernedTwo 仅在运行时已知。
  2. 二级属性已知,即。 PredeterminedOnePredeterminedTwo 已知,后代属性也已知。
{
    "notConcerned0ne": {
        "PredeterminedOne": {
            "label": "blue",
            "description": "something..."
        }
    },
    "notConcernedTwo": {
        "PredeterminedTwo": {
            "label": "green",
            "description": "something..."
        }
    }
}

我简化了我的数据,实际上,至少会有20个预定属性。

以下工作正常。但是鉴于我将拥有更多预定属性,我正在寻找一种解决方案来减少执行相同操作的重复结构。


// working

struct Domain: Codable {
    let notConcerned0Ne: NotConcerned0Ne
    let notConcernedTwo: NotConcernedTwo

    enum CodingKeys: String, CodingKey {
        case notConcerned0Ne = "notConcerned0ne"
        case notConcernedTwo = "notConcernedTwo"
    }
}


struct NotConcerned0Ne: Codable {
    let predeterminedOne: Predetermined

    enum CodingKeys: String, CodingKey {
        case predeterminedOne = "PredeterminedOne"
    }
}


struct Predetermined: Codable {
    let label, predeterminedDescription: String

    enum CodingKeys: String, CodingKey {
        case label
        case predeterminedDescription = "description"
    }
}


struct NotConcernedTwo: Codable {
    let predeterminedTwo: Predetermined

    enum CodingKeys: String, CodingKey {
        case predeterminedTwo = "PredeterminedTwo"
    }
}

下面是我的尝试,就是减少重复代码。

struct Domain: Codable {
    let notConcerned0Ne, notConcernedTwo: NotConcernedShared

    enum CodingKeys: String, CodingKey {
        case notConcerned0Ne = "notConcerned0ne"
        case notConcernedTwo = "notConcernedTwo"
    }
}


struct NotConcernedShared: Codable {
    let predeterminedOne, predeterminedTwo: Predetermined

    enum CodingKeys: String, CodingKey {
        case predeterminedOne = "PredeterminedOne"
        case predeterminedTwo = "PredeterminedTwo"
    }
}


struct Predetermined: Codable {
    let label, predeterminedDescription: String

    enum CodingKeys: String, CodingKey {
        case label
        case predeterminedDescription = "description"
    }
}

但这会出现错误

keyNotFound(CodingKeys(stringValue: "PredeterminedTwo", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "notConcerned0ne", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"PredeterminedTwo\", intValue: nil) (\"PredeterminedTwo\").", underlyingError: nil))

keyNotFound(CodingKeys(stringValue: "PredeterminedTwo", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "notConcerned0ne", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"PredeterminedTwo\", intValue: nil) (\"PredeterminedTwo\").", underlyingError: nil))

请您帮忙指出一些我明显遗漏的有关可编码协议的具体信息。或者以我形成结构的方式。

此外,也许我可以删除根级密钥并执行 typealias TheDomain = [String: Domain],因为我不关心根级 属性 名称。

如果你想看的话,我的加载函数工作正常。

func load<T: Decodable>(_ filename: String) -> T {
    let data: Data

    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

您可以将 json 解码为字典字典,其中内部字典使用 Predetermined 作为值

let values = try JSONDecoder().decode([String: [String: Predetermined]].self, from: data)
    .map(\.value)

然后您可以轻松查找“PredeterminedOne/Two”

的正确元素

如果你不关心“PredeterminedOne/Two”(我不确定这个)那么你也可以使用.flatMap直接得到一个Predetermined的数组,只是将其添加到上面的代码中

let values = try JSONDecoder().decode([String: [String: Predetermined]].self, from: data)
    .map(\.value)
    .flatMap(\.values)