Swift - 如何为单个模型使用多个解码器方法

Swift - How to have multiple decoder method for a single model

struct Classroom: Codable {
  let teacher: String
  let id: Int
  let status: Bool

    init(from decoder: Decoder) throws {
        ...
        ...
    }    
}

现在我需要一种使用简单字符串

创建 Classroom 实例的方法

{ "classroom": "Test" }//received from API

let classRoom = ClassRoom(teacher: "Test", id: 0, status: true)

现在我需要添加一个辅助 decoder 方法,它可以使用 "classroom": "Test" 数据创建此 classroom 实例。 “测试”值应用作“教师”的值,其他属性应包含默认值。

我知道我可以解码 String 值并创建一个新的初始化程序。有没有办法直接解码 String 到这个模型对象?

将第二个嵌套案例解码为另一种类型

struct SimpleClassroom: Decodable {
    let classroom: String
}

然后有一个计算的 属性 用于映射到具有默认值的原始类型

extension SimpleClassroom {
    var classroomValue: Classroom {
        Classroom(teacher: classroom, id: 0, status: false)
    }
}

如果我理解得很好,我假设你有一个糟糕的 json 格式,如下所示

[
  {
     "teacher":"test",
     "id":5,
     "status":true
  },
  {
     "classroom":"Test"
  }
]

而你想对这两个对象进行解码,你可以执行以下操作

let data = """
[
  {
    "teacher": "test",
    "id": 5,
    "status": true
  },
  {
    "classroom": "Test"
  }
]
""".data(using: .utf8)!

struct Classroom: Codable {
    
  let teacher: String
  let id: Int
  let status: Bool
    
    private enum CodingKeys: CodingKey {
        case teacher, id, status
    }
    
    private enum SecCodingKeys: CodingKey {
        case classroom
    }
    
    init(from decoder: Decoder) throws {
        let value = try decoder.container(keyedBy: CodingKeys.self)
        let secValue = try decoder.container(keyedBy: SecCodingKeys.self)
        let teacher_1 = try value.decodeIfPresent(String.self, forKey: .teacher)
        let teacher_2 = try secValue.decodeIfPresent(String.self, forKey: .classroom)
        teacher = teacher_1 ?? teacher_2 ?? ""
        id = try value.decodeIfPresent(Int.self, forKey: .id) ?? 0
        status = try value.decodeIfPresent(Bool.self, forKey: .status) ?? false
    }
}
do {
    let rooms = try JSONDecoder().decode([Classroom].self, from: data)
    print(rooms.map(\.teacher))
} catch {
    print(error)
}

结果,

["test", "Test"] 

如果 "Test" 是对教室的有效描述,并且您想继续创建教室,那么您有多种选择。

如果您知道来自给定 API 端点的教室将始终采用这种字符串格式,您可以使用解码器的上下文字典预先告诉它使用哪种策略来解码教室。如果 有时 一个教室是一个格式正确的字典,有时它只是一个字符串,而你想继续进行,那么你必须在 init(from:) 中处理这种情况。

无论哪种方式,您都在查看自定义初始化方法。第二种情况,您处理两种类型,看起来像这样:

init(from decoder: Decoder) throws {
    // Do we have a single-value container? 
    do {
        let container = try decoder.singleValueContainer()
        let string = try container.decode(String.self)
        self.teacher = string
        self.id = 0
        self.status = true
        return
    } catch {
        // OK, it was a dictionary
    }
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.teacher = try container.decode(String.self, forKey: .teacher)
    self.id = try container.decode(Int.self, forKey: .id)
    self.status = try container.decode(Bool.self, forKey: .status)
}

鉴于此 made-up,可怕 JSON:

[
    "Test",
    { "teacher": "Mr Chips", "id": 0, "status": true }
]
let rooms = try JSONDecoder().decode([Classroom].self, from: data)

在数组中为您提供两个有效的 Classroom 类型。