Swift 可解码不一致 API

Swift Decodable with inconsistent API

我想知道当 API 发送 JSON 可能与其键入不一致时,是否有一种方法可以将 JSON 解码为结构。在这种情况下,它有时将 属性 作为数组发送,有时作为字符串发送。我不确定如何处理,或者 Decodable 是否有好的方法。下面的例子。 我的结构:

struct Movie: Decodable {
    let title: String
    let cast: [String]
    let director: [String]
}

JSON:

[{
  "title": "Pulp Fiction",
  "cast": [
    "John Travolta",
    "Uma Thurman",
    "Samuel L. Jackson"
  ],
  "director": "Quentin Tarantino"
},
{
  "title": "Spider-Man: Into the Spider-Verse",
  "cast": [
    "John Travolta",
    "Uma Thurman",
    "Samuel L. Jackson"
  ],
  "director": [
    "Bob Persichetti",
    "Peter Ramsey",
    "Rodney Rothman"
  ]
}]

我能够毫无问题地解码蜘蛛侠,但如果只有一个导演,它会以字符串而不是数组的形式出现。有没有办法为此使用 Decodable?我知道我可以手动构建结构,但不必这样做会很好。 不幸的是,我无法控制这里的 API 。 提前致谢。

struct Movie: Codable {
    let title: String
    let cast: [String]
    let director: Director
}

enum Director: Codable {
    case string(String)
    case stringArray([String])

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode([String].self) {
            self = .stringArray(x)
            return
        }
        
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        throw DecodingError.typeMismatch(Director.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Director"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let x):
            try container.encode(x)
        case .stringArray(let x):
            try container.encode(x)
        }
    }
}

typealias Movies = [Movie]

解码不同类型的最通用方法是具有关联值的枚举。

但是在这种类型非常相关的情况下,您还可以编写自定义初始化程序,结构成员以复数形式命名并声明为数组

struct Movie: Decodable {
    let title: String
    let cast: [String]
    let directors: [String]
    
    private enum CodingKeys : String, CodingKey { case title, cast, director }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.title = try container.decode(String.self, forKey: .title)
        self.cast = try container.decode([String].self, forKey: .cast)
        if let singleDirector = try? container.decode(String.self, forKey: .director) {
            self.directors = [singleDirector]
        } else {
            self.directors = try container.decode([String].self, forKey: .director)
        }
    }
}