【问题标题】:How to use Any in Codable Type如何在 Codable 类型中使用 Any
【发布时间】:2018-06-26 02:34:09
【问题描述】:

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

struct Person: Codable
{
    var id: Any
}

上述代码中的id 可以是String 或Int。这就是id 是Any 类型的原因。

我知道Any 不是Codable。

我需要知道的是如何让它发挥作用。

【问题讨论】:

  • 相关:Swift structures handling multiple tapes for a single property。总而言之:您不应该使用“Any”,但有 2 个可选属性(在您的情况下为“String”类型和“Int”类型之一)并尝试将 JSON 值解码为两者。此外,您的情况实际上很简单,因为“Int”总是可以转换为“String”。
  • 链接的答案也回答了这个问题,即使您使用Any。你不应该使用Any,你应该使用枚举,但是对于Any,该方法仍然以完全相同的方式工作;只需从容器中手动解码,看看它是否有效。如果没有,请转到下一个类型。
  • 这种事情已经回答过很多次了。例如,Int-Or-String 问题在这里得到了巧妙的解决:stackoverflow.com/a/47215561/341994 另见 stackoverflow.com/questions/46392046/… 以及例如stackoverflow.com/questions/44603248/… 在没有充分搜索的情况下打开赏金是一种浪费。
  • @matt:这可以作为 dup 关闭吗?
  • @halfer 我不认为这是一个骗局。以前提出的问题都不能回答我们在这里的问题。此外,没有一个问题可以用如此多的方式来回答。

标签: ios swift codable decodable encodable


【解决方案1】:

量子价值

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

就是这样。让我们测试一下!

JSON 1:id 是 String

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

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

JSON 2:id 是 Int

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

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

更新 1 比较值

这个新段落应该回答 cmets 的问题。

如果您想将量子值与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

更新 2

这部分回答@Abrcd18留下的评论。

您可以将此计算属性添加到Person 结构。

var idAsString: String {
    switch id {
    case .string(let string): return string
    case .int(let int): return String(int)
    }
}

现在要填充标签,只需编写

label.text = person.idAsString

希望对你有帮助。

【讨论】:

  • 首先,让我说,这个解决方案非常聪明,并且非常适合所提出的问题。您可以添加到您的答案是稍后使用该属性:if case let QuantumValue.string(s) = person { print("id is: \(s)") }。如果我们走得更远一点,从代码的逻辑角度做这样的事情是很可怕的。在我看来,更好的解决方案是在响应对象中传达更改,使其仅与单一类型保持一致。
  • 我怀疑我们如何比较(person.id == 123)我无法比较有没有办法将 QuantumValue 转换为 int? @Luca Angeletti
  • @DhanunjayKumar 你明白了吗?
  • @King 不,我无法将quantumValue 与 int 或 double 进行比较。所以我在解析时将 int 转换为 double 。任何线索如何比较?
  • 嘿@Abrcd18,看看我添加到我的答案的更新。
【解决方案2】:

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

【讨论】:

  • 在你使用泛型的方法中,我仍然必须知道我从JSON 得到的id 的数据类型。
  • 是的,因此是我回答的第一句话。如果你不知道你不能使用可编码的类型。您将需要尝试有条件的展开。 Codable 必须知道类型。
【解决方案3】:

我解决了这个问题,定义了一个名为 AnyDecodable 的新可解码结构,所以我使用 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)

【讨论】:

  • 谢谢朱塞佩!这是最实用的答案,即使它不是真正的“AnyCodable”,因为 Any 在这里只能是原始类型(而不是其他自定义 Codable 类型),但对于大多数用例来说应该足够好......顺便说一句,为什么不你不包括其他类型,如日期、数据、URL,它们也是原生支持的吗?
  • 因为在 Json 中它们是字符串。要知道它们代表日期数据和 URL,我们需要我们没有“任何”的洞察力。 ://
  • 我明白了.. 对于我的用例,因为我将编码和解码作为应用程序中的一对(即后端数据也总是从应用程序中保存),我认为它应该只适用于Swift 使用的默认日期/数据/url 方案
  • 这非常有效。干得好。具有讽刺意味的是,最后,我认为这样做毫无意义。当您最终想要在某处使用已解析的数据时,您需要知道该数据在结构中的位置以及它是什么类型。因此,如果您要通过该可选链和强制转换,您不妨首先在标准 Codable 对象中定义它。只是不要解析你不知道的数据,这在 Codable 中通过省略键很容易实现。
  • 这很有趣,@GiuseppeLanza。但是,如果我们看到同构数组,有没有办法通过自动将 [Any] 转换为 [] 来改进解码?
【解决方案4】:

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

基本上我定义了一个新的可解码类型

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】:

    要使 key 成为 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 {
    
            }
    

    【讨论】:

    • 这一行:'if let string' 如果是 'else if let' 会更好。
    • 在枚举中写入这部分解决了我的问题 var any:Any{ get{ switch self { case .double(let value): return value case .int(let value): return value case .string (让值): 返回值 } } }
    【解决方案6】:

    您可以将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 了解如何执行此操作的示例。

    【讨论】:

      【解决方案7】:

      您只需使用来自 Matt Thompson 的酷库 AnyCodable 中的 AnyCodable 类型。

      例如:

      import AnyCodable
      
      struct Person: Codable
      {
          var id: AnyCodable
      }
      

      【讨论】:

      • 有趣,但假设 id 是一个字符串,我如何将它转换/解析为一个字符串,因为id as String 不起作用?
      【解决方案8】:

      首先,正如您可以在其他答案和 cmets 中看到的那样,使用 Any 这不是好的设计。如果可能,请三思。

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

      下面的代码通过将id始终编码为字符串并根据找到的值解码为Int或String来实现它。

      import 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 或String,并将解码尝试包装在do...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 */
          }
      }
      

      在我看来它更丑。

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

      【讨论】:

        【解决方案9】:

        您的id 可以是任何Codable 类型:

        斯威夫特 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")
        

        【讨论】:

        • 不鼓励使用纯代码的答案。请单击edit 并添加一些词来总结您的代码如何解决问题,或者解释您的答案与之前的答案/答案有何不同。谢谢
        • 这与已经接受的答案有何不同?你从字面上复制粘贴。这在很多层面上都是错误的......
        • “在很多层面上都错了”你的意思是什么?不一样。
        【解决方案10】:

        感谢 Luka Angeletti 的回答 (https://stackoverflow.com/a/48388443/7057338) 我已将 enum 更改为 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
            }
        }
        

        【讨论】:

          【解决方案11】:

          Luca Angeletti 的解决方案没有涵盖一个极端案例。

          例如,如果 Cordinate 的类型是 Double 或 [Double],Angeletti 的解决方案会导致错误:“Expected to decode Double but found an array instead”

          在这种情况下,您必须在坐标系中使用嵌套枚举。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-06-26
            • 2018-11-10
            • 1970-01-01
            相关资源
            最近更新 更多