如何在Codable类型中使用Any

时间:2022-03-23 15:52:50

I'm currently working with Codable types in my project and facing an issue.

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

struct Person: Codable
{
    var id: Any
}

id in the above code could be either a String or an Int. This is the reason id is of type Any.

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

I know that Any is not Codable.

我知道Any不是Codable。

What I need to know is how can I make it work.

我需要知道的是我如何才能使它发挥作用。

8 个解决方案

#1


13  

Codable needs to know the type to cast to.

Codable需要知道要转换的类型。

Firstly I would try to address the issue of not knowing the type, see if you can fix that and make it simpler.

首先,我会尝试解决不知道类型的问题,看看你是否可以修复它并使其更简单。

Otherwise the only way I can think of solving your issue currently is to use generics like below.

否则,我现在想到解决问题的唯一方法就是使用如下的泛型。

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


10  

Quantum Value

First of all you can define a type that can be decoded both from a String and Int value. Here it is.

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

Person

Now you can define your struct like this

现在您可以像这样定义结构

struct Person: Decodable {
    let id: QuantumValue
}

That's it. Let's test it!

而已。我们来试试吧!

JSON 1: id is 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 is Int

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

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

#3


6  

I solved this issue defining a new Decodable Struct called AnyDecodable, so instead of Any I use AnyDecodable. It works perfectly also with nested types.

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

Try this in a playground:

在游乐场试试这个:

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)

You could extend my struct to be AnyCodable if you are interested also in the Encoding part.

如果您对编码部分感兴趣,可以将我的结构扩展为AnyCodable。

Edit: I actually did it.

编辑:我实际上做到了。

Here is 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"))
      }

    }
  }
}

You can test it With the previous json in this way in a playground:

你可以在游乐场用这种方式测试它以前的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


2  

You can replace Any with an enum accepting an Int or a String:

您可以使用接受Int或String的枚举替换Any:

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

struct Person: Codable
{
    var id: Id
}

Then the compiler will complain about the fact that Id does not conform to Decodable. Because Id has associated values you need to implement this yourself. Read https://littlebitesofcocoa.com/318-codable-enums for an example of how to do this.

然后编译器会抱怨Id不符合Decodable的事实。由于Id具有关联值,因此您需要自己实现。请阅读https://littlebitesofcocoa.com/318-codable-enums,了解如何执行此操作的示例。

#5


2  

If your problem is that it's uncertain the type of id as it might be either a string or an integer value, I can suggest you this blog post: http://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/

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

Basically I defined a new Decodable type

基本上我定义了一个新的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)"))
        }

    }
}

From now on, your Person object would be

从现在开始,您的Person对象将是

struct Person: Decodable {
    var id: UncertainValue<Int, String>
}

you will be able to access your id using id.value

您将能够使用id.value访问您的ID

#6


1  

First of all, as you can read in other answers and comments, using Any for this is not good design. If possible, give it a second thought.

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

That said, if you want to stick to it for your own reasons, you should write your own encoding/decoding and adopt some kind of convention in the serialized JSON.

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

The code below implements it by encoding id always as string and decoding to Int or String depending on the found value.

下面的代码通过将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)>" }
}

Examples

Encode object with numeric id:

用数字id编码对象:

var p1 = Person(id: 1)
print(String(data: try JSONEncoder().encode(p1), 
      encoding: String.Encoding.utf8) ?? "/* ERROR */")
// {"id":"1"}

Encode object with string id:

用字符串id编码对象:

var p2 = Person(id: "root")
print(String(data: try JSONEncoder().encode(p2), 
      encoding: String.Encoding.utf8) ?? "/* ERROR */")
// {"id":"root"}

Decode to numeric id:

解码为数字ID:

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

Decode to string id:

解码为字符串ID:

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

An alternative implementation would be encoding to Int or String and wrap the decoding attempts in a do...catch.

另一种实现方式是对Int或String进行编码,并将解码尝试包装在do ... catch中。

In the encoding part:

在编码部分:

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

And then decode to the right type in multiple attempts:

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

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 */
    }
}

It's uglier in my opinion.

在我看来这很丑陋。

Depending on the control you have over the server serialization you can use either of them or write something else adapted to the actual serialization.

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

#7


0  

There is a corner case which is not covered by Luca Angeletti's solution.

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

For instance, if Cordinate's type is Double or [Double], Angeletti's solution will cause an error: "Expected to decode Double but found an array instead"

例如,如果Cordinate的类型是Double或[Double],Angeletti的解决方案将导致错误:“预计解码Double但发现了一个数组”

In this case, you have to use nested enum instead in Cordinate.

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

Simply you can use AnyCodable type from Matt Thompson's cool library AnyCodable.

您可以使用Matt Thompson的酷库AnyCodable中的AnyCodable类型。

Eg:

例如:

import AnyCodable

struct Person: Codable
{
    var id: AnyCodable
}

#1


13  

Codable needs to know the type to cast to.

Codable需要知道要转换的类型。

Firstly I would try to address the issue of not knowing the type, see if you can fix that and make it simpler.

首先,我会尝试解决不知道类型的问题,看看你是否可以修复它并使其更简单。

Otherwise the only way I can think of solving your issue currently is to use generics like below.

否则,我现在想到解决问题的唯一方法就是使用如下的泛型。

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


10  

Quantum Value

First of all you can define a type that can be decoded both from a String and Int value. Here it is.

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

Person

Now you can define your struct like this

现在您可以像这样定义结构

struct Person: Decodable {
    let id: QuantumValue
}

That's it. Let's test it!

而已。我们来试试吧!

JSON 1: id is 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 is Int

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

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

#3


6  

I solved this issue defining a new Decodable Struct called AnyDecodable, so instead of Any I use AnyDecodable. It works perfectly also with nested types.

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

Try this in a playground:

在游乐场试试这个:

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)

You could extend my struct to be AnyCodable if you are interested also in the Encoding part.

如果您对编码部分感兴趣,可以将我的结构扩展为AnyCodable。

Edit: I actually did it.

编辑:我实际上做到了。

Here is 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"))
      }

    }
  }
}

You can test it With the previous json in this way in a playground:

你可以在游乐场用这种方式测试它以前的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


2  

You can replace Any with an enum accepting an Int or a String:

您可以使用接受Int或String的枚举替换Any:

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

struct Person: Codable
{
    var id: Id
}

Then the compiler will complain about the fact that Id does not conform to Decodable. Because Id has associated values you need to implement this yourself. Read https://littlebitesofcocoa.com/318-codable-enums for an example of how to do this.

然后编译器会抱怨Id不符合Decodable的事实。由于Id具有关联值,因此您需要自己实现。请阅读https://littlebitesofcocoa.com/318-codable-enums,了解如何执行此操作的示例。

#5


2  

If your problem is that it's uncertain the type of id as it might be either a string or an integer value, I can suggest you this blog post: http://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/

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

Basically I defined a new Decodable type

基本上我定义了一个新的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)"))
        }

    }
}

From now on, your Person object would be

从现在开始,您的Person对象将是

struct Person: Decodable {
    var id: UncertainValue<Int, String>
}

you will be able to access your id using id.value

您将能够使用id.value访问您的ID

#6


1  

First of all, as you can read in other answers and comments, using Any for this is not good design. If possible, give it a second thought.

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

That said, if you want to stick to it for your own reasons, you should write your own encoding/decoding and adopt some kind of convention in the serialized JSON.

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

The code below implements it by encoding id always as string and decoding to Int or String depending on the found value.

下面的代码通过将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)>" }
}

Examples

Encode object with numeric id:

用数字id编码对象:

var p1 = Person(id: 1)
print(String(data: try JSONEncoder().encode(p1), 
      encoding: String.Encoding.utf8) ?? "/* ERROR */")
// {"id":"1"}

Encode object with string id:

用字符串id编码对象:

var p2 = Person(id: "root")
print(String(data: try JSONEncoder().encode(p2), 
      encoding: String.Encoding.utf8) ?? "/* ERROR */")
// {"id":"root"}

Decode to numeric id:

解码为数字ID:

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

Decode to string id:

解码为字符串ID:

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

An alternative implementation would be encoding to Int or String and wrap the decoding attempts in a do...catch.

另一种实现方式是对Int或String进行编码,并将解码尝试包装在do ... catch中。

In the encoding part:

在编码部分:

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

And then decode to the right type in multiple attempts:

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

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 */
    }
}

It's uglier in my opinion.

在我看来这很丑陋。

Depending on the control you have over the server serialization you can use either of them or write something else adapted to the actual serialization.

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

#7


0  

There is a corner case which is not covered by Luca Angeletti's solution.

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

For instance, if Cordinate's type is Double or [Double], Angeletti's solution will cause an error: "Expected to decode Double but found an array instead"

例如,如果Cordinate的类型是Double或[Double],Angeletti的解决方案将导致错误:“预计解码Double但发现了一个数组”

In this case, you have to use nested enum instead in Cordinate.

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

Simply you can use AnyCodable type from Matt Thompson's cool library AnyCodable.

您可以使用Matt Thompson的酷库AnyCodable中的AnyCodable类型。

Eg:

例如:

import AnyCodable

struct Person: Codable
{
    var id: AnyCodable
}