【问题标题】:Decoding JSON in Swift with Mixed Types and Mixed Keyed/Unkeyed使用混合类型和混合键控/非键控在 Swift 中解码 JSON
【发布时间】:2022-01-11 10:04:31
【问题描述】:

我正在努力解码 Swift 5 中的 JSON 结构,它看起来像下面的简化示例。我正在努力解决两个问题。外部数组是无键的,内部数组是有键的。最重要的是,内部数组偶尔包含混合类型 String 和 Int 的数组。我可以提供几十个根本不起作用的东西,但我只会提供 JSON:

[
  12,
  {
    "a": [
      "orange",
      10,
      "purple"
    ],
    "b": [
      "red",
      9,
      "blue
    ],
    "c": [
      "yellow",
      "green"
    ]
  },
  "string one",
  "string two"
]

感谢任何想法。

【问题讨论】:

  • 异构 JSON 数组是一种非常糟糕的做法。您可以使用具有关联值的枚举和自定义初始化程序对其进行解码。但是,如果您能够更改 JSON,请执行此操作。
  • 我希望我能改变它,但必须忍受它。是的,这是一种可怕的做法!
  • 带有枚举和关联值的自定义init(from decoder: Decoder)方法是解决方案。

标签: json swift


【解决方案1】:

可在 Playground 中使用的可能解决方案:

func heterogenousJSON() {
    let jsonStr = """
        [
        12,
        {
        "a": [
        "orange",
        10,
        "purple"
        ],
        "b": [
        "red",
        9,
        "blue"
        ],
        "c": [
        "yellow",
        "green"
        ]
        },
        "string one",
        "string two"
        ]
        """

    struct CodableStruct: Codable, CustomStringConvertible {
        let a: [CodableStructValues]
        let b: [CodableStructValues]
        let c: [String] //Here I set it as [String], but could be indeed [CodableStructValues], just to make it more "usual case"

        var description: String {
            "{ \"a\": [\(a.map{ $0.description }.joined(separator: ", "))] }\n" +
            "{ \"b\": [\(b.map{ $0.description }.joined(separator: ", "))] }\n" +
            "{ \"c\": [\(c.map{ $0 }.joined(separator: ", "))] }"
        }
    }

    enum CodableStructValues: Codable, CustomStringConvertible {
        case asInt(Int)
        case asString(String)

        init(from decoder: Decoder) throws {
            let values = try decoder.singleValueContainer()

            if let asInt = try? values.decode(Int.self) {
                self = .asInt(asInt)
                return
            }
            //For the next: we didn't use `try?` but try, and it will throw if it's not a String
            // We assume that if it wasn't okay from previous try?, it's the "last chance". It needs to be of this type, or it will throw an error
            let asString = try values.decode(String.self)
            self = .asString(asString)
        }

        var description: String {
            switch self {
            case .asInt(let intValue):
                return "\(intValue)"
            case .asString(let stringValue):
                return stringValue
            }
        }
    }

    enum Heterogenous: Codable {
        case asInt(Int)
        case asString(String)
        case asCodableStruct(CodableStruct)

        init(from decoder: Decoder) throws {
            let values = try decoder.singleValueContainer()
            if let asInt = try? values.decode(Int.self) {
                self = .asInt(asInt)
                return
            } else if let asString = try? values.decode(String.self) {
                self = .asString(asString)
                return
            }
            //For the next: we didn't use `try?` but try, and it will throw if it's not a String
            // We assume that if it wasn't okay from previous try?, it's the "last chance". It needs to be of this type, or it will throw an error
            let asStruct = try values.decode(CodableStruct.self)
            self = .asCodableStruct(asStruct)
        }
    }

    do {
        let json = Data(jsonStr.utf8)
        let parsed = try JSONDecoder().decode([Heterogenous].self, from: json)
        print(parsed)

        parsed.forEach { aHeterogenousParsedValue in
            switch aHeterogenousParsedValue {
            case .asInt(let intValue):
                print("Got Int: \(intValue)")
            case .asString(let stringValue):
                print("Got String: \(stringValue)")
            case .asCodableStruct(let codableStruct):
                print("Got Struct: \(codableStruct)")
            }
        }
    } catch {
        print("Error while decoding JSON: \(error)")
    }
}

heterogenousJSON()

主要思想是使用 Codable enum with associated values 来保存所有异构值。然后你需要有一个自定义的init(from decoder: Decoder)。我创建了Codable 的值,但实际上我只创建了Decodable 部分。没有反向覆盖。

我使用CustomStringConvertible(及其description)来获得更易读的打印。

我在打印parsed 时添加了forEach(),以向您展示如何处理这些值。如果只需要一种情况,可以使用if case let 代替开关。

正如@vadian in comments 所说,在数组中具有这样的异构值并不是一个好习惯。你说在你的情况下你不能用后端开发改变它们,但我在这个答案中指出它,以防其他人有同样的问题,如果他/她可以改变它,推荐。

【讨论】:

  • 非常感谢,这是一个非常好的解决方案。令人沮丧的是,这种东西来自 websocket 连接。我们这些接收端的人并没有太多考虑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
  • 2016-04-05
  • 2019-02-11
相关资源
最近更新 更多