Swift's JSONDecoder with multiple date formats in a JSON string?

前端 未结 8 1555
既然无缘
既然无缘 2020-12-14 06:48

Swift\'s JSONDecoder offers a dateDecodingStrategy property, which allows us to define how to interpret incoming date strings in accordance with a

相关标签:
8条回答
  • 2020-12-14 07:41

    Swift 5

    Actually based on @BrownsooHan version using a JSONDecoder extension

    JSONDecoder+dateDecodingStrategyFormatters.swift

    extension JSONDecoder {
    
        /// Assign multiple DateFormatter to dateDecodingStrategy
        ///
        /// Usage :
        ///
        ///      decoder.dateDecodingStrategyFormatters = [ DateFormatter.standard, DateFormatter.yearMonthDay ]
        ///
        /// The decoder will now be able to decode two DateFormat, the 'standard' one and the 'yearMonthDay'
        ///
        /// Throws a 'DecodingError.dataCorruptedError' if an unsupported date format is found while parsing the document
        var dateDecodingStrategyFormatters: [DateFormatter]? {
            @available(*, unavailable, message: "This variable is meant to be set only")
            get { return nil }
            set {
                guard let formatters = newValue else { return }
                self.dateDecodingStrategy = .custom { decoder in
    
                    let container = try decoder.singleValueContainer()
                    let dateString = try container.decode(String.self)
    
                    for formatter in formatters {
                        if let date = formatter.date(from: dateString) {
                            return date
                        }
                    }
    
                    throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)")
                }
            }
        }
    }
    

    It is a bit of a hacky way to add a variable that can only be set, but you can easily transform var dateDecodingStrategyFormatters by func setDateDecodingStrategyFormatters(_ formatters: [DateFormatter]? )

    Usage

    lets say that you have already defined several DateFormatters in your code like so :

    extension DateFormatter {
        static let standardT: DateFormatter = {
            var dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
            return dateFormatter
        }()
    
        static let standard: DateFormatter = {
            var dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
            return dateFormatter
        }()
    
        static let yearMonthDay: DateFormatter = {
            var dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd"
            return dateFormatter
        }()
    }
    

    you can now just assign these to the decoder straight away by setting dateDecodingStrategyFormatters :

    // Data structure
    struct Dates: Codable {
        var date1: Date
        var date2: Date
        var date3: Date
    }
    
    // The Json to decode 
    let jsonData = """
    {
        "date1": "2019-05-30 15:18:00",
        "date2": "2019-05-30T05:18:00",
        "date3": "2019-04-17"
    }
    """.data(using: .utf8)!
    
    // Assigning mutliple DateFormatters
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategyFormatters = [ DateFormatter.standardT,
                                               DateFormatter.standard,
                                               DateFormatter.yearMonthDay ]
    
    
    do {
        let dates = try decoder.decode(Dates.self, from: jsonData)
        print(dates)
    } catch let err as DecodingError {
        print(err.localizedDescription)
    }
    

    Sidenotes

    Once again I am aware that setting the dateDecodingStrategyFormatters as a var is a bit hacky, and I dont recommend it, you should define a function instead. However it is a personal preference to do so.

    0 讨论(0)
  • 2020-12-14 07:48

    There is no way to do this with a single encoder. Your best bet here is to customize the encode(to encoder:) and init(from decoder:) methods and provide your own translation for one these values, leaving the built-in date strategy for the other one.

    It might be worthwhile looking into passing one or more formatters into the userInfo object for this purpose.

    0 讨论(0)
提交回复
热议问题