enum PostType: Decodable {
init(from decoder: Decoder) throws {
// What do i put here?
}
case Image
enum CodingKeys: String, CodingKey {
case image
}
}
我要完成这项工作?
另外,假设我将case
更改为:
case image(value: Int)
如何使其符合Decodable?
编辑这是我的完整代码(不起作用)
let jsonData = """
{
"count": 4
}
""".data(using: .utf8)!
do {
let decoder = JSONDecoder()
let response = try decoder.decode(PostType.self, from: jsonData)
print(response)
} catch {
print(error)
}
}
}
enum PostType: Int, Codable {
case count = 4
}
最终修改 另外,它将如何处理这样的枚举?
enum PostType: Decodable {
case count(number: Int)
}
答案 0 :(得分:162)
非常简单,只需使用隐式分配的String
或Int
原始值。
enum PostType: Int, Codable {
case image, blob
}
image
编码为0
,blob
编码为1
或
enum PostType: String, Codable {
case image, blob
}
image
编码为"image"
,blob
编码为"blob"
这是如何使用它的简单示例:
enum PostType : Int, Codable {
case count = 4
}
struct Post : Codable {
var type : PostType
}
let jsonString = "{\"type\": 4}"
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(Post.self, from: jsonData)
print("decoded:", decoded.type)
} catch {
print(error)
}
答案 1 :(得分:66)
Codable
此答案与@Howard Lovatt类似,但避免创建PostTypeCodableForm
结构,而是使用KeyedEncodingContainer
类型provided by Apple作为Encoder
和Decoder
的属性},这减少了样板。
enum PostType: Codable {
case count(number: Int)
case title(String)
}
extension PostType {
private enum CodingKeys: String, CodingKey {
case count
case title
}
enum PostTypeCodingError: Error {
case decoding(String)
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? values.decode(Int.self, forKey: .count) {
self = .count(number: value)
return
}
if let value = try? values.decode(String.self, forKey: .title) {
self = .title(value)
return
}
throw PostTypeCodingError.decoding("Whoops! \(dump(values))")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .count(let number):
try container.encode(number, forKey: .count)
case .title(let value):
try container.encode(value, forKey: .title)
}
}
}
此代码适用于Xcode 9b3。
import Foundation // Needed for JSONEncoder/JSONDecoder
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()
let count = PostType.count(number: 42)
let countData = try encoder.encode(count)
let countJSON = String.init(data: countData, encoding: .utf8)!
print(countJSON)
// {
// "count" : 42
// }
let decodedCount = try decoder.decode(PostType.self, from: countData)
let title = PostType.title("Hello, World!")
let titleData = try encoder.encode(title)
let titleJSON = String.init(data: titleData, encoding: .utf8)!
print(titleJSON)
// {
// "title": "Hello, World!"
// }
let decodedTitle = try decoder.decode(PostType.self, from: titleData)
答案 2 :(得分:20)
如果遇到未知的枚举值,Swift会抛出.dataCorrupted
错误。如果您的数据来自服务器,它可以随时向您发送未知的枚举值(错误服务器端,API版本中添加的新类型,并且您希望应用程序的先前版本能够优雅地处理案例等),你最好做好准备,编码和防守风格"安全解码您的枚举。
以下是有关如何操作的示例,包括或不包含相关值
enum MediaType: Decodable {
case audio
case multipleChoice
case other
// case other(String) -> we could also parametrise the enum like that
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
switch label {
case "AUDIO": self = .audio
case "MULTIPLE_CHOICES": self = .multipleChoice
default: self = .other
// default: self = .other(label)
}
}
}
如何在封闭结构中使用它:
struct Question {
[...]
let type: MediaType
enum CodingKeys: String, CodingKey {
[...]
case type = "type"
}
extension Question: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
[...]
type = try container.decode(MediaType.self, forKey: .type)
}
}
答案 3 :(得分:10)
要扩展@Tacka的答案,你也可以在枚举中添加一个原始的可表示值,并使用默认的可选构造函数来构建没有switch
的枚举:
enum MediaType: String, Decodable {
case audio = "AUDIO"
case multipleChoice = "MULTIPLE_CHOICES"
case other
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
self = MediaType(rawValue: label) ?? .other
}
}
可以使用允许重构构造函数的自定义协议进行扩展:
protocol EnumDecodable: RawRepresentable, Decodable {
static var defaultDecoderValue: Self { get }
}
extension EnumDecodable where RawValue: Decodable {
init(from decoder: Decoder) throws {
let value = try decoder.singleValueContainer().decode(RawValue.self)
self = Self(rawValue: value) ?? Self.defaultDecoderValue
}
}
enum MediaType: String, EnumDecodable {
static let defaultDecoderValue: MediaType = .other
case audio = "AUDIO"
case multipleChoices = "MULTIPLE_CHOICES"
case other
}
如果指定了无效的枚举值,也可以轻松扩展以抛出错误,而不是默认值。有关此更改的要点可在此处获取:https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128 使用Swift 4.1 / Xcode 9.3编译和测试代码。
答案 4 :(得分:5)
@ proxpero的一个变体是terser,它将把解码器表示为:
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
guard let key = values.allKeys.first else { throw err("No valid keys in: \(values)") }
func dec<T: Decodable>() throws -> T { return try values.decode(T.self, forKey: key) }
switch key {
case .count: self = try .count(dec())
case .title: self = try .title(dec())
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .count(let x): try container.encode(x, forKey: .count)
case .title(let x): try container.encode(x, forKey: .title)
}
}
这允许编译器详尽地验证这些情况,并且在编码值与密钥的预期值不匹配的情况下也不会抑制错误消息。
答案 5 :(得分:3)
你可以做你想做的事,但有点涉及:(
@click="editClicked({{$sale}})"
答案 6 :(得分:3)
实际上上面的答案真的很棒,但是他们在一个持续开发的客户端/服务器项目中缺少许多人需要的一些细节。我们开发应用程序,而我们的后端随着时间的推移不断发展,这意味着一些枚举案例将改变这种演变。因此,我们需要一种能够解码包含未知案例的枚举数组的枚举解码策略。否则解码包含该数组的对象就会失败。
我所做的很简单:
enum Direction: String, Decodable {
case north, south, east, west
}
struct DirectionList {
let directions: [Direction]
}
extension DirectionList: Decodable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var directions: [Direction] = []
while !container.isAtEnd {
// Here we just decode the string from the JSON which always works as long as the array element is a string
let rawValue = try container.decode(String.self)
guard let direction = Direction(rawValue: rawValue) else {
// Unknown enum value found - ignore, print error to console or log error to analytics service so you'll always know that there are apps out which cannot decode enum cases!
continue
}
// Add all known enum cases to the list of directions
directions.append(direction)
}
self.directions = directions
}
}
加分:隐藏实施&gt;使其成为集合
隐藏实现细节总是一个好主意。为此,您只需要更多代码。诀窍是使DirectionsList
符合Collection
并使您的内部list
数组保密:
struct DirectionList {
typealias ArrayType = [Direction]
private let directions: ArrayType
}
extension DirectionList: Collection {
typealias Index = ArrayType.Index
typealias Element = ArrayType.Element
// The upper and lower bounds of the collection, used in iterations
var startIndex: Index { return directions.startIndex }
var endIndex: Index { return directions.endIndex }
// Required subscript, based on a dictionary index
subscript(index: Index) -> Element {
get { return directions[index] }
}
// Method that returns the next index when iterating
func index(after i: Index) -> Index {
return directions.index(after: i)
}
}
您可以在John Sundell撰写的这篇博客文章中阅读有关符合自定义集合的更多信息:https://medium.com/@johnsundell/creating-custom-collections-in-swift-a344e25d0bb0
答案 7 :(得分:0)
import Foundation
enum DecodableEnum<Enum: RawRepresentable> where Enum.RawValue == String {
case value(Enum)
case error(DecodingError)
var value: Enum? {
switch self {
case .value(let value): return value
case .error: return nil
}
}
var error: DecodingError? {
switch self {
case .value: return nil
case .error(let error): return error
}
}
enum DecodingError: Error {
case notDefined(rawValue: String)
case decoding(error: Error)
}
}
extension DecodableEnum: Decodable {
init(from decoder: Decoder) throws {
do {
let rawValue = try decoder.singleValueContainer().decode(String.self)
guard let layout = Enum(rawValue: rawValue) else {
self = .error(.notDefined(rawValue: rawValue))
return
}
self = .value(layout)
} catch let err {
self = .error(.decoding(error: err))
}
}
}
enum SimpleEnum: String, Codable {
case a, b, c, d
}
struct Model: Decodable {
let num: Int
let str: String
let enum1: DecodableEnum<SimpleEnum>
let enum2: DecodableEnum<SimpleEnum>
let enum3: DecodableEnum<SimpleEnum>
let enum4: DecodableEnum<SimpleEnum>?
}
let dictionary: [String : Any] = ["num": 1, "str": "blablabla", "enum1": "b", "enum2": "_", "enum3": 1]
let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print("1. \(object.enum1.value)")
print("2. \(object.enum2.error)")
print("3. \(object.enum3.error)")
print("4. \(object.enum4)")
答案 8 :(得分:0)
这里有很多好的方法,但我还没有看到讨论具有多个值的枚举,尽管可以从示例中推断出来 - 也许有人可以找到这个方法的用途:
import Foundation
enum Tup {
case frist(String, next: Int)
case second(Int, former: String)
enum TupType: String, Codable {
case first
case second
}
enum CodingKeys: String, CodingKey {
case type
case first
case firstNext
case second
case secondFormer
}
}
extension Tup: Codable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let type = try values.decode(TupType.self, forKey: .type)
switch type {
case .first:
let str = try values.decode(String.self, forKey: .first)
let next = try values.decode(Int.self, forKey: .firstNext)
self = .frist(str, next: next)
case .second:
let int = try values.decode(Int.self, forKey: .second)
let former = try values.decode(String.self, forKey: .secondFormer)
self = .second(int, former: former)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .frist(let str, next: let next):
try container.encode(TupType.first, forKey: .type)
try container.encode(str, forKey: .first)
try container.encode(next, forKey: .firstNext)
case .second(let int, former: let former):
try container.encode(TupType.second, forKey: .type)
try container.encode(int, forKey: .second)
try container.encode(former, forKey: .secondFormer)
}
}
}
let example1 = Tup.frist("123", next: 90)
do {
let encoded = try JSONEncoder().encode(example1)
print(encoded)
let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
print("decoded 1 = \(decoded)")
}
catch {
print("errpr = \(error.localizedDescription)")
}
let example2 = Tup.second(10, former: "dantheman")
do {
let encoded = try JSONEncoder().encode(example2)
print(encoded)
let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
print("decoded 2 = \(decoded)")
}
catch {
print("errpr = \(error.localizedDescription)")
}