我正在为具有可能的关联值的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)
但是,这在编码阶段给了我一个错误,如下所示:
"Top-level MyValue encoded as number JSON fragment."
问题似乎是,无论出于何种原因,
JSONEncoder
都不允许将不能识别的基元的顶级类型编码为单个基元值。如果我将singleValueContainer()
更改为unkeyedContainer()
,那么它工作得很好,除了生成的JSON
当然是一个数组,而不是单个值,或者我可以使用带键控的容器,但这会产生一个带有键开销的对象。使用单个值容器,我在这里要做的是不可能的吗?如果没有,我可以使用一些替代方法吗?
我的目标是使我的类型
Codable
开销最小,而不仅仅是JSON
(解决方案应支持任何有效的Encoder
/Decoder
)。 最佳答案
对此有一个错误报告:
https://bugs.swift.org/browse/SR-6163
基本上,从RFC-7159开始,像123
这样的值就是有效的JSON,但JSONDecoder
将不支持它。您可能会继续关注错误报告,以查看有关此问题的任何将来修复程序。
失败的地方
它在下面的代码行中失败,您可以在其中看到如果该对象既不是数组也不是字典,它将失败:
https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/JSONSerialization.swift#L120
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
}
//...
}
解决方法
您可以使用
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)
}
编码为键值对
最后,您还可以使您的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)
关于swift - JSONEncoder不允许将类型编码为原始值,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50257242/