如何在Codable类型中使用Any

时间:2018-01-17 09:07:21

标签: ios swift codable decodable encodable

我目前正在处理项目中的Codable类型并遇到问题。

struct Person: Codable
{
    var id: Any
}
上述代码中的

id可以是StringInt。这就是id属于Any类型的原因。

我知道Any不是Codable

我需要知道的是如何使其发挥作用。

11 个答案:

答案 0 :(得分:21)

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

答案 1 :(得分:19)

量子值

首先,您可以定义一个可以从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)
}

答案 2 :(得分:10)

我解决了这个问题,定义了一个名为AnyDecodable的新Decodable Struct,因此我使用AnyDecodable而不是Any。它也适用于嵌套类型。

在操场上试试这个:

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)

答案 3 :(得分:2)

您可以使用枚举接受AnyInt替换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以获取有关如何执行此操作的示例。

答案 4 :(得分:2)

如果你的问题是不确定id的类型,因为它可能是字符串或整数值,我可以建议你这篇博文:http://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-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

答案 5 :(得分:2)

要使密钥为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 {

        }

答案 6 :(得分:1)

首先,您可以阅读其他答案和评论,因此使用id并不是一个好的设计。如果可能的话,再考虑一下。

那就是说,如果你想坚持自己的原因,你应该编写自己的编码/解码,并在序列化的JSON中采用某种约定。

下面的代码通过将Int始终作为字符串进行编码并根据找到的值解码为Stringimport Foundation struct Person: Codable { var id: Any init(id: Any) { self.id = id } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) if let idstr = try container.decodeIfPresent(String.self, forKey: .id) { if let idnum = Int(idstr) { id = idnum } else { id = idstr } return } fatalError() } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: Keys.self) try container.encode(String(describing: id), forKey: .id) } enum Keys: String, CodingKey { case id } } extension Person: CustomStringConvertible { var description: String { return "<Person id:\(id)>" } } 来实现它。

id

实施例

使用数字var p1 = Person(id: 1) print(String(data: try JSONEncoder().encode(p1), encoding: String.Encoding.utf8) ?? "/* ERROR */") // {"id":"1"} 编码对象:

id

使用字符串var p2 = Person(id: "root") print(String(data: try JSONEncoder().encode(p2), encoding: String.Encoding.utf8) ?? "/* ERROR */") // {"id":"root"} 编码对象:

id

解码为数字print(try JSONDecoder().decode(Person.self, from: "{\"id\": \"2\"}".data(using: String.Encoding.utf8)!)) // <Person id:2>

id

解码为字符串print(try JSONDecoder().decode(Person.self, from: "{\"id\": \"admin\"}".data(using: String.Encoding.utf8)!)) // <Person id:admin>

Int

另一种实现方式是编码为Stringdo...catch并将解码尝试包装在 if let idstr = id as? String { try container.encode(idstr, forKey: .id) } else if let idnum = id as? Int { try container.encode(idnum, forKey: .id) } 中。

在编码部分:

do {
    if let idstr = try container.decodeIfPresent(String.self, forKey: .id) {
        id = idstr
        id_decoded = true
    }
}
catch {
    /* pass */
}

if !id_decoded {
    do {
        if let idnum = try container.decodeIfPresent(Int.self, forKey: .id) {
            id = idnum
        }
    }
    catch {
        /* pass */
    }
}

然后在多次尝试中解码为正确的类型:

My text

我认为这很丑陋。

根据您对服务器序列化的控制,您可以使用其中任何一个或编写适合实际序列化的其他内容。

答案 7 :(得分:0)

Luca Angeletti的解决方案没有涵盖角落案例。

例如,如果Cordinate的类型是Double或[Double],Angeletti的解决方案将导致错误:&#34;预期解码Double但是找到了一个数组&#34;

在这种情况下,您必须在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)
    }
}

答案 8 :(得分:0)

只需使用Matt Thompson酷图书馆AnyCodable中的AnyCodable类型。

例如:

import AnyCodable

struct Person: Codable
{
    var id: AnyCodable
}

答案 9 :(得分:0)

您的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")

答案 10 :(得分:0)

感谢Luka Angeletti的回答(https://stackoverflow.com/a/48388443/7057338),我已将枚举更改为struct,以便我们可以更轻松地使用它

struct QuantumValue: Codable {

    public var string: String?
    public var integer: Int?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let int = try? container.decode(Int.self) {
            self.integer = int
            return
        }
        if let string = try? container.decode(String.self) {
            self.string = string
            return
        }
        throw QuantumError.missingValue
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(string)
        try container.encode(integer)
    }

    enum QuantumError: Error {
         case missingValue
    }

    func value() -> Any? {
        if let s = string {
            return s
        }
        if let i = integer {
            return i
        }
        return nil
    }
}