如何在 Swift 中表示通用 JSON 结构?

How to represent a generic JSON structure in Swift?

我想在 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 对其进行编码吗?

您可以为此使用泛型:

typealias JSON<T: Any> = [String: T] where T: Equatable

在我看来最合适的是SwiftyJSON。它很好 API 让开发人员确定如何解析和使用 JSON 对象。

JSON 对象有非常好的界面来处理不同类型的响应。

来自 Apple 文档:

Types that conform to the Equatable protocol can be compared for equality using the equal-to operator (==) or inequality using the not-equal-to operator (!=). Most basic types in the Swift standard library conform to Equatable.

假设您 ask.We 应该检查 JSON 是否符合协议 Equatable

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

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()
        }
    }
}

鉴于此,我相信您可以自己实施 DecodableEquatable


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

你可以试试这个BeyovaJSON

import BeyovaJSON

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

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

我 运行 进入了这个问题,但是我有太多类型想要反序列化,所以我想我必须从接受的答案中的 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

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