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

问题描述

我处理的 API 返回:

{
  "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 结构的级别上实现解码,但这意味着每个具有 TitleStringValue 的结构都需要一个自定义解码器,所以我更愿意在 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 也可用于解码数组。因此,您得到的错误是由 tryinit(from:) 函数内的第二个 TitleStringValue 而不是之前产生的。

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

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)
        }
    }
}