Search code examples
jsonswiftmodeldecodecodable

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 {
        ...
        ...
    }    
}

Now I need a way to create Classroom instance with a simple String

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

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

Now I need to add a secondary decoder method which can create this classroom instance using the "classroom": "Test" data. The "Test" value should be used as value for "teacher" and other properties should contain default values.

I know I can decode the String value and create a new initializer. Is there a way to directly decode String to this model object?


Solution

  • if i understand well, i assume you have a bad json format like below

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

    And you want to decode both objects, you can do the following

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

    and the result,

    ["test", "Test"]