繁体   English   中英

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

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

我有两个类,一个扩展了另一个类(请参见下面的DateDemoDateDemo2 )。 如果孩子中有[Date] ,则JSONDecoder无法正确解析它。

这是我在iOS上运行的测试类DateDecodeTests 基本上,我只是尝试使用testDateDecodingWithHierarchy测试方法来传递返回与nil不同的eventsDateTimes3

从这两个测试中可以看出, testDateDecoding可以完美地工作,相反, testDateDecodingWithHierarchy在解析eventsDateTimes3字段(该字段仅由子类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的属性进行DateDemo

DateDemo2DateDemo继承。 它添加了一个属性: eventsDateTimes3 它没有定义任何初始化程序。 通常,Swift会抱怨缺少初始化器。 但是,由于new属性既是var又是可选属性,因此Swift认为它的默认值为nil

由于DateDemo2的唯一新属性具有默认值,因此Swift将应用自动初始化程序继承 DateDemo2类继承了其超类的所有指定初始化器。 只能继承一个初始化程序:合成的Decodable初始化程序。 继承的初始化程序将eventsDateTimes3初始化为其默认值( nil )。 这就是继承的初始化程序的工作方式。

你希望斯威夫特会合成一个Decodable初始化为DateDemo2因为它没有为DateDemo ,通过初始化eventsDateTimes3Decoder 但是Swift并没有这样做。 这是Itai Ferber(负责Coding实现的Apple程序员)在Swift论坛上说的:

如果编译器可以合成SuperClass.init(from:)而不是继承,则这种情况将得到改善,但是如果没有重构Swift的协议一致性和继承系统(并且没有语法来消除“我是因为我想继承而没有提供实现”与“因为我想合成而没有提供实现”)

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

  1. 您希望子类继承其超类的初始化程序,并默认初始化新属性。
  2. 您希望编译器合成一个初始化器,该初始化器对新属性进行解码。

您现在总是得到#1,因为这就是添加Codable系统之前的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])

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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