Jsondecoder Swift с несколькими форматами дат в строке JSON?

Свифт JSONDecoder предлагает dateDecodingStrategy свойство, которое позволяет определить, как интерпретировать входящие строки даты в соответствии с

6 ответов


есть несколько способов справиться с этим:

  • вы можете создать DateFormatter подкласс, который сначала пытается формат строки даты-времени, а затем, если это не удается, пытается простой формат даты
  • вы можете дать .custom Date стратегия декодирования, в которой вы спрашиваете Decoder на singleValueContainer(), декодируйте строку и передайте ее через любые форматеры, которые вы хотите, прежде чем передавать проанализированную дату
  • вы можете создать обертку вокруг Date тип который предоставляет пользовательский init(from:) и encode(to:) который делает это (но это на самом деле не лучше, чем .custom стратегия)
  • вы можете использовать простые строки, как вы предлагаете
  • вы можете предоставить собственный init(from:) на всех типах, которые используют эти даты и еще разные вещи есть

в целом, первые два метода, скорее всего, будут самыми легкими и чистыми - вы сохраните синтезированную по умолчанию реализацию Codable везде без ущерба для безопасности типа.


пожалуйста, попробуйте декодер сконфигурирован следующим образом:

lazy var decoder: JSONDecoder = {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
        let container = try decoder.singleValueContainer()
        let dateStr = try container.decode(String.self)
        // possible date strings: "2016-05-01",  "2016-07-04T17:37:21.119229Z", "2018-05-20T15:00:00Z"
        let len = dateStr.count
        var date: Date? = nil
        if len == 10 {
            date = dateNoTimeFormatter.date(from: dateStr)
        } else if len == 20 {
            date = isoDateFormatter.date(from: dateStr)
        } else {
            date = self.serverFullDateFormatter.date(from: dateStr)
        }
        guard let date_ = date else {
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateStr)")
        }
        print("DATE DECODER \(dateStr) to \(date_)")
        return date_
    })
    return decoder
}()

столкнулись с этой же проблемой, я написал следующее расширение:

extension JSONDecoder.DateDecodingStrategy {
    static func custom(_ formatterForKey: @escaping (CodingKey) throws -> DateFormatter?) -> JSONDecoder.DateDecodingStrategy {
        return .custom({ (decoder) -> Date in
            guard let codingKey = decoder.codingPath.last else {
                throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No Coding Path Found"))
            }

            guard let container = try? decoder.singleValueContainer(),
                let text = try? container.decode(String.self) else {
                    throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode date text"))
            }

            guard let dateFormatter = try formatterForKey(codingKey) else {
                throw DecodingError.dataCorruptedError(in: container, debugDescription: "No date formatter for date text")
            }

            if let date = dateFormatter.date(from: text) {
                return date
            } else {
                throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(text)")
            }
        })
    }
}

это расширение позволяет создать DateDecodingStrategy для JSONDecoder, который обрабатывает несколько различных форматов даты в одной строке JSON. Расширение содержит функцию, которая требует реализации закрытия, которое дает вам CodingKey, и это зависит от вас, чтобы предоставить правильный DateFormatter для предоставленного ключа.

давайте скажем, что у вас есть следующее В JSON:

{
    "publication_date": "2017-11-02",
    "opening_date": "2017-11-03",
    "date_updated": "2017-11-08 17:45:14"
}

следующую структуру:

struct ResponseDate: Codable {
    var publicationDate: Date
    var openingDate: Date?
    var dateUpdated: Date

    enum CodingKeys: String, CodingKey {
        case publicationDate = "publication_date"
        case openingDate = "opening_date"
        case dateUpdated = "date_updated"
    }
}

затем, чтобы декодировать JSON, вы должны использовать следующий код:

let dateFormatterWithTime: DateFormatter = {
    let formatter = DateFormatter()

    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

    return formatter
}()

let dateFormatterWithoutTime: DateFormatter = {
    let formatter = DateFormatter()

    formatter.dateFormat = "yyyy-MM-dd"

    return formatter
}()

let decoder = JSONDecoder()

decoder.dateDecodingStrategy = .custom({ (key) -> DateFormatter? in
    switch key {
    case ResponseDate.CodingKeys.publicationDate, ResponseDate.CodingKeys.openingDate:
        return dateFormatterWithoutTime
    default:
        return dateFormatterWithTime
    }
})

let results = try? decoder.decode(ResponseDate.self, from: data)

нет никакого способа сделать это с помощью одного датчика. Лучше всего для настройки encode(to encoder:) и init(from decoder:) методы и предоставить свой собственный перевод для одного из этих значений, оставив встроенную стратегию даты для другого.

возможно, стоит посмотреть на передачу одного или нескольких форматтеров в


попробуйте это. (swift 4)

var decoder: JSONDecoder {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .custom { decoder in
        let container = try decoder.singleValueContainer()
        let dateString = try container.decode(String.self)

        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        if let date = formatter.date(from: dateString) {
            return date
        }
        formatter.dateFormat = "yyyy-MM-dd"
        if let date = formatter.date(from: dateString) {
            return date
        }
        throw DecodingError.dataCorruptedError(in: container,
            debugDescription: "Cannot decode date string \(dateString)")
    }
    return decoder
}

Если у вас есть несколько дат с разными форматами в одной модели, ее довольно сложно применить .dateDecodingStrategy для каждой даты.

проверьте здесь https://gist.github.com/romanroibu/089ec641757604bf78a390654c437cb0 для удобного решения