我正在Codable为enum具有可能关联值的类型的实现工作.由于这些对于每种情况都是独一无二的,我认为在编码过程中我可以在没有键的情况下输出它们,然后简单地看一下我在解码时可以得到什么来恢复正确的情况.
这是一个非常简洁,人为的例子,演示了一种动态类型的值:
enum MyValueError : Error { case invalidEncoding }
enum MyValue {
case bool(Bool)
case float(Float)
case integer(Int)
case string(String)
}
extension MyValue : Codable {
init(from theDecoder:Decoder) throws {
let theEncodedValue = try theDecoder.singleValueContainer()
if let theValue = try? theEncodedValue.decode(Bool.self) {
self = .bool(theValue)
} else if let theValue = try? theEncodedValue.decode(Float.self) {
self = .float(theValue)
} else if let theValue = try? theEncodedValue.decode(Int.self) {
self = .integer(theValue)
} else if let theValue = try? theEncodedValue.decode(String.self) {
self = .string(theValue)
} else { throw MyValueError.invalidEncoding }
}
func encode(to theEncoder:Encoder) throws {
var theEncodedValue = theEncoder.singleValueContainer()
switch self {
case .bool(let theValue):
try theEncodedValue.encode(theValue)
case .float(let theValue):
try theEncodedValue.encode(theValue)
case .integer(let theValue):
try theEncodedValue.encode(theValue)
case .string(let theValue):
try theEncodedValue.encode(theValue)
}
}
}
let theEncodedValue = try! JSONEncoder().encode(MyValue.integer(123456))
let theEncodedString = String(data: theEncodedValue, encoding: .utf8)
let theDecodedValue = try! JSONDecoder().decode(MyValue.self, from: theEncodedValue)
Run Code Online (Sandbox Code Playgroud)
但是,这在编码阶段给出了一个错误,如下所示:
"Top-level MyValue encoded as number JSON fragment."
Run Code Online (Sandbox Code Playgroud)
问题似乎是,无论出于何种原因,JSONEncoder都不允许将不是可识别原语的顶级类型编码为单个原始值.如果我更改singleValueContainer()为a unkeyedContainer()然后它工作得很好,除了当然结果JSON是一个数组,而不是一个值,或者我可以使用一个键控容器,但这会产生一个带有额外开销的对象.
我想用一个单值容器在这里做什么不可能?如果没有,是否有一些我可以使用的解决方法?
我的目标是以Codable最小的开销使我的类型,而不仅仅是JSON(解决方案应该支持任何有效的Encoder/ Decoder).
有一个错误报告:
https://bugs.swift.org/browse/SR-6163
SR-6163:JSONDecoder无法解码RFC 7159 JSON
基本上,自RFC-7159以来,类似的值123是有效的JSON,但JSONDecoder不支持它.您可以跟进错误报告,以查看未来的任何修复.
它在以下代码行中失败,您可以看到如果对象不是数组或字典,它将失败:
open class JSONSerialization : NSObject {
//...
// top level object must be an Swift.Array or Swift.Dictionary
guard obj is [Any?] || obj is [String: Any?] else {
return false
}
//...
}
Run Code Online (Sandbox Code Playgroud)
您可以使用JSONSerialization以下选项.allowFragments:
let jsonText = "123"
let data = Data(jsonText.utf8)
do {
let myString = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(myString)
}
catch {
print(error)
}
Run Code Online (Sandbox Code Playgroud)
最后,您还可以将JSON对象看起来像这样:
{ "integer": 123456 }
要么
{ "string": "potatoe" }
为此,您需要执行以下操作:
import Foundation
enum MyValue {
case integer(Int)
case string(String)
}
extension MyValue: Codable {
enum CodingError: Error {
case decoding(String)
}
enum CodableKeys: String, CodingKey {
case integer
case string
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodableKeys.self)
if let integer = try? values.decode(Int.self, forKey: .integer) {
self = .integer(integer)
return
}
if let string = try? values.decode(String.self, forKey: .string) {
self = .string(string)
return
}
throw CodingError.decoding("Decoding Failed")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodableKeys.self)
switch self {
case let .integer(i):
try container.encode(i, forKey: .integer)
case let .string(s):
try container.encode(s, forKey: .string)
}
}
}
let theEncodedValue = try! JSONEncoder().encode(MyValue.integer(123456))
let theEncodedString = String(data: theEncodedValue, encoding: .utf8)
print(theEncodedString!) // { "integer": 123456 }
let theDecodedValue = try! JSONDecoder().decode(MyValue.self, from: theEncodedValue)
Run Code Online (Sandbox Code Playgroud)