iOS 개발자가 많이 하는 실수 - JSONDecoder에서 Date 포맷/전략을 설정하지 않아 날짜 디코딩이 실패하는 실수
Dev Study/iOS 2025. 12. 4. 20:27iOS 개발자가 많이 하는 실수 - JSONDecoder에서 Date 포맷/전략을 설정하지 않아 날짜 디코딩이 실패하는 실수
네트워크 API에서 날짜(Date) 필드는 매우 자주 등장하지만,
Swift의 JSONDecoder는 기본적으로 날짜 문자열을 자동으로 파싱하지 않습니다.
그래서 초보자가 가장 자주 겪는 문제 중 하나가:
“왜 Date 디코딩이 계속 nil이 되지?”
“왜 decodingError.dataCorrupted가 발생하지?”
라는 상황입니다.
1. 문제 패턴: 날짜가 포함된 JSON 디코딩 실패
예를 들어 서버 응답이 다음과 같다고 해봅시다.
{
"id": 1,
"createdAt": "2024-10-09T18:00:00Z"
}
Swift 모델:
struct Post: Decodable {
let id: Int
let createdAt: Date // ❌ Date
}
초보자가 흔히 쓰는 디코딩 코드:
let post = try? JSONDecoder().decode(Post.self, from: data)
결과:
- 대부분 nil이 나오거나
- DecodingError.dataCorrupted 발생
왜?
JSONDecoder는 기본적으로 Date 타입을 파싱할 방법을 모른다.
2. JSONDecoder 기본 Date 전략은 Date → Number(timestamp) 형태만 지원
공식 설명에 따르면:
- JSONDecoder 기본 Date 전략 = deferredToDate
- 이는 Date가 숫자(TimeInterval) 형태로 들어올 때만 작동
즉, 서버가 아래처럼 timestamp를 전달하면 정상 디코딩됨:
{ "createdAt": 1700000000 }
하지만 대부분의 서버는 ISO8601 문자열을 사용한다:
"2024-10-09T18:00:00Z"
이 문자열은 기본 JSONDecoder로는 절대 디코딩되지 않는다.
3. 해결책: DateDecodingStrategy 지정
3-1. 가장 많이 사용하는 ISO8601 포맷
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
일반적인 REST API(특히 Node, Go, Python FastAPI, Rails, Java Spring)의 표준 출력과 대부분 호환됨.
3-2. millisecond 기반 timestamp (자주 사용)
서버가 아래처럼 ms timestamp를 내려줄 수도 있다:
{ "createdAt": 1700000000123 }
Swift에서 이를 처리하려면:
decoder.dateDecodingStrategy = .millisecondsSince1970
또는 second 기반:
decoder.dateDecodingStrategy = .secondsSince1970
3-3. 커스텀 포맷(DateFormatter 사용)
서버 날짜가 다음처럼 예쁘게 포맷된 문자열일 경우:
"2024-10-09 18:00:00"
이런 경우:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = .utc
decoder.dateDecodingStrategy = .formatted(formatter)
중요:
- locale은 반드시 "en_US_POSIX"
- timezone도 명확하게 지정
3-4. 여러 날짜 포맷을 허용해야 할 때
현실에서는 서버가 날짜 포맷을 일관되게 유지하지 않는 경우가 많다.
이럴 때는 커스텀 전략으로 포맷 시도 순서를 정할 수 있다.
decoder.dateDecodingStrategy = .custom { decoder -> Date in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
let formats = [
"yyyy-MM-dd'T'HH:mm:ssZ",
"yyyy-MM-dd HH:mm:ss",
"yyyy/MM/dd HH:mm:ss"
]
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
for format in formats:
formatter.dateFormat = format
if let date = formatter.date(from: dateString) {
return date
}
}
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Unsupported date format: \(dateString)"
)
}
4. 실무에서 흔한 문제들
4-1. 서버 날짜 끝에 Z(UTC) 유무
예:
- "2024-10-09T18:00:00Z" → ISO8601 표준
- "2024-10-09T18:00:00" → timezone 정보 없음
timezone이 없으면 Swift가 디코딩에 실패하거나
로컬 시간대로 오해할 수 있다.
4-2. 서버가 fractional seconds(소수점 초)를 포함하는 경우
예:
"2024-10-09T18:00:00.123Z"
기본 .iso8601 전략은 fractional seconds를 지원하지 않는다.
해결책:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601WithFractionalSeconds
Swift에는 기본 API가 없지만, 확장을 통해 쉽게 만들 수 있다.
4-3. Date 대신 String으로 먼저 받은 다음 변환하는 실수
초보자들이 자주 하는 코드:
let createdAtString = try container.decode(String.self, forKey: .createdAt)
let createdAt = Date() // ❌ 임시 값 넣어버림
이건 날짜를 아예 버리는 셈.
→ 반드시 Date로 정확히 디코딩하도록 해야 한다.
5. 좋은 Date 모델 설계 예시
서버 응답
{
"id": 1,
"createdAt": "2024-10-09T18:00:00Z"
}
Swift 모델 + Date 전략 설정
struct Post: Decodable {
let id: Int
let createdAt: Date
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let post = try decoder.decode(Post.self, from: data)
6. async/await 기반 네트워크 코드에서도 동일함
func loadPost() async throws -> Post {
let (data, _) = try await URLSession.shared.data(from: url)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(Post.self, from: data)
}
7. 실무용 체크리스트
다음 질문에 YES라고 답할 수 있어야 한다.
- 서버의 실제 날짜 포맷을 정확히 알고 있는가?
- JSONDecoder의 dateDecodingStrategy가 설정되어 있는가?
- timezone, fractional seconds 여부를 점검했는가?
- 여러 포맷이 혼재된 경우 custom 전략을 적용했는가?
- decode 실패 시 원인을 알 수 있도록 do-catch로 처리했는가?
8. 요약
- JSONDecoder는 기본적으로 문자열 날짜를 자동 디코딩하지 않는다.
- 서버 날짜 포맷에 맞춰 dateDecodingStrategy를 반드시 설정해야 한다.
- 가장 흔한 실수는 ISO8601 날짜를 .iso8601 전략 없이 파싱하려는 것.
- fractional seconds, timezone 유무 등도 디코딩 실패의 원인이 된다.
핵심 문장:
날짜가 디코딩되지 않는 것은 Swift가 잘못한 게 아니라, Date 전략을 설정하지 않았기 때문이다.

