Swift - Как заставить JSONDecoder правильно анализировать массив дат в иерархии классов - PullRequest
0 голосов
/ 18 октября 2018

У меня есть два класса, один расширяет другой (см. DateDemo и DateDemo2 ниже).Если у меня есть [Date] внутри дочернего элемента, JSONDecoder не сможет его правильно проанализировать.

Вот мой тестовый класс DateDecodeTests, который я запускаю на iOS.По сути, я просто пытаюсь пройти тестовый метод testDateDecodingWithHierarchy, возвращая eventsDateTimes3, отличный от nil.

Из этих двух тестов видно, что 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
    }
}

Ответы [ 2 ]

0 голосов
/ 18 октября 2018

Это зависит от вашего варианта использования, но другой обходной путь - поместить общие атрибуты в протокол и использовать их вместо наследования.(также позволяет использовать структуры ?)

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])
0 голосов
/ 18 октября 2018

DateDemo соответствует Decodable, но явно не определяет init(from:Decoder) throws.Таким образом, Swift синтезирует определение init(from:Decoder) throws, которое декодирует свойства DateDemo.

DateDemo2 наследуется от DateDemo.Он добавляет одно свойство: eventsDateTimes3.Он не определяет никаких инициализаторов.Обычно Swift жаловался на отсутствие инициализаторов.Однако, поскольку новое свойство является одновременно var и необязательным, Swift считает, что оно имеет значение по умолчанию nil.

Поскольку единственное новое свойство DateDemo2 имеет значение по умолчанию, Swift применяется автоматическое наследование инициализатора .Ваш класс DateDemo2 наследует все инициализаторы, назначенные его суперклассом.Унаследован только один инициализатор: синтезированный Decodable инициализатор.Унаследованный инициализатор инициализирует eventsDateTimes3 его значением по умолчанию (nil).Именно так работают наследуемые инициализаторы.

Вы надеялись, что Swift синтезирует Decodable инициализатор для DateDemo2, как это было для DateDemo, путем инициализации eventsDateTimes3 из Decoder.Но Свифт не делает этого.Вот что сказал Итай Фербер (программист Apple, ответственный за реализацию Coding) на форуме Swift :

Этот случай был бы улучшен, если бы компилятор мог синтезировать SuperClass.init(from:)вместо наследования, но он не сможет обойтись без рефакторинга системы соответствия и наследования протоколов Swift (и без синтаксиса для устранения неоднозначности между «я не предоставляю реализацию, потому что я хотел бы наследовать» и «я»).я не предоставляю реализацию, потому что я хотел бы синтезировать ")

Другими словами, есть два законных случая:

  1. Вы хотите, чтобы ваш подкласс наследовал свой суперклассinitializer и default-initialize новые свойства.
  2. Вы хотите, чтобы компилятор синтезировал инициализатор, который декодирует новые свойства.

Вы всегда получаете # 1 сейчас, потому что это все, что былодо того, как была добавлена ​​система Codable, и потому что нет синтаксиса для сообщения компилятору, что вы хотите # 2.

Так что вам нужно внедрить init(from:) самостоятельно в DateDemo2.Вот реализация:

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
    }
}
...