Swift/JSONEncoder:包含嵌套原始 JSON 对象文字的编码类
Posted
技术标签:
【中文标题】Swift/JSONEncoder:包含嵌套原始 JSON 对象文字的编码类【英文标题】:Swift/JSONEncoder: Encoding class containing a nested raw JSON object literal 【发布时间】:2021-01-26 13:06:08 【问题描述】:我在 Swift 中有一个类,其结构类似于:
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
)
【问题讨论】:
你知道data
的结构吗?
不,这就是为什么我不努力对其进行解码/编码的原因。
(几乎)一切皆有可能,但模型越不标准,您必须编写的代码就越多。当你要编码这些东西使用更合适的设计。
【参考方案1】:
您首先需要将 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)
【讨论】:
这是一个非常有用的实用程序,它很好地解决了这个问题。我很惊讶JSONEncoder
中没有包含任何东西。也许我被 Newtonsoft.Json 宠坏了。不过,我必须修复 Int/Double 歧义。
我今天在这方面做了更多的工作(参见 github);您需要修复什么 Int/Double 歧义?我在那里做了一些工作。我现在在内部将值存储为 NSNumber,这很有帮助。
我刚刚通过您的 RNJSON 链接找到了代码。我最近回答了一个类似的问题here,我认为某些代码肯定可以简化。但是,与我不同的是,您正在解决我忽略的 null
案例。【参考方案2】:
你可以这样使用:
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)
【讨论】:
以上是关于Swift/JSONEncoder:包含嵌套原始 JSON 对象文字的编码类的主要内容,如果未能解决你的问题,请参考以下文章