Swift - 如何使 JSONDecoder 正确解析 类 层次结构中的日期数组

Swift - How to make JSONDecoder correctly parse Date array in hierarchy of classes

我有两个 classes,一个扩展另一个(参见下面的 DateDemoDateDemo2)。 如果我在 child 中有一个 [Date],JSONDecoder 无法正确解析它。

这是我在 运行 iOS 上 class DateDecodeTests 的测试。 基本上我只是尝试 testDateDecodingWithHierarchy 测试方法通过返回不同于 nil.

eventsDateTimes3

从这两个测试中可以看出 testDateDecoding 完美地工作,相反 testDateDecodingWithHierarchy 在解析 eventsDateTimes3 字段(该字段仅由 child class DateDemo2).

import Foundation
import XCTest

class DateDecodeTests: XCTestCase {

    class DateDemo :Decodable {
        var dates : [Date]
        var eventsDateTimes : [Date]?
        var eventsDateTimes2 : [Date]?
    }

    class DateDemo2 : DateDemo {
        var eventsDateTimes3 : [Date]?
    }

    func testDateDecoding() throws {

        let json = """
        {
            "dates": ["2018-10-17T23:00:00.000+01:00", "2018-10-18T00:30:00.000+01:00"],
            "eventsDateTimes": ["2018-10-18T22:00:00.000+02:00", "2018-10-31T00:30:00.000+01:00", "2018-11-08T20:00:00.000+01:00"],
            "eventsDateTimes2": [],
        }
        """.data(using: .utf8)!

        let decoder : JSONDecoder = JSONDecoder.myDefaultJsonDecoder

        let date = try decoder.decode(DateDemo.self, from: json)

        print(date.dates)
        print(date.eventsDateTimes)
        print(date.eventsDateTimes2)

        // It prints out:
        // [2018-10-17 22:00:00 +0000, 2018-10-17 23:30:00 +0000]
        // Optional([2018-10-18 20:00:00 +0000, 2018-10-30 23:30:00 +0000, 2018-11-08 19:00:00 +0000])
        // Optional([])

    }

    func testDateDecodingWithHierarchy() throws {

        let json = """
        {
            "dates": ["2018-10-17T23:00:00.000+01:00", "2018-10-18T00:30:00.000+01:00"],
            "eventsDateTimes": ["2018-10-18T22:00:00.000+02:00", "2018-10-31T00:30:00.000+01:00", "2018-11-08T20:00:00.000+01:00"],
            "eventsDateTimes2": [],
            "eventsDateTimes3": ["2018-10-18T22:00:00.000+02:00", "2018-10-31T00:30:00.000+01:00", "2018-11-08T20:00:00.000+01:00"],
        }
        """.data(using: .utf8)!

        let decoder : JSONDecoder = JSONDecoder.myDefaultJsonDecoder

        let date = try decoder.decode(DateDemo2.self, from: json)

        print(date.dates)
        print(date.eventsDateTimes)
        print(date.eventsDateTimes2)
        print(date.eventsDateTimes3)

        // It prints out:
        // [2018-10-17 22:00:00 +0000, 2018-10-17 23:30:00 +0000]
        // Optional([2018-10-18 20:00:00 +0000, 2018-10-30 23:30:00 +0000, 2018-11-08 19:00:00 +0000])
        // Optional([])
        // nil ---> ??? Why is this nil?

        assert(date.eventsDateTimes3 != nil, "eventsDateTimes3 must not be nil")
    }

}

extension JSONDecoder {
    static var myDefaultJsonDecoder: JSONDecoder {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .formatted(DateFormatter.myDefaultDateFormatter)
        return decoder
    }
}

extension DateFormatter {
    static var myDefaultDateFormatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"
        return formatter
    }
}

DateDemo 符合 Decodable 但未明确定义 init(from:Decoder) throws。所以 Swift 综合了 init(from:Decoder) throws 的定义,解码了 DateDemo.

的属性

DateDemo2 继承自 DateDemo。它添加了一个 属性:eventsDateTimes3。它没有定义任何初始值设定项。通常,Swift 会抱怨缺少初始化程序。但是,因为新的 属性 既是 var 也是可选的,Swift 认为它具有默认值 nil.

由于DateDemo2中唯一的新属性有默认值,所以Swift适用automatic initializer inheritance。您的 DateDemo2 class 继承了它的所有 superclass 的指定初始值设定项。只有一个初始化器可以继承:合成的 Decodable 初始化器。继承的初始化程序将 eventsDateTimes3 初始化为其默认值 (nil)。这就是继承初始化器的工作方式。

您希望 Swift 会像 DateDemo 那样通过从 eventsDateTimes3 初始化 eventsDateTimes3DateDemo2 合成一个 Decodable 初始化30=]。但是 Swift 不会那样做。以下是 Itai Ferber(负责 Coding 实施的 Apple 程序员)在 Swift forum 上所说的:

This case would be improved if the compiler could synthesize SuperClass.init(from:) instead of inheriting, but it won't be able to without a refactor of Swift's protocol conformance and inheritance system (and without syntax to disambiguate between "I'm not providing an implementation because I'd like to inherit" vs. "I'm not providing an implementation because I'd like to synthesize")

换句话说,有两种合法的情况:

  1. 您希望您的子class继承其超class的初始化器并默认初始化新属性。
  2. 您希望编译器合成一个初始化器来解码新属性。

你现在总是得到 #1,因为那是添加 Codable 系统之前的所有内容,而且因为没有语法告诉编译器你想要 #2。

所以你需要在DateDemo2中自己实现init(from:)。这是一个实现:

class DateDemo2 : DateDemo {
    var eventsDateTimes3 : [Date]?

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        eventsDateTimes3 = try container.decode([Date]?.self, forKey: .eventsDateTimes3)
        try super.init(from: decoder)
    }

    private enum CodingKeys: String, CodingKey {
        case eventsDateTimes3
    }
}

这取决于您的用例,但另一种解决方法是将公共属性放在协议中并使用它代替继承。 (还允许您使用结构)

protocol AnyDateHolder: Decodable {
    var dates : [Date] {get set}
    var eventsDateTimes : [Date]? {get set}
    var eventsDateTimes2 : [Date]? {get set}
}

class DateDemo: AnyDateHolder {
    var dates : [Date]
    var eventsDateTimes : [Date]?
    var eventsDateTimes2 : [Date]?
}

class DateDemo2: AnyDateHolder {
    var dates : [Date]
    var eventsDateTimes : [Date]?
    var eventsDateTimes2 : [Date]?
    var eventsDateTimes3 : [Date]?
}

第二次测试打印:

[2018-10-17 22:00:00 +0000, 2018-10-17 23:30:00 +0000]
Optional([2018-10-18 20:00:00 +0000, 2018-10-30 23:30:00 +0000, 2018-11-08 19:00:00 +0000])
Optional([])
Optional([2018-10-18 20:00:00 +0000, 2018-10-30 23:30:00 +0000, 2018-11-08 19:00:00 +0000])