我有一個不一致的 API,它可能會回傳 aString或 anNumber作為 JSON 回應的一部分。
日期也可以與 aString或 a相同的方式表示Number,但始終是 UNIX 時間戳(即timeIntervalSince1970)。
為了解決日期問題,我只是使用了自定義JSONDecoder.DateDecodingStrategy:
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.custom({ decoder in
let container = try decoder.singleValueContainer()
if let doubleValue = try? container.decode(Double.self) {
return Date(timeIntervalSince1970: doubleValue)
} else if let stringValue = try? container.decode(String.self),
let doubleValue = Double(stringValue) {
return Date(timeIntervalSince1970: doubleValue)
}
throw DecodingError.dataCorruptedError(in: container,
debugDescription: "Unable to decode value of type `Date`")
})
但是,對于我想要應用它的Int或型別,沒有這樣的定制可用。Double
因此,我不得不為Codable我正在使用的每種模型型別撰寫初始化程式。
我正在尋找的替代方法是子類化JSONDecoder并覆寫該decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable方法。
在該方法中,我想“檢查”T我嘗試解碼的型別,然后,如果基本實作 ( super) 失敗,請嘗試先解碼該值String,然后再解碼為T(目標型別)。
到目前為止,我最初的原型是這樣的:
final class CustomDecoder: JSONDecoder {
override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
do {
return try super.decode(type, from: data)
} catch {
if type is Int.Type {
print("Trying to decode as a String")
if let decoded = try? super.decode(String.self, from: data),
let converted = Int(decoded) {
return converted as! T
}
}
throw error
}
}
}
但是,我發現該"Trying to decode as a String"訊息由于某種原因從未列印,即使控制元件到達catch階段。
我很高興只為IntandDouble型別提供自定義路徑,因為Tis Codableand that 并不能保證使用 初始化值的能力String,但是,我當然歡迎更通用的方法。
Here's the sample Playground code that I came up with to test my prototype. It can be copy-pasted directly into the Playground and works just fine.
My goal is to have both jsonsample1 and jsonsample2 to produce the same result.
import UIKit
final class CustomDecoder: JSONDecoder {
override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
do {
return try super.decode(type, from: data)
} catch {
if type is Int.Type {
print("Trying to decode as a String")
if let decoded = try? super.decode(String.self, from: data),
let converted = Int(decoded) {
return converted as! T
}
}
throw error
}
}
}
let jsonSample1 =
"""
{
"name": "Paul",
"age": "38"
}
"""
let jsonSample2 =
"""
{
"name": "Paul",
"age": 38
}
"""
let data1 = jsonSample1.data(using: .utf8)!
let data2 = jsonSample2.data(using: .utf8)!
struct Person: Codable {
let name: String?
let age: Int?
}
let decoder = CustomDecoder()
let person1 = try? decoder.decode(Person.self, from: data1)
let person2 = try? decoder.decode(Person.self, from: data2)
print(person1 as Any)
print(person2 as Any)
What could be the reason for my CustomDecoder not working?
uj5u.com熱心網友回復:
您的解碼器沒有按照您的預期執行的主要原因是您沒有覆寫您想要的方法:是您呼叫時呼叫JSONDecoder.decode<T>(_:from:)的頂級方法
try JSONDecoder().decode(Person.self, from: data)
但這不是解碼期間內部呼叫的方法。給定您作為示例顯示的 JSON,如果我們將Person結構撰寫為
struct Person: Decodable {
let name: String
let age: Int
}
然后編譯器將撰寫一個如下所示的init(from:)方法:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
age = try container.decode(Int.self, forKey: .age)
}
請注意,當我們 decode 時age,我們不是直接呼叫解碼器上的方法,而是呼叫KeyedCodingContainer從解碼器獲得的 a ——特別Int.Type是KeyedDecodingContainer.decode(_:forKey:).
為了在 a 的中間層掛鉤到解碼期間呼叫的方法Decoder,您需要掛鉤到它的實際容器方法,這非常困難——所有JSONDecoder的容器和內部都是私有的。為了通過子類化來做到這一點JSONDecoder,您最終需要從頭開始重新實作整個事情,這比您嘗試做的事情要復雜得多。
正如評論中所建議的那樣,您可能會更好:
Person.init(from:)通過嘗試對屬性和屬性進行解碼并Int.self保留成功的任何一個來手動撰寫,或者String.self.age如果您需要跨多種型別重用此解決方案,您可以撰寫一個包裝器型別以用作屬性:
struct StringOrNumber: Decodable { let number: Double init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { number = try container.decode(Double.self) } catch (DecodingError.typeMismatch) { let string = try container.decode(String.self) if let n = Double(string) { number = n } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Value wasn't a number or a string...") } } } } struct Person: Decodable { let name: String let age: StringOrNumber }You can also write
StringOrNumberas anenumwhich can hold eithercase string(String)orcase number(Double)if knowing which type of value was in the payload was important:enum StringOrNumber: Decodable { case number(Double) case string(String) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { self = try .number(container.decode(Double.self)) } catch (DecodingError.typeMismatch) { let string = try container.decode(String.self) if let n = Double(string) { self = .string(string) } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Value wasn't a number or a string...") } } } }Though this isn't as relevant if you always need
Double/Intaccess to the data, since you'd need to re-convert at the use site every time (and you call this out in a comment)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/435546.html
標籤:json swift type-conversion codable decodable
上一篇:用另一個鏈接替換鏈接的每個實體
