반응형

iOS 개발자가 많이 하는 실수 - 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라고 답할 수 있어야 한다.

  1. 서버의 실제 날짜 포맷을 정확히 알고 있는가?
  2. JSONDecoder의 dateDecodingStrategy가 설정되어 있는가?
  3. timezone, fractional seconds 여부를 점검했는가?
  4. 여러 포맷이 혼재된 경우 custom 전략을 적용했는가?
  5. decode 실패 시 원인을 알 수 있도록 do-catch로 처리했는가?

8. 요약

  • JSONDecoder는 기본적으로 문자열 날짜를 자동 디코딩하지 않는다.
  • 서버 날짜 포맷에 맞춰 dateDecodingStrategy를 반드시 설정해야 한다.
  • 가장 흔한 실수는 ISO8601 날짜를 .iso8601 전략 없이 파싱하려는 것.
  • fractional seconds, timezone 유무 등도 디코딩 실패의 원인이 된다.

핵심 문장:

날짜가 디코딩되지 않는 것은 Swift가 잘못한 게 아니라, Date 전략을 설정하지 않았기 때문이다.

 

반응형
Posted by 까칠코더
,