我正在为具有可能的关联值的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/

10-11 23:55
查看更多