Как использовать Any в Codable Type

В настоящее время я работаю с типами Codable в своем проекте и сталкиваюсь с проблемой.

struct Person: Codable
{
    var id: Any
}

id в приведенном выше коде может быть либо String либо Int. Это причина, по которой id имеет тип Any.

Я знаю, что Any не Codable.

Мне нужно знать, как я могу заставить его работать.

Ответ 1

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

Ответ 2

Квантовое значение

Прежде всего вы можете определить тип, который может быть декодирован как из значения 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)
}

[ОБНОВЛЕНИЕ] Сравнение значений

Этот новый абзац должен ответить на вопросы из комментариев.

Если вы хотите сравнить квантовое значение с 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

Ответ 3

Я решил эту проблему, определяя новый Decodable Struct под названием AnyDecodable, поэтому вместо 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)

Ответ 4

Если ваша проблема в том, что он не знает тип идентификатора, поскольку он может быть строкой или целочисленным значением, я могу предложить вам эту запись в блоге: 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, используя id.value

Ответ 5

Вы можете заменить 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 для примера того, как это сделать.

Ответ 6

Чтобы сделать ключ как Любой, мне нравятся все вышеприведенные ответы. Но когда вы не уверены, какой тип данных будет отправлен вашим парнем сервера, вы используете класс Quantum (как указано выше), но тип Quantum немного сложно использовать или управлять. Итак, вот мое решение сделать ваш декодируемый ключ класса как Любой тип данных (или "id" для любителей obj-c)

   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 {

        }

Ответ 7

Прежде всего, как вы можете прочитать в других ответах и ​​комментариях, использование 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 */
    }
}

Это уродливое, на мой взгляд.

В зависимости от элемента управления, который у вас есть над сериализацией сервера, вы можете использовать любой из них или написать что-то еще, адаптированное к фактической сериализации.

Ответ 8

Существует угловой корпус, который не покрывается решением Luca Angeletti.

Например, если тип Cordinate Double или [Double], решение Angeletti вызовет ошибку: "Ожидается, что будет декодировать 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)
    }
}

Ответ 9

Просто вы можете использовать AnyCodable тип от Matt Thompson классной библиотеки AnyCodable.

Например:

import AnyCodable

struct Person: Codable
{
    var id: AnyCodable
}

Ответ 10

Здесь ваш 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")