Swift:解码 JSON 响应并将嵌套的 JSON 存储为字符串或 JSON
Swift: Decode JSON response and store nested JSON as String or JSON
根据网络请求给出以下 JSON;如果您想将其解码为与 Codable
一致的 Swift 对象,但又想保留嵌套的 JSON,即键 configuration_payload
的值,怎么可能你做到了吗?
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
}
使用下面的 Swift struct
,我希望能够将 configuration_payload
作为 String
.
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: String?
}
据我所知,Swift 中的 JSONDecoder
将 configuration_payload
的值视为嵌套的 JSON 并希望将其解码为自己的对象.更让人困惑的是,configuration_payload
并不总是会 return 相同的 JSON 结构,它会有所不同,所以我无法创建 Swift struct
我可以期待并在需要时简单地 JSON 再次对其进行编码。我需要能够将该值存储为字符串,以说明 configuration_payload
键下 JSON 的变化。
这里是 configurationPayload
字典,所以你的 Registration
结构如下所示
struct Registration : Codable {
let configurationPayload : ConfigurationPayload?
let deviceType : String?
let discoveryTimeout : Int?
let id : String?
let installationTimeout : Int?
let state : String?
let thingUuid : Int?
enum CodingKeys: String, CodingKey {
case configurationPayload = "configuration_payload"
case deviceType = "device_type"
case discoveryTimeout = "discovery_timeout"
case id = "id"
case installationTimeout = "installation_timeout"
case state = "state"
case thingUuid = "thing_uuid"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
configurationPayload = ConfigurationPayload(from: decoder)
deviceType = try values.decodeIfPresent(String.self, forKey: .deviceType)
discoveryTimeout = try values.decodeIfPresent(Int.self, forKey: .discoveryTimeout)
id = try values.decodeIfPresent(String.self, forKey: .id)
installationTimeout = try values.decodeIfPresent(Int.self, forKey: .installationTimeout)
state = try values.decodeIfPresent(String.self, forKey: .state)
thingUuid = try values.decodeIfPresent(Int.self, forKey: .thingUuid)
}
}
你的ConfigurationPayload
看起来像这样
struct ConfigurationPayload : Codable {
let category : String?
let title : String?
let url : String?
let views : Int?
enum CodingKeys: String, CodingKey {
case category = "category"
case title = "title"
case url = "url"
case views = "views"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
category = try values.decodeIfPresent(String.self, forKey: .category)
title = try values.decodeIfPresent(String.self, forKey: .title)
url = try values.decodeIfPresent(String.self, forKey: .url)
views = try values.decodeIfPresent(Int.self, forKey: .views)
}
}
一种(比您可能想要的更有限)方法是确保 configuration_payload
JSON 中的 Value
部分是已知的 Codable
单一类型(String
) 而不是 Any
可以产生多种类型(String
、Int
、Double
等)。
我试图让它与 [String: Any]
一起用于 configuration_payload
,问题是 Any
不符合 Codable
。
然后我尝试使用 [String: String]
实现 configuration_payload
并且能够像下面那样工作。
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: [String: String]? // NOT [String: Any]?
enum CodingKeys: String, CodingKey {
case id = "id"
case deviceType = "device_type"
case state = "state"
case thingUUID = "thing_uuid"
case discoveryTimeout = "discovery_timeout"
case installationTimeout = "installation_timeout"
case configurationPayload = "configuration_payload"
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id) ?? ""
deviceType = try values.decodeIfPresent(String.self, forKey: .deviceType) ?? ""
let stateRaw = try values.decodeIfPresent(String.self, forKey: .state) ?? ""
state = Registration.State(rawValue: stateRaw) ?? .provisioning
thingUUID = try values.decodeIfPresent(Int.self, forKey: .thingUUID)
discoveryTimeout = try values.decodeIfPresent(Int.self, forKey: .discoveryTimeout) ?? 0
installationTimeout = try values.decodeIfPresent(Int.self, forKey: .installationTimeout) ?? 0
configurationPayload = try values.decodeIfPresent([String: String].self, forKey: .configurationPayload)
}
}
测试
let json = Data("""
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload": {
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": "9999"
}
}
""".utf8
)
let decoded = try JSONDecoder().decode(Registration.self, from: json)
print(decoded)
let encoded = try JSONEncoder().encode(decoded)
print(String(data: encoded, encoding: .utf8))
您可以使用 AnyCodable.
等第三方库将 JSON 对象解码为 [String: Any]
您的 Registration
结构将如下所示:
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: [String: AnyCodable]?
}
然后你可以将 [String: AnyCodable]
类型转换为 [String: Any]
甚至 String
:
let jsonString = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let registration = try decoder.decode(Registration.self, from: Data(jsonString.utf8))
// to [String: Any]
let dictionary = registration.configurationPayload?.mapValues { [=11=].value }
// to String
if let configurationPayload = registration.configurationPayload {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let data = try encoder.encode(configurationPayload)
let string = String(decoding: data, as: UTF8.self)
print(string)
}
} catch {
print(error)
}
这对于 Codable 协议是不可能的,因为你事先不知道类型。您必须编写自己的方法或采用不同的解码策略。
let json = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload": {
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
""".data(using: .utf8)
do {
let decoded = try? Registration.init(jsonData: json!)
print(decoded)
}catch {
print(error)
}
public struct Registration {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id: String
public let device_type: String
public let state: State
public let error: String?
public let thing_uuid: Int?
public let discovery_timeout, installation_timeout: Int
public let configuration_payload: [String: Any]?
public init(jsonData: Data) throws {
let package = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String : Any]
id = package["id"] as! String
device_type = package["device_type"] as! String
state = State(rawValue: package["state"] as! String)!
error = package["error"] as? String
thing_uuid = package["thing_uuid"] as? Int
discovery_timeout = package["discovery_timeout"] as! Int
installation_timeout = package["installation_timeout"] as! Int
configuration_payload = package["configuration_payload"] as? [String: Any]
}
}
这是处理不同类型的一种可能方法。您还可以创建一个包含键的结构并循环遍历它们,我认为这说明了基本思想。
编辑:
if let remaining = package["configuration_payload"] as? Data,
let data = try? JSONSerialization.data(withJSONObject: remaining, options: []) as Data,
let string = String(data: data, encoding: .utf8) {
// store your string if you want it in string formatt
print(string)
}
如果你有一个可能的键列表,使用可选项是你可以使用 Codable 的另一种方式。您可以通过这种方式混合密钥 - 只有可用的密钥才会尝试成为 encoded/decoded
import UIKit
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUuid: Int?
public let discoveryTimeout, installationTimeout: Int
public var configurationPayload: ConfigurationPayload?
}
// nested json can be represented as a codable struct
public struct ConfigurationPayload: Codable {
let title: String?
let url: String?
let category: String?
let views: Int?
let nonTitle: String?
let anotherUrl: String?
let someCategory: String?
let someViews: Int?
// computed properties aren't part of the coding strategy
// TODO: avoid duplication in loop
var jsonString: String {
let mirror = Mirror(reflecting: self).children
let parameters = mirror.compactMap({[=10=].label})
let values = mirror.map({[=10=].value})
let keyValueDict = zip(parameters, values)
var returnString: String = "{\n"
for (key, value) in keyValueDict {
if let value = value as? Int {
returnString.append("\"\(key)\": \"\(value)\n")
} else if let value = value as? String {
returnString.append("\"\(key)\": \"\(value)\n")
}
}
returnString.append("}")
return returnString
}
}
// your json has a preceding key of "registration", this is the type you will decode
public struct RegistrationParent: Codable {
var registration: Registration
}
let jsonDataA =
"""
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
}
""".data(using: .utf8)!
let jsonDataB =
"""
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"non_title": "Some Title",
"another_url": "https://www.someurl.com/",
"some_category": "test",
"some_views": 9999
}
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
var registrationA = try decoder.decode(RegistrationParent.self, from: jsonDataA)
print(registrationA.registration.configurationPayload?.jsonString ?? "{}")
var registrationB = try decoder.decode(RegistrationParent.self, from: jsonDataB)
print(registrationB.registration.configurationPayload?.jsonString ?? "{}")
} catch {
print(error)
}
正如其他人所说,你不能只保留一部分而不解码。然而,解码未知数据是微不足道的:
enum RawJsonValue {
case boolean(Bool)
case number(Double)
case string(String)
case array([RawJsonValue?])
case object([String: RawJsonValue])
}
extension RawJsonValue: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let boolValue = try? container.decode(Bool.self) {
self = .boolean(boolValue)
} else if let numberValue = try? container.decode(Double.self) {
self = .number(numberValue)
} else if let stringValue = try? container.decode(String.self) {
self = .string(stringValue)
} else if let arrayValue = try? container.decode([RawJsonValue?].self) {
self = .array(arrayValue)
} else {
let objectValue = try container.decode([String: RawJsonValue].self)
self = .object(objectValue)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .boolean(let boolValue):
try container.encode(boolValue)
case .number(let numberValue):
try container.encode(numberValue)
case .string(let stringValue):
try container.encode(stringValue)
case .array(let arrayValue):
try container.encode(arrayValue)
case .object(let objectValue):
try container.encode(objectValue)
}
}
}
现在我们可以安全地解码并根据需要转换为 JSON 字符串:
struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
let id, deviceType: String
let state: State
let error: String?
let thingUUID: Int?
let discoveryTimeout, installationTimeout: Int
let configurationPayload: RawJsonValue?
}
let jsonData = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let registration = try! decoder.decode(Registration.self, from: jsonData)
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let payloadString = String(data: try! encoder.encode(registration.configurationPayload), encoding: .utf8)!
print(payloadString) // {"title":"Some Title","views":9999,"url":"https:\/\/www.someurl.com\/","category":"test"}
我能看到的唯一问题是解码十进制数时可能会丢失精度,这是 Foundation JSON 解码器的一个已知问题。
此外,还可以删除一些 null
值。这可以通过迭代键并具有特殊的 null
类型手动解码 object
来解决。
根据网络请求给出以下 JSON;如果您想将其解码为与 Codable
一致的 Swift 对象,但又想保留嵌套的 JSON,即键 configuration_payload
的值,怎么可能你做到了吗?
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
}
使用下面的 Swift struct
,我希望能够将 configuration_payload
作为 String
.
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: String?
}
据我所知,Swift 中的 JSONDecoder
将 configuration_payload
的值视为嵌套的 JSON 并希望将其解码为自己的对象.更让人困惑的是,configuration_payload
并不总是会 return 相同的 JSON 结构,它会有所不同,所以我无法创建 Swift struct
我可以期待并在需要时简单地 JSON 再次对其进行编码。我需要能够将该值存储为字符串,以说明 configuration_payload
键下 JSON 的变化。
这里是 configurationPayload
字典,所以你的 Registration
结构如下所示
struct Registration : Codable {
let configurationPayload : ConfigurationPayload?
let deviceType : String?
let discoveryTimeout : Int?
let id : String?
let installationTimeout : Int?
let state : String?
let thingUuid : Int?
enum CodingKeys: String, CodingKey {
case configurationPayload = "configuration_payload"
case deviceType = "device_type"
case discoveryTimeout = "discovery_timeout"
case id = "id"
case installationTimeout = "installation_timeout"
case state = "state"
case thingUuid = "thing_uuid"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
configurationPayload = ConfigurationPayload(from: decoder)
deviceType = try values.decodeIfPresent(String.self, forKey: .deviceType)
discoveryTimeout = try values.decodeIfPresent(Int.self, forKey: .discoveryTimeout)
id = try values.decodeIfPresent(String.self, forKey: .id)
installationTimeout = try values.decodeIfPresent(Int.self, forKey: .installationTimeout)
state = try values.decodeIfPresent(String.self, forKey: .state)
thingUuid = try values.decodeIfPresent(Int.self, forKey: .thingUuid)
}
}
你的ConfigurationPayload
看起来像这样
struct ConfigurationPayload : Codable {
let category : String?
let title : String?
let url : String?
let views : Int?
enum CodingKeys: String, CodingKey {
case category = "category"
case title = "title"
case url = "url"
case views = "views"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
category = try values.decodeIfPresent(String.self, forKey: .category)
title = try values.decodeIfPresent(String.self, forKey: .title)
url = try values.decodeIfPresent(String.self, forKey: .url)
views = try values.decodeIfPresent(Int.self, forKey: .views)
}
}
一种(比您可能想要的更有限)方法是确保 configuration_payload
JSON 中的 Value
部分是已知的 Codable
单一类型(String
) 而不是 Any
可以产生多种类型(String
、Int
、Double
等)。
我试图让它与 [String: Any]
一起用于 configuration_payload
,问题是 Any
不符合 Codable
。
然后我尝试使用 [String: String]
实现 configuration_payload
并且能够像下面那样工作。
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: [String: String]? // NOT [String: Any]?
enum CodingKeys: String, CodingKey {
case id = "id"
case deviceType = "device_type"
case state = "state"
case thingUUID = "thing_uuid"
case discoveryTimeout = "discovery_timeout"
case installationTimeout = "installation_timeout"
case configurationPayload = "configuration_payload"
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id) ?? ""
deviceType = try values.decodeIfPresent(String.self, forKey: .deviceType) ?? ""
let stateRaw = try values.decodeIfPresent(String.self, forKey: .state) ?? ""
state = Registration.State(rawValue: stateRaw) ?? .provisioning
thingUUID = try values.decodeIfPresent(Int.self, forKey: .thingUUID)
discoveryTimeout = try values.decodeIfPresent(Int.self, forKey: .discoveryTimeout) ?? 0
installationTimeout = try values.decodeIfPresent(Int.self, forKey: .installationTimeout) ?? 0
configurationPayload = try values.decodeIfPresent([String: String].self, forKey: .configurationPayload)
}
}
测试
let json = Data("""
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload": {
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": "9999"
}
}
""".utf8
)
let decoded = try JSONDecoder().decode(Registration.self, from: json)
print(decoded)
let encoded = try JSONEncoder().encode(decoded)
print(String(data: encoded, encoding: .utf8))
您可以使用 AnyCodable.
等第三方库将 JSON 对象解码为[String: Any]
您的 Registration
结构将如下所示:
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUUID: Int?
public let discoveryTimeout, installationTimeout: Int
public let configurationPayload: [String: AnyCodable]?
}
然后你可以将 [String: AnyCodable]
类型转换为 [String: Any]
甚至 String
:
let jsonString = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let registration = try decoder.decode(Registration.self, from: Data(jsonString.utf8))
// to [String: Any]
let dictionary = registration.configurationPayload?.mapValues { [=11=].value }
// to String
if let configurationPayload = registration.configurationPayload {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let data = try encoder.encode(configurationPayload)
let string = String(decoding: data, as: UTF8.self)
print(string)
}
} catch {
print(error)
}
这对于 Codable 协议是不可能的,因为你事先不知道类型。您必须编写自己的方法或采用不同的解码策略。
let json = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload": {
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
""".data(using: .utf8)
do {
let decoded = try? Registration.init(jsonData: json!)
print(decoded)
}catch {
print(error)
}
public struct Registration {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id: String
public let device_type: String
public let state: State
public let error: String?
public let thing_uuid: Int?
public let discovery_timeout, installation_timeout: Int
public let configuration_payload: [String: Any]?
public init(jsonData: Data) throws {
let package = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String : Any]
id = package["id"] as! String
device_type = package["device_type"] as! String
state = State(rawValue: package["state"] as! String)!
error = package["error"] as? String
thing_uuid = package["thing_uuid"] as? Int
discovery_timeout = package["discovery_timeout"] as! Int
installation_timeout = package["installation_timeout"] as! Int
configuration_payload = package["configuration_payload"] as? [String: Any]
}
}
这是处理不同类型的一种可能方法。您还可以创建一个包含键的结构并循环遍历它们,我认为这说明了基本思想。
编辑:
if let remaining = package["configuration_payload"] as? Data,
let data = try? JSONSerialization.data(withJSONObject: remaining, options: []) as Data,
let string = String(data: data, encoding: .utf8) {
// store your string if you want it in string formatt
print(string)
}
如果你有一个可能的键列表,使用可选项是你可以使用 Codable 的另一种方式。您可以通过这种方式混合密钥 - 只有可用的密钥才会尝试成为 encoded/decoded
import UIKit
public struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
public let id, deviceType: String
public let state: State
public let error: String?
public let thingUuid: Int?
public let discoveryTimeout, installationTimeout: Int
public var configurationPayload: ConfigurationPayload?
}
// nested json can be represented as a codable struct
public struct ConfigurationPayload: Codable {
let title: String?
let url: String?
let category: String?
let views: Int?
let nonTitle: String?
let anotherUrl: String?
let someCategory: String?
let someViews: Int?
// computed properties aren't part of the coding strategy
// TODO: avoid duplication in loop
var jsonString: String {
let mirror = Mirror(reflecting: self).children
let parameters = mirror.compactMap({[=10=].label})
let values = mirror.map({[=10=].value})
let keyValueDict = zip(parameters, values)
var returnString: String = "{\n"
for (key, value) in keyValueDict {
if let value = value as? Int {
returnString.append("\"\(key)\": \"\(value)\n")
} else if let value = value as? String {
returnString.append("\"\(key)\": \"\(value)\n")
}
}
returnString.append("}")
return returnString
}
}
// your json has a preceding key of "registration", this is the type you will decode
public struct RegistrationParent: Codable {
var registration: Registration
}
let jsonDataA =
"""
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
}
""".data(using: .utf8)!
let jsonDataB =
"""
{
"registration": {
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"non_title": "Some Title",
"another_url": "https://www.someurl.com/",
"some_category": "test",
"some_views": 9999
}
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
var registrationA = try decoder.decode(RegistrationParent.self, from: jsonDataA)
print(registrationA.registration.configurationPayload?.jsonString ?? "{}")
var registrationB = try decoder.decode(RegistrationParent.self, from: jsonDataB)
print(registrationB.registration.configurationPayload?.jsonString ?? "{}")
} catch {
print(error)
}
正如其他人所说,你不能只保留一部分而不解码。然而,解码未知数据是微不足道的:
enum RawJsonValue {
case boolean(Bool)
case number(Double)
case string(String)
case array([RawJsonValue?])
case object([String: RawJsonValue])
}
extension RawJsonValue: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let boolValue = try? container.decode(Bool.self) {
self = .boolean(boolValue)
} else if let numberValue = try? container.decode(Double.self) {
self = .number(numberValue)
} else if let stringValue = try? container.decode(String.self) {
self = .string(stringValue)
} else if let arrayValue = try? container.decode([RawJsonValue?].self) {
self = .array(arrayValue)
} else {
let objectValue = try container.decode([String: RawJsonValue].self)
self = .object(objectValue)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .boolean(let boolValue):
try container.encode(boolValue)
case .number(let numberValue):
try container.encode(numberValue)
case .string(let stringValue):
try container.encode(stringValue)
case .array(let arrayValue):
try container.encode(arrayValue)
case .object(let objectValue):
try container.encode(objectValue)
}
}
}
现在我们可以安全地解码并根据需要转换为 JSON 字符串:
struct Registration: Codable {
public enum State: String, Codable {
case provisioning, provisioned
}
let id, deviceType: String
let state: State
let error: String?
let thingUUID: Int?
let discoveryTimeout, installationTimeout: Int
let configurationPayload: RawJsonValue?
}
let jsonData = """
{
"id": "0000-0000-0000-0000-000",
"device_type": "device",
"state": "provisioning",
"thing_uuid": 999999999,
"discovery_timeout": 10,
"installation_timeout": 90,
"configuration_payload":
{
"title": "Some Title",
"url": "https://www.someurl.com/",
"category": "test",
"views": 9999
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let registration = try! decoder.decode(Registration.self, from: jsonData)
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let payloadString = String(data: try! encoder.encode(registration.configurationPayload), encoding: .utf8)!
print(payloadString) // {"title":"Some Title","views":9999,"url":"https:\/\/www.someurl.com\/","category":"test"}
我能看到的唯一问题是解码十进制数时可能会丢失精度,这是 Foundation JSON 解码器的一个已知问题。
此外,还可以删除一些 null
值。这可以通过迭代键并具有特殊的 null
类型手动解码 object
来解决。