简体   繁体   中英

How to JSON encode multiple date formats within the same struct

Need to encode into JSON, a struct that has 2 Date instance variables (day and time), however, I need to encode each date instance variable with a different format, ie. for "day":"yyyy-Md" and "time":"H:m:s".

Have written a custom decoder which works no problems. But not sure how to write the required custom encoder to solve this.

For example I can decode the following JSON string: { "biometrics" : [ {"biometricId":1,"amount":2.1,"source":"Alderaan","day":"2019-1-3","time":"11-3-3","unitId":2}, {"biometricId":10,"amount":3.1,"source":"Endoor","day":"2019-2-4","time":"11-4-4","unitId":20}] }

However, when I encode it, I can only encode it in a single date format :( Help, would be greatly appreciated. Thank you.

import UIKit

let biometricsJson = """
{ "biometrics" : [
    {"biometricId":1,"amount":2.1,"source":"Alderaan","day":"2019-1-3","time":"11-3-3","unitId":2},
    {"biometricId":10,"amount":3.1,"source":"Endoor","day":"2019-2-4","time":"11-4-4","unitId":20}]
}
"""

struct Biometrics: Codable {
    var biometrics: [Biometric]
}

struct Biometric: Codable {

    var biometricId: Int
    var unitId: Int
    var source: String?
    var amount: Double
    var day: Date
    var time: Date

    init(biometricId: Int, unitId: Int, source: String, amount: Double, day: Date, time: Date){
        self.biometricId = biometricId
        self.unitId = unitId
        self.source = source
        self.amount = amount
        self.day = day
        self.time = time
    }
}

extension Biometric {

    static let 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.timeZone = TimeZone.current
            formatter.dateFormat = "H:m:s"
            if let date = formatter.date(from: dateString) {
                return date
            }

            formatter.dateFormat = "yyyy-M-d"
            if let date = formatter.date(from: dateString) {
                return date
            }
            throw DecodingError.dataCorruptedError(in: container,
                                                   debugDescription: "Cannot decode date string \(dateString)")
        }
        return decoder
    }()
}

let biometrics = try Biometric.decoder.decode(Biometrics.self, from:biometricsJson.data(using: .utf8)!)

let jsonEncoder = JSONEncoder()
let encodedJson = try jsonEncoder.encode(biometrics)
let jsonString = String(data: encodedJson, encoding: .utf8)
if biometricsJson != jsonString {
    print("error: decoding, then encoding does not give the same string")
    print("biometricsJson: \(biometricsJson)")
    print("jsonString: \(jsonString!)")
}

I expect the encoded JSON, to be decodable by the decoder. ie biometricsJson == jsonString

In a custom encode(to:) , just encode each one as a string using the desired formatter. There's no "date" type in JSON; it's just a string. Something along these lines:

enum CodingKeys: CodingKey {
    case biometricId, amount, source, day, time, unitId
}

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(biometricId, forKey: .biometricId)
    try container.encode(unitId, forKey: .unitId)
    try container.encode(source, forKey: .source)
    try container.encode(amount, forKey: .amount)

    let formatter = DateFormatter()
    formatter.timeZone = TimeZone.current
    formatter.dateFormat = "H:m:s"
    let timeString = formatter.string(from: time)
    try container.encode(timeString, forKey: .time)

    formatter.dateFormat = "yyyy-M-d"
    let dayString = formatter.string(from: day)
    try container.encode(dayString, forKey: .day)
}

But note that you can't test for equivalent strings. JSON dictionaries aren't order-preserving, so there's no way to guarantee a character-by-character match.

Note that if you really want to have days and times, you should consider DateComponents rather than a Date . A date is a specific instance in time; it's not in any time zone, and it can't be just an hour, minute, and second.

Also, your use of Double is going to cause rounding differences. So 2.1 will be encoded as 2.1000000000000001. If that's a problem, you should use Decimal for amount rather than Double .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM