5

我想在 Swift 中表示一个通用的 JSON 对象:

let foo: [String: Any] = [
    "foo": 1,
    "bar": "baz",
]

但是[String: Any]编译器建议的类型并不能很好地工作。例如,我无法检查该类型的两个实例是否相等,而这应该可以使用两个 JSON 树。

也不起作用的是使用Codable机器将该值编码为 JSON 字符串:

let encoded = try JSONEncoder().encode(foo)

这会因错误而爆炸:

fatal error: Dictionary<String, Any> does not conform to Encodable because Any does not conform to Encodable.

我知道我可以引入一个精确的类型,但我追求的是一个通用的 JSON 结构。我什至尝试为通用 JSON 引入特定类型:

enum JSON {
    case string(String)
    case number(Float)
    case object([String:JSON])
    case array([JSON])
    case bool(Bool)
    case null
}

但是在Codable为这个枚举实现时,我不知道如何实现encode(to:),因为键控容器(用于编码对象)需要一个特定的CodingKey参数,而我不知道如何得到它。

真的不可能创建一个Equatable通用的 JSON 树并使用它进行编码Codable吗?

4

5 回答 5

5

我们将使用通用字符串作为编码键:

extension String: CodingKey {
    public init?(stringValue: String) {
        self = stringValue
    }

    public var stringValue: String {
        return self
    }

    public init?(intValue: Int) {
        return nil
    }

    public var intValue: Int? {
        return nil
    }
}

剩下的只是获取正确类型的容器并将您的值写入其中。

extension JSON: Encodable {
    public func encode(to encoder: Encoder) throws {
        switch self {
        case .string(let string):
            var container = encoder.singleValueContainer()
            try container.encode(string)
        case .number(let number):
            var container = encoder.singleValueContainer()
            try container.encode(number)
        case .object(let object):
            var container = encoder.container(keyedBy: String.self)

            for (key, value) in object {
                try container.encode(value, forKey: key)
            }
        case .array(let array):
            var container = encoder.unkeyedContainer()

            for value in array {
                try container.encode(value)
            }
        case .bool(let bool):
            var container = encoder.singleValueContainer()
            try container.encode(bool)
        case .null:
            var container = encoder.singleValueContainer()
            try container.encodeNil()
        }
    }
}

Decodable鉴于此,我相信您可以Equatable自己实施。


请注意,如果您尝试将数组或对象以外的任何内容编码为顶级元素,这将崩溃。

于 2017-08-24T19:58:27.000 回答
2

我遇到了这个问题,但我想反序列化的类型太多,所以我想我必须从接受的答案中枚举 JSON 枚举中的所有类型。所以我创建了一个简单的包装器,效果非常好:

struct Wrapper: Encodable {
    let value: Encodable
    func encode(to encoder: Encoder) throws {
        try value.encode(to: encoder)
    }
}

那么你可以写

let foo: [String: Wrapper] = [
    "foo": Wrapper(value: 1),
    "bar": Wrapper(value: "baz"),
]

let encoded = try JSONEncoder().encode(foo) // now this works

不是有史以来最漂亮的代码,但适用于您想要编码的任何类型,无需任何额外代码。

于 2018-01-30T02:22:14.837 回答
1

你可以试试这个BeyovaJSON

import BeyovaJSON

let foo: JToken = [
    "foo": 1,
    "bar": "baz",
]

let encoded = try JSONEncoder().encode(foo)
于 2017-10-28T16:06:42.590 回答
1

您可以为此使用泛型:

typealias JSON<T: Any> = [String: T] where T: Equatable
于 2017-05-08T09:03:24.487 回答
-1

从我的角度来看,最合适的是SwiftyJSON。它有很好的 API 供开发人员确定如何解析和使用JSON对象。

JSON对象有很好的接口来处理不同类型的响应。

来自苹果文档:

可以使用等于运算符 (==) 比较符合 Equatable 协议的类型是否相等,或者使用不等于运算符 (!=) 比较不等式。Swift 标准库中的大多数基本类型都符合 Equatable。

让我们考虑你问的情况。我们应该检查是否JSON符合协议Equatable

于 2017-05-08T09:18:21.307 回答