解码期间未从其他结构中找到 CodingKey

CodingKey not found from a different struct during decoding

我正在尝试使用来自 Datum 结构的已解析日期字符串 (dateString) 在 AzimuthAtDate class...

{
    "11": [{
        "name": "Sun",
        "date": "2021-01-26",
        "rising": [{
            "coord": "113.6681",
            "hour": "07:16"
        }],
        "transit-sup": [{
            "coord": "32.4711",
            "hour": "12:16"
        }],
        "setting": [{
            "coord": "246.4743",
            "hour": "17:17"
        }]
    }]
}
struct Datum: Codable, CustomStringConvertible {

    let name: String
    let dateString: String
    let isoDate: Date?
    let rising: [AzimuthAtDate]
    let transit: [ElevationAtDate]
    let setting: [AzimuthAtDate]
    
    enum CodingKeys: String, CodingKey {
        case name
        case dateString = "date"
        case isoDate
        case rising = "rising"
        case transit = "transit-sup"
        case setting = "setting"
    }

    init(from decoder: Decoder) throws {
        let con = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try con.decode(String.self, forKey: .name)
        self.dateString = try con.decode(String.self, forKey: .dateString)
        self.isoDate = try con.decode(Date.self, forKey: .dateString)
        self.rising = decodeAzimuthAtDate(decoder: decoder, key: .rising)
        self.transit = decodeElevationAtDate(decoder: decoder, key: .transit)
        self.setting = decodeAzimuthAtDate(decoder: decoder, key: .setting)
    }
}

class CoordHour: Codable {
    let coord: String
    let hour: String
    var isoDate: Date?
    init(coord: String, hour: String, isoDate: Date?) {
        self.coord = coord
        self.hour = hour
        self.isoDate = isoDate
    }
}

但是在尝试从 AzimuthAtDate 中访问 Datum.CodingKeys 的密钥时,datumContainer 没有密钥。所以我得到这样的错误: keyNotFound(CodingKeys(stringValue: "date", intValue: nil) ... debugDescription: "No value associated with key CodingKeys(stringValue: \"date\", intValue: nil) (\"date\").", underlyingError: nil))

class AzimuthAtDate: CoordHour {
    required init(from decoder: Decoder) throws {
        let coordHourContainer = try decoder.container(keyedBy: CoordHour.CodingKeys.self)
        let coord = try coordHourContainer.decode(String.self, forKey: .coord)
        let hour = try coordHourContainer.decode(String.self, forKey: .hour)
        
        print("coord:\(coord) - hour:\(hour)") // prints correct info
        
        let datumContainer = try decoder.container(keyedBy: Datum.CodingKeys.self)

        print("datumContainer keys:\(datumContainer.allKeys)") // shows []
        
        let dateString = try datumContainer.decode(String.self, forKey: .dateString)
        print("dateString:\(dateString)")
        
        let currentTZ = TimeZone.currentOffsetFromUTCString
        let isoDateString = "\(dateString)T\(hour)\(currentTZ)"
        let isoDate = DateFormatter.Iso.date(from: isoDateString)
        
        super.init(coord: coord, hour: hour, isoDate: isoDate)
    }
}

AzimuthAtDate class 中,我怎样才能访问 Datum.dateString 键以便我可以使用它的值来形成 isoDate?

func decodeAzimuthAtDate(decoder: Decoder, key: Datum.CodingKeys) -> [AzimuthAtDate] {
    var result = [AzimuthAtDate]()
    do {
        let con = try decoder.container(keyedBy: Datum.CodingKeys.self)
        if let az = try con.decode([AzimuthAtDate]?.self, forKey: key)?.first {
            result.append(az)
        }
    } catch let error {
        print(error)
    }
    return result
}

忽略解码和JSON并从更一般的角度考虑问题。内部结构如何知道将其保存为 属性 的外部结构?

例如,这里会发生什么:

struct InnerStruct {
    func test() {
        // ?
    }
}
struct OuterStruct {
    let inner = InnerStruct()
    let other = "howdy"
}

...为了调用 innertest() 来打印 "howdy"?显然 inner 的 InnerStruct 需要 reference 到持有它的 OuterStruct。像这样:

struct InnerStruct {
    weak var container : OuterStruct?
    func test() {
        print(self.container?.other)
    }
}
class OuterStruct {
    var inner = InnerStruct()
    let other = "howdy"
    init() {
        self.inner.container = self
    }
}
let outerStruct = OuterStruct()
outerStruct.inner.test()

这就是您需要做的事情。您的内部类型需要一个 属性 这是对其容器的引用,以便它们可以“看到”该日期,并且您需要在配置其他所有内容时设置该引用。或者您甚至可以只使用外部日期来设置内部类型的 属性。但是你不能把它作为解码的一部分;必须在所有配置完成后完成。

根据您的数据(这是 playground 代码),这是一种可能性的简单示例:

let json = """
{
    "11": [{
        "name": "Sun",
        "date": "2021-01-26",
        "rising": [{
            "coord": "113.6681",
            "hour": "07:16"
        }],
        "transit-sup": [{
            "coord": "32.4711",
            "hour": "12:16"
        }],
        "setting": [{
            "coord": "246.4743",
            "hour": "17:17"
        }]
    }]
}
"""

let jsonData = json.data(using: .utf8)

class Inner: Decodable, CustomStringConvertible {
    let coord : String
    let hour : String
    var outerDate : String?
    var description : String {
        return (self.coord + ";" + self.hour + ";" + (self.outerDate ?? ""))
    }
}
struct Outer: Decodable {
    let name: String
    let date: String
    let rising: [Inner]
    let transitsup: [Inner]
    let setting: [Inner]
    enum CodingKeys: String, CodingKey {
        case name
        case date
        case rising
        case transitsup = "transit-sup"
        case setting
    }
}
struct Main: Decodable {
    let x: [Outer]
    enum CodingKeys: String, CodingKey {
        case x = "11"
    }
}
let result = try! JSONDecoder().decode(Main.self, from: jsonData!)
let outers = result.x
for anOuter in outers {
    let date = anOuter.date
    for anInner in anOuter.rising {
        anInner.outerDate = date
    }
    for anInner in anOuter.setting {
        anInner.outerDate = date
    }
    for anInner in anOuter.transitsup {
        anInner.outerDate = date
    }
}
print(outers)

如您所见,完成后,每个 Inner 都知道其容器的 date。因此它可以继续计算日期时间。