Swift/JSONEncoder:编码 class 包含嵌套的原始 JSON 对象文字
Swift/JSONEncoder: Encoding class containing a nested raw JSON object literal
我在 Swift 中有一个 class,其结构类似于:
class MyClass {
var name: String
var data: String
}
可以在 data
包含编码为字符串的 JSON 对象的地方进行初始化。
var instance = MyClass()
instance.name = "foo"
instance.data = "{\"bar\": \"baz\"}"
我现在想使用 JSONEncoder
序列化这个实例,我会得到类似于这样的输出:
{
"name": "foo",
"data": "{\"bar\": \"baz\"}"
}
不过,我真的很喜欢
{
"name": "foo",
"data": {
"bar": "baz"
}
}
我可以用 JSONEncoder 实现吗? (不改变 data
类型远离 String
)
你可以使用这样的东西:
extension MyClass {
func jsonFormatted() throws -> String? {
guard let data = data.data(using: .utf8) else {
return nil
}
let anyData = try JSONSerialization.jsonObject(with: data, options: [])
let dictionary = ["name": name, "data": anyData] as [String : Any]
let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted)
let jsonString = String(data: jsonData, encoding: .utf8)
return jsonString
}
}
所以基本上,您保留 data
的结构不变,但其余部分包含在一个字典中,可以转换为您想要实现的 json 字符串。
请注意,您需要处理可能抛出的可选和错误。
你可以用这个来测试:
if let jsonString = try? instance.jsonFormatted() {
print(jsonString)
}
您首先需要将 data
解码为通用 JSON。这有点乏味,但并不太难。请参阅 RNJSON 了解我编写的版本,或者这里有一个可以处理您的问题的精简版本。
enum JSON: Codable {
struct Key: CodingKey, Hashable {
let stringValue: String
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.init(stringValue) }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
case string(String)
case number(Double) // FIXME: Split Int and Double
case object([Key: JSON])
case array([JSON])
case bool(Bool)
case null
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) { self = .string(string) }
else if let number = try? decoder.singleValueContainer().decode(Double.self) { self = .number(number) }
else if let object = try? decoder.container(keyedBy: Key.self) {
var result: [Key: JSON] = [:]
for key in object.allKeys {
result[key] = (try? object.decode(JSON.self, forKey: key)) ?? .null
}
self = .object(result)
}
else if var array = try? decoder.unkeyedContainer() {
var result: [JSON] = []
for _ in 0..<(array.count ?? 0) {
result.append(try array.decode(JSON.self))
}
self = .array(result)
}
else if let bool = try? decoder.singleValueContainer().decode(Bool.self) { self = .bool(bool) }
else if let isNull = try? decoder.singleValueContainer().decodeNil(), isNull { self = .null }
else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [],
debugDescription: "Unknown JSON type")) }
}
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 .bool(let bool):
var container = encoder.singleValueContainer()
try container.encode(bool)
case .object(let object):
var container = encoder.container(keyedBy: Key.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 .null:
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
}
有了它,您可以解码 JSON 然后重新编码它:
extension MyClass: Encodable {
enum CodingKeys: CodingKey {
case name, data
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
let json = try JSONDecoder().decode(JSON.self, from: Data(data.utf8))
try container.encode(json, forKey: .data)
}
}
我在 Swift 中有一个 class,其结构类似于:
class MyClass {
var name: String
var data: String
}
可以在 data
包含编码为字符串的 JSON 对象的地方进行初始化。
var instance = MyClass()
instance.name = "foo"
instance.data = "{\"bar\": \"baz\"}"
我现在想使用 JSONEncoder
序列化这个实例,我会得到类似于这样的输出:
{
"name": "foo",
"data": "{\"bar\": \"baz\"}"
}
不过,我真的很喜欢
{
"name": "foo",
"data": {
"bar": "baz"
}
}
我可以用 JSONEncoder 实现吗? (不改变 data
类型远离 String
)
你可以使用这样的东西:
extension MyClass {
func jsonFormatted() throws -> String? {
guard let data = data.data(using: .utf8) else {
return nil
}
let anyData = try JSONSerialization.jsonObject(with: data, options: [])
let dictionary = ["name": name, "data": anyData] as [String : Any]
let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted)
let jsonString = String(data: jsonData, encoding: .utf8)
return jsonString
}
}
所以基本上,您保留 data
的结构不变,但其余部分包含在一个字典中,可以转换为您想要实现的 json 字符串。
请注意,您需要处理可能抛出的可选和错误。
你可以用这个来测试:
if let jsonString = try? instance.jsonFormatted() {
print(jsonString)
}
您首先需要将 data
解码为通用 JSON。这有点乏味,但并不太难。请参阅 RNJSON 了解我编写的版本,或者这里有一个可以处理您的问题的精简版本。
enum JSON: Codable {
struct Key: CodingKey, Hashable {
let stringValue: String
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.init(stringValue) }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
case string(String)
case number(Double) // FIXME: Split Int and Double
case object([Key: JSON])
case array([JSON])
case bool(Bool)
case null
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) { self = .string(string) }
else if let number = try? decoder.singleValueContainer().decode(Double.self) { self = .number(number) }
else if let object = try? decoder.container(keyedBy: Key.self) {
var result: [Key: JSON] = [:]
for key in object.allKeys {
result[key] = (try? object.decode(JSON.self, forKey: key)) ?? .null
}
self = .object(result)
}
else if var array = try? decoder.unkeyedContainer() {
var result: [JSON] = []
for _ in 0..<(array.count ?? 0) {
result.append(try array.decode(JSON.self))
}
self = .array(result)
}
else if let bool = try? decoder.singleValueContainer().decode(Bool.self) { self = .bool(bool) }
else if let isNull = try? decoder.singleValueContainer().decodeNil(), isNull { self = .null }
else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [],
debugDescription: "Unknown JSON type")) }
}
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 .bool(let bool):
var container = encoder.singleValueContainer()
try container.encode(bool)
case .object(let object):
var container = encoder.container(keyedBy: Key.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 .null:
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
}
有了它,您可以解码 JSON 然后重新编码它:
extension MyClass: Encodable {
enum CodingKeys: CodingKey {
case name, data
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
let json = try JSONDecoder().decode(JSON.self, from: Data(data.utf8))
try container.encode(json, forKey: .data)
}
}