如何在 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()
}
}
}
鉴于此,我相信您可以自己实施 Decodable
和 Equatable
。
请注意,如果您尝试将数组或对象以外的任何内容编码为顶级元素,这将会崩溃。
你可以试试这个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
这不是有史以来最漂亮的代码,但适用于您想要编码的任何类型,无需任何额外代码。
我想在 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()
}
}
}
鉴于此,我相信您可以自己实施 Decodable
和 Equatable
。
请注意,如果您尝试将数组或对象以外的任何内容编码为顶级元素,这将会崩溃。
你可以试试这个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
这不是有史以来最漂亮的代码,但适用于您想要编码的任何类型,无需任何额外代码。