web-dev-qa-db-ja.com

Codable TypeでAnyを使用する方法

現在、私のプロジェクトでCodable型を使用しており、問題に直面しています。

struct Person: Codable
{
    var id: Any
}

上記のコードのidは、StringまたはIntのいずれかです。これが、idAny型である理由です。

AnyCodableではないことを知っています。

私が知る必要があるのは、どのように機能させるかです。

17
PGDev

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")
20
Scriptable

量子値

まず第一に、StringIntの両方の値からデコードできる型を定義できます。ここにあります。

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
}

それでおしまい。テストしてみましょう!

JSON 1:idStringです

let data = """
{
"id": "123"
}
""".data(using: String.Encoding.utf8)!

if let person = try? JSONDecoder().decode(Person.self, from: data) {
    print(person)
}

JSON 2:idIntです

let data = """
{
"id": 123
}
""".data(using: String.Encoding.utf8)!

if let person = try? JSONDecoder().decode(Person.self, from: data) {
    print(person)
}

[更新]値の比較

この新しい段落は、コメントからの質問に答える必要があります。

クォンタム値をIntと比較する場合、クォンタム値にIntまたはStringが含まれることがあることに注意する必要があります。

質問は:StringIntを比較することはどういう意味ですか?

クォンタム値を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
20
Luca Angeletti

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)
11
Giuseppe Lanza

あなたの問題が、文字列または整数値である可能性があるため、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にアクセスできるようになります

4
Giuseppe Lanza

Anyを、IntまたはStringを受け入れる列挙型に置き換えることができます。

enum Id: Codable {
    case numeric(value: Int)
    case named(name: String)
}

struct Person: Codable
{
    var id: Id
}

次に、コンパイラは、IdDecodableに適合しないという事実について文句を言います。 Idには関連付けられた値があるため、これを自分で実装する必要があります。これを行う方法の例については、 https://littlebitesofcocoa.com/318-codable-enums を参照してください。

3
Clafou

キーを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 {

        }
2
indrajit

ここで、idは任意のCodableタイプにすることができます:

Swift 4.2

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")
0
Mad Man

マットトンプソンのクールなライブラリ AnyCodableAnyCodableタイプを使用できます。

例えば:

import AnyCodable

struct Person: Codable
{
    var id: AnyCodable
}
0
Johnykutty

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)
    }
}
0
UnchartedWorks