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

Time:04-30

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?

CodePudding user response:

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"] 

CodePudding user response:

Decode the second, nested case, as another type

struct SimpleClassroom: Decodable {
    let classroom: String
}

and then have a computed property for mapping to the original type with default values

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

CodePudding user response:

If "Test" is a valid description of classroom, and you want to go ahead and create the classroom, then you have a number of options.

If you know classrooms from a given API endpoint will always be in this string format, you can use the decoder's context dictionary to tell it up front which strategy to use to decode the classroom. If sometimes a classroom is a properly formed dictionary, and sometimes it's just a string, and you want to proceed either way, then you have to handle that case in the init(from:).

Either way you're looking at a custom init method. The second case, where you handle both types, would look like this:

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

Given this made-up, horrible JSON:

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

Gives you two valid Classroom types in an array.

  • Related