JSONDecoder로 데이터를 디코딩할 때 에러가 없지만 디코딩된 데이터가 비어있는 경우가 있을 수 있습니다

 

 

Decoding 할 데이터

우선 다음과 같은 데이터 모델을 Decoding 하려고 시도하였습니다, 하지만 끊임없이 문제가 발생하였습니다.

@Model
class ExerciseRecordContainer: Identifiable, Decodable {
    
    enum CodingKeys: CodingKey {
        case startDate
        case endDate
        case totalTime
        case routineName
    }
    
    var startDate: Date = Date()
    var endDate: Date = Date()
    var totalTime: Int = 0
    var routineName: String = ""
    
    var exerciseRecordModel: [ExerciseRecordModel] = []
    
    init(startDate: Date, endDate: Date, totalTime: Int, routineName: String, exerciseRecordModel: [ExerciseRecordModel]) {
        self.startDate = startDate
        self.endDate = endDate
        self.totalTime = totalTime
        self.routineName = routineName
        self.exerciseRecordModel = exerciseRecordModel
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        startDate = try container.decode(Date.self, forKey: .startDate)
        endDate = try container.decode(Date.self, forKey: .endDate)
        totalTime = try container.decode(Int.self, forKey: .totalTime)
        routineName = try container.decode(String.self, forKey: .routineName)
    }
}

class ExerciseRecordModel: Codable, Identifiable {
    var id = UUID()
    var exerciseName: String = "달리기"
    var part: [ExercisePart] = []
    var tool: ExerciseTool = ExerciseTool.bodyWeight
    var set: Int = 0
    var count: [Int] = []
    var kg: [Int] = []
    var done: [Bool] = []
    

    init(exerciseName: String, part: [ExercisePart], tool: ExerciseTool, set: Int, count: [Int], kg: [Int], done: [Bool]) {
        self.exerciseName = exerciseName
        self.part = part
        self.tool = tool
        self.set = set
        self.count = count
        self.kg = kg
        self.done = done
    }
}

 

다음으로는 시도한 디코딩 코드입니다. 분명 ExerciseRecordContainer로 디코딩은 문제가 없이 잘 되었습니다. 하지만 디코딩 하였으면 내부에 exerciseRecordModel에도 값이 채워져야 하는데 값을 지속적으로 nil을 반환하는 것과 do catch 문에서 에러도 발생하지 않는 것을 확인하였습니다.

{
    "startDate": 20240507095452,
    "endDate": 20240507095852,
    "totalTime": 44,
    "routineName": "ㅋㅋ 루틴",
    "exerciseRecordModel": [
        {
            "id": "A1B2C3D4-E5F6-G7H8-I9J0-K1L2M3N4O5P6",
            "exerciseName": "스쿼트",
            "part": ["quadriceps"],
            "tool": "barbell",
            "set": 3,
            "count": [12, 10, 8],
            "kg": [60, 70, 80],
            "done": [true, true, false]
        }
    ]
}
recordContainer = try JSONDecoder().decode(ExerciseRecordContainer.self, from: data)

 

수많은 시간을 들여 고생하여 결국 생각한 것은 아래서부터 차근차근 해결해보자 였습니다. ExerciseRecordContainer로 디코딩하기 전에 ExerciseRecordModel로 먼저 디코딩을 해보는 것입니다.

var test = try JSONDecoder().decode(ExerciseRecordModel.self, from: data)

 

do-catch 문으로 에러가 잡혔습니다, 에러는 part의 데이터 형식이 다르다는 것을 확인하였으며 아래처럼 고쳐주게 된다면 문제없이 성공적으로 디코딩이 가능해집니다.

 {
     "startDate": 20240507095452,
     "endDate": 20240507095852,
     "totalTime": 44,
     "routineName": "루틴",
     "exerciseRecordModel": [
         {
             "id": "8749fadd-3a9b-4d53-9250-fb32f98e57dd",
             "exerciseName": "스쿼트",
             "part": ["대퇴사두"],
             "tool": "덤벨",
             "set": 3,
             "count": [12, 10, 8],
             "kg": [60, 70, 80],
             "done": [true, true, false]
         }
     ]
 }

 

하지만 그래도 nil로 값이 디코딩되는 것을 확인할 수 있었습니다. 결국 해결한 방법으로는 이 코드에 경우 required init(from decoder: Decoder)를 완전히 구현하지 않았기 때문에 발생한 에러였음을 확인할 수 있었습니다.

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    startDate = try container.decode(Date.self, forKey: .startDate)
    endDate = try container.decode(Date.self, forKey: .endDate)
    totalTime = try container.decode(Int.self, forKey: .totalTime)
    routineName = try container.decode(String.self, forKey: .routineName)
}

 

@Model 메크로를 사용하여 디코딩하기 위해서는 디코딩할 값들을 모두 어떻게 디코딩할 것인지를 명시해야하는데 exerciseRecordModel은 명시되지 않았기 때문에 발생했었습니다. 고로 아래처럼 수정하면 성공입니다

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    startDate = try container.decode(Date.self, forKey: .startDate)
    endDate = try container.decode(Date.self, forKey: .endDate)
    totalTime = try container.decode(Int.self, forKey: .totalTime)
    routineName = try container.decode(String.self, forKey: .routineName)
    exerciseRecordModel = try container.decode([ExerciseRecordModel].self, forKey: .exerciseRecordModel)
}

 

ytw_developer