現在、私のプロジェクトでCodable
型を使用しており、問題に直面しています。
struct Person: Codable
{
var id: Any
}
上記のコードのid
は、String
またはInt
のいずれかです。これが、id
がAny
型である理由です。
Any
はCodable
ではないことを知っています。
私が知る必要があるのは、どのように機能させるかです。
Codableは、キャストする型を知る必要があります。
まず、タイプがわからないという問題に対処し、それを修正して簡単にすることができるかどうかを確認します。
さもなければ、私が現在あなたの問題を解決することを考えることができる唯一の方法は、以下のようなジェネリックを使用することです。
struct Person<T> {
var id: T
var name: String
}
let person1 = Person<Int>(id: 1, name: "John")
let person2 = Person<String>(id: "two", name: "Steve")
まず第一に、String
とInt
の両方の値からデコードできる型を定義できます。ここにあります。
enum QuantumValue: Decodable {
case int(Int), string(String)
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw QuantumError.missingValue
}
enum QuantumError:Error {
case missingValue
}
}
これで、このように構造体を定義できます
struct Person: Decodable {
let id: QuantumValue
}
それでおしまい。テストしてみましょう!
id
はString
ですlet data = """
{
"id": "123"
}
""".data(using: String.Encoding.utf8)!
if let person = try? JSONDecoder().decode(Person.self, from: data) {
print(person)
}
id
はInt
ですlet data = """
{
"id": 123
}
""".data(using: String.Encoding.utf8)!
if let person = try? JSONDecoder().decode(Person.self, from: data) {
print(person)
}
この新しい段落は、コメントからの質問に答える必要があります。
クォンタム値をInt
と比較する場合、クォンタム値にInt
またはString
が含まれることがあることに注意する必要があります。
質問は:String
とInt
を比較することはどういう意味ですか?
クォンタム値をInt
に変換する方法を探している場合は、この拡張機能を追加するだけです
extension QuantumValue {
var intValue: Int? {
switch self {
case .int(let value): return value
case .string(let value): return Int(value)
}
}
}
今、あなたは書くことができます
let quantumValue: QuantumValue: ...
quantumValue.intValue == 123
AnyDecodableと呼ばれる新しいDecodable Structを定義してこの問題を解決したので、Anyの代わりにAnyDecodableを使用します。ネストされた型でも完全に機能します。
遊び場でこれを試してください:
var json = """
{
"id": 12345,
"name": "Giuseppe",
"last_name": "Lanza",
"age": 31,
"happy": true,
"rate": 1.5,
"classes": ["maths", "phisics"],
"dogs": [
{
"name": "Gala",
"age": 1
}, {
"name": "Aria",
"age": 3
}
]
}
"""
public struct AnyDecodable: Decodable {
public var value: Any
private struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}
public init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
var result = [String: Any]()
try container.allKeys.forEach { (key) throws in
result[key.stringValue] = try container.decode(AnyDecodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyDecodable.self).value)
}
value = result
} else if let container = try? decoder.singleValueContainer() {
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
}
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
}
}
}
let stud = try! JSONDecoder().decode(AnyDecodable.self, from: jsonData).value as! [String: Any]
print(stud)
エンコード部分にも関心がある場合は、私の構造体をAnyCodableに拡張できます。
編集:実際にやった。
AnyCodableはこちら
struct AnyCodable: Decodable {
var value: Any
struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}
init(value: Any) {
self.value = value
}
init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
var result = [String: Any]()
try container.allKeys.forEach { (key) throws in
result[key.stringValue] = try container.decode(AnyCodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyCodable.self).value)
}
value = result
} else if let container = try? decoder.singleValueContainer() {
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
}
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
}
}
}
extension AnyCodable: Encodable {
func encode(to encoder: Encoder) throws {
if let array = value as? [Any] {
var container = encoder.unkeyedContainer()
for value in array {
let decodable = AnyCodable(value: value)
try container.encode(decodable)
}
} else if let dictionary = value as? [String: Any] {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dictionary {
let codingKey = CodingKeys(stringValue: key)!
let decodable = AnyCodable(value: value)
try container.encode(decodable, forKey: codingKey)
}
} else {
var container = encoder.singleValueContainer()
if let intVal = value as? Int {
try container.encode(intVal)
} else if let doubleVal = value as? Double {
try container.encode(doubleVal)
} else if let boolVal = value as? Bool {
try container.encode(boolVal)
} else if let stringVal = value as? String {
try container.encode(stringVal)
} else {
throw EncodingError.invalidValue(value, EncodingError.Context.init(codingPath: [], debugDescription: "The value is not encodable"))
}
}
}
}
プレイグラウンドでこの方法で以前のjsonでテストできます:
let stud = try! JSONDecoder().decode(AnyCodable.self, from: jsonData)
print(stud.value as! [String: Any])
let backToJson = try! JSONEncoder().encode(stud)
let jsonString = String(bytes: backToJson, encoding: .utf8)!
print(jsonString)
あなたの問題が、文字列または整数値である可能性があるため、idのタイプが不確実であるということである場合、このブログ投稿を提案できます: http://agostini.tech/2017/11/12/Swift -4-実際の生活の一部-2 /
基本的に、新しいDecodableタイプを定義しました
public struct UncertainValue<T: Decodable, U: Decodable>: Decodable {
public var tValue: T?
public var uValue: U?
public var value: Any? {
return tValue ?? uValue
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
tValue = try? container.decode(T.self)
uValue = try? container.decode(U.self)
if tValue == nil && uValue == nil {
//Type mismatch
throw DecodingError.typeMismatch(type(of: self), DecodingError.Context(codingPath: [], debugDescription: "The value is not of type \(T.self) and not even \(U.self)"))
}
}
}
これからは、Personオブジェクトは
struct Person: Decodable {
var id: UncertainValue<Int, String>
}
id.valueを使用してIDにアクセスできるようになります
Any
を、Int
またはString
を受け入れる列挙型に置き換えることができます。
enum Id: Codable {
case numeric(value: Int)
case named(name: String)
}
struct Person: Codable
{
var id: Id
}
次に、コンパイラは、Id
がDecodable
に適合しないという事実について文句を言います。 Id
には関連付けられた値があるため、これを自分で実装する必要があります。これを行う方法の例については、 https://littlebitesofcocoa.com/318-codable-enums を参照してください。
キーをAnyにする、上記のすべての回答が好きです。ただし、サーバーガイがどのデータタイプを送信するかわからない場合は、(上記のように)Quantumクラスを使用しますが、Quantumタイプの使用または管理はほとんど困難です。デコード可能なクラスキーをAnyデータ型(またはobj-cの愛好家の場合は「id」)にするための私のソリューションは次のとおりです。
class StatusResp:Decodable{
var success:Id? // Here i am not sure which datatype my server guy will send
}
enum Id: Decodable {
case int(Int), double(Double), string(String) // Add more cases if you want
init(from decoder: Decoder) throws {
//Check each case
if let dbl = try? decoder.singleValueContainer().decode(Double.self),dbl.truncatingRemainder(dividingBy: 1) != 0 { // It is double not a int value
self = .double(dbl)
return
}
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw IdError.missingValue
}
enum IdError:Error { // If no case matched
case missingValue
}
var any:Any{
get{
switch self {
case .double(let value):
return value
case .int(let value):
return value
case .string(let value):
return value
}
}
}
}
使用法:
let json = "{\"success\":\"hii\"}".data(using: .utf8) // response will be String
//let json = "{\"success\":50.55}".data(using: .utf8) //response will be Double
//let json = "{\"success\":50}".data(using: .utf8) //response will be Int
let decoded = try? JSONDecoder().decode(StatusResp.self, from: json!)
print(decoded?.success) // It will print Any
if let doubleValue = decoded?.success as? Double {
}else if let doubleValue = decoded?.success as? Int {
}else if let doubleValue = decoded?.success as? String {
}
ここで、id
は任意のCodable
タイプにすることができます:
struct Person<T: Codable>: Codable {
var id: T
var name: String?
}
let p1 = Person(id: 1, name: "Bill")
let p2 = Person(id: "one", name: "John")
マットトンプソンのクールなライブラリ AnyCodable のAnyCodable
タイプを使用できます。
例えば:
import AnyCodable
struct Person: Codable
{
var id: AnyCodable
}
Luca Angelettiのソリューションではカバーされないコーナーケースがあります。
たとえば、CordinateのタイプがDoubleまたは[Double]の場合、アンジェレッティのソリューションは「Doubleをデコードするはずですが、代わりに配列が見つかりました」というエラーが発生します。
この場合、代わりにCordinateでネストされた列挙を使用する必要があります。
enum Cordinate: Decodable {
case double(Double), array([Cordinate])
init(from decoder: Decoder) throws {
if let double = try? decoder.singleValueContainer().decode(Double.self) {
self = .double(double)
return
}
if let array = try? decoder.singleValueContainer().decode([Cordinate].self) {
self = .array(array)
return
}
throw CordinateError.missingValue
}
enum CordinateError: Error {
case missingValue
}
}
struct Geometry : Decodable {
let date : String?
let type : String?
let coordinates : [Cordinate]?
enum CodingKeys: String, CodingKey {
case date = "date"
case type = "type"
case coordinates = "coordinates"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
date = try values.decodeIfPresent(String.self, forKey: .date)
type = try values.decodeIfPresent(String.self, forKey: .type)
coordinates = try values.decodeIfPresent([Cordinate].self, forKey: .coordinates)
}
}