可以是数组或字符串的自定义 Decodable 类型

Custom Decodable type that can be an Array or a String

一个API我处理returns要么:

{
  "title": "Hello World"
}

{
  "title": [{
    "text": "Hello World"
  }]
}

我的想法是 struct TitleStringValue 有一个自定义解码器,像这样:

struct TitleStringValue: Decodable {
    let text: String
    
    struct TitleStringValueInner: Decodable {
        let text: String
    }
    
    init(from decoder: Decoder) throws {
        if let stringContainer = try? decoder.singleValueContainer() {
            text = try stringContainer.decode(String.self)
        } else {
            var arrayContainer = try decoder.unkeyedContainer()
            text = try arrayContainer.decode(TitleStringValueInner.self).text
            while !arrayContainer.isAtEnd {
                _ = try? arrayContainer.decode(TitleStringValueInner.self)
            }
        }
    }
}

struct MyResult: Decodable {
  let title: TitleStringValue
}

但是在 title 由数组组成的情况下,TitleStringValue init(from decoder: Decoder) 从未被调用,因为解码器之前失败,遇到一个需要单一值类型的数组。

有办法解决吗?

我可以在我的 MyResult 结构级别上实现解码,但这意味着每个具有 TitleStringValues 的结构都需要一个自定义解码器,所以我更愿意在 TitleStringValue 级别上实现它。

我的建议是在这两种情况下都保留 title 属性。

首先尝试解码String。如果这无法解码 Text 的数组(我保持名称简单)获取第一项并将 text 值分配给 title.

struct Title: Decodable {
    
    struct Text : Decodable { let text : String }
    
    let title : String
    
    private enum CodingKeys : String, CodingKey { case title }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            title = try container.decode(String.self, forKey: .title)
        } catch DecodingError.typeMismatch {
            let textArray = try container.decode([Text].self, forKey: .title)
            title = textArray.first?.text ?? ""
        }
    }
}

这里的问题是 singleValueContainer 也可用于解码数组。因此,您得到的错误是由 TitleStringValueinit(from:) 函数内部的第二个 try 产生的,而不是之前的

话虽如此,您可以像这样进一步简化您的自定义解码:

struct TitleStringValue: Decodable {
    let text: String
    
    struct TitleStringValueInner: Decodable {
        let text: String
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let string = try? container.decode([TitleStringValueInner].self).first?.text {
            text = string
        } else {
            text = try container.decode(String.self)
        }
    }
}