我目前正在Codable
我的项目中使用类型并面临一个问题。
struct Person: Codable
{
var id: Any
}
id
在上面的代码中可以是 aString
或Int
. 这就是id
类型的原因Any
。
我知道那Any
不是Codable
。
我需要知道的是如何使它工作。
首先,您可以定义一个可以从 aString
和Int
value 解码的类型。这里是。
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)
}
这个新段落应该回答评论中的问题。
如果要将量子值与 a 进行比较,则Int
必须记住,量子值可能包含 anInt
或 a String
。
String
所以问题是:比较 a和 an是什么意思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
这部分回答@Abrcd18留下的评论。
您可以将此计算属性添加到Person
结构中。
var idAsString: String {
switch id {
case .string(let string): return string
case .int(let int): return String(int)
}
}
现在填充标签只需写
label.text = person.idAsString
希望能帮助到你。
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")
我解决了这个问题,定义了一个名为 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)
如果您的问题是不确定 id 的类型,因为它可能是字符串或整数值,我可以建议您这篇博文:http: //agostini.tech/2017/11/12/swift-4-codable -现实生活中的第 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
只需使用AnyCodable
Matt Thompson 的酷库AnyCodable中的类型。
例如:
import AnyCodable
struct Person: Codable
{
var id: AnyCodable
}
要将 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 {
}
您可以用Any
接受 anInt
或 a的枚举替换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
它并不是好的设计。如果可能的话,再考虑一下。
也就是说,如果您出于自己的原因想坚持下去,您应该编写自己的编码/解码并在序列化 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 */
}
}
在我看来它更丑陋。
根据您对服务器序列化的控制,您可以使用它们中的任何一个或编写适合实际序列化的其他内容。
这里你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")
感谢 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
}
}
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)
}
}