Swift Codable - 解析 JSON 可以包含不同数据类型的数组
Swift Codable - Parse JSON array which can contain different data type
我正在尝试解析一个 JSON 数组,它可以是
{
"config_data": [
{
"name": "illuminate",
"config_title": "Blink"
},
{
"name": "shoot",
"config_title": "Fire"
}
]
}
也可以是以下类型
{
"config_data": [
"illuminate",
"shoot"
]
}
甚至
{
"config_data": [
25,
100
]
}
所以为了使用 JSON解码器解析它,我创建了一个结构如下 -
Struct Model: Codable {
var config_data: [Any]?
enum CodingKeys: String, CodingKey {
case config_data = "config_data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
config_data = try values.decode([Any].self, forKey: .config_data)
}
}
但这行不通,因为 Any 不确认可解码协议。这可能是什么解决方案。该数组可以包含任何类型的数据
您正在尝试 JSON to object
或 object to JSON
?您可以尝试使用此代码添加任何 swift 文件:
extension String {
var xl_json: Any? {
if let data = data(using: String.Encoding.utf8) {
return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
}
return nil
}
}
extension Array {
var xl_json: String? {
guard let data = try? JSONSerialization.data(withJSONObject: self, options: []) else {
return nil
}
return String(data: data, encoding: .utf8)
}
}
extension Dictionary {
var xl_json: String? {
guard let data = try? JSONSerialization.data(withJSONObject: self, options: []) else {
return nil
}
return String(data: data, encoding: .utf8)
}
}
和运行此代码:
let str = "{\"key\": \"Value\"}"
let dict = str.xl_json as! [String: String] // JSON to Objc
let json = dict.xl_json // Objc to JSON
print("jsonStr - \(str)")
print("objc - \(dict)")
print("jsonStr - \(json ?? "nil")")
终于,你会得到它:
jsonStr - {"key": "Value"}
objc - ["key": "Value"]
jsonStr - {"key":"Value"}
如果第二个 JSON 出现,您首先需要决定要做什么。第二种 JSON 格式的信息较少。您想如何处理丢失的这些数据 (config_title
)?你真的需要它们吗?
如果确实需要存储 config_title
,那么我建议您创建一个 ConfigItem
结构,如下所示:
struct ConfigItem: Codable {
let name: String
let configTitle: String?
init(name: String, configTitle: String? = nil) {
self.name = name
self.configTitle = configTitle
}
// encode and init(decoder:) here...
// ...
}
实施所需的 encode
和 init(decoder:)
方法。你懂的。
现在,当您解码 JSON 时,照常解码 config_data
密钥。但是这次,您可以解码为 [ConfigItem]
,而不是使用 [Any]
!显然这并不总是有效,因为 JSON 有时可能是第二种形式。因此,您捕获由此引发的任何错误并使用 [String]
解码 config_data
。然后,将字符串数组映射到一堆 ConfigItem
s!
我使用 quicktype 来推断 config_data
的类型,它为您的对象、字符串和整数值建议了一个具有不同大小写的枚举:
struct ConfigData {
let configData: [ConfigDatumElement]
}
enum ConfigDatumElement {
case configDatumClass(ConfigDatumClass)
case integer(Int)
case string(String)
}
struct ConfigDatumClass {
let name, configTitle: String
}
这是the complete code example。解码 enum
有点棘手,但 quicktype 可以帮助您:
// To parse the JSON, add this file to your project and do:
//
// let configData = try? JSONDecoder().decode(ConfigData.self, from: jsonData)
import Foundation
struct ConfigData: Codable {
let configData: [ConfigDatumElement]
enum CodingKeys: String, CodingKey {
case configData = "config_data"
}
}
enum ConfigDatumElement: Codable {
case configDatumClass(ConfigDatumClass)
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(ConfigDatumClass.self) {
self = .configDatumClass(x)
return
}
throw DecodingError.typeMismatch(ConfigDatumElement.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ConfigDatumElement"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .configDatumClass(let x):
try container.encode(x)
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
struct ConfigDatumClass: Codable {
let name, configTitle: String
enum CodingKeys: String, CodingKey {
case name
case configTitle = "config_title"
}
}
使用 enum
很好,因为这样您可以获得最多的 type-safety。其他答案似乎失去了这一点。
使用 quicktype 的便捷初始化选项,一个有效的代码示例是:
let data = try ConfigData("""
{
"config_data": [
{
"name": "illuminate",
"config_title": "Blink"
},
{
"name": "shoot",
"config_title": "Fire"
},
"illuminate",
"shoot",
25,
100
]
}
""")
for item in data.configData {
switch item {
case .configDatumClass(let d):
print("It's a class:", d)
case .integer(let i):
print("It's an int:", i)
case .string(let s):
print("It's a string:", s)
}
}
这会打印:
It's a class: ConfigDatumClass(name: "illuminate", configTitle: "Blink")
It's a class: ConfigDatumClass(name: "shoot", configTitle: "Fire")
It's a string: illuminate
It's a string: shoot
It's an int: 25
It's an int: 100
我正在尝试解析一个 JSON 数组,它可以是
{
"config_data": [
{
"name": "illuminate",
"config_title": "Blink"
},
{
"name": "shoot",
"config_title": "Fire"
}
]
}
也可以是以下类型
{
"config_data": [
"illuminate",
"shoot"
]
}
甚至
{
"config_data": [
25,
100
]
}
所以为了使用 JSON解码器解析它,我创建了一个结构如下 -
Struct Model: Codable {
var config_data: [Any]?
enum CodingKeys: String, CodingKey {
case config_data = "config_data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
config_data = try values.decode([Any].self, forKey: .config_data)
}
}
但这行不通,因为 Any 不确认可解码协议。这可能是什么解决方案。该数组可以包含任何类型的数据
您正在尝试 JSON to object
或 object to JSON
?您可以尝试使用此代码添加任何 swift 文件:
extension String {
var xl_json: Any? {
if let data = data(using: String.Encoding.utf8) {
return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
}
return nil
}
}
extension Array {
var xl_json: String? {
guard let data = try? JSONSerialization.data(withJSONObject: self, options: []) else {
return nil
}
return String(data: data, encoding: .utf8)
}
}
extension Dictionary {
var xl_json: String? {
guard let data = try? JSONSerialization.data(withJSONObject: self, options: []) else {
return nil
}
return String(data: data, encoding: .utf8)
}
}
和运行此代码:
let str = "{\"key\": \"Value\"}"
let dict = str.xl_json as! [String: String] // JSON to Objc
let json = dict.xl_json // Objc to JSON
print("jsonStr - \(str)")
print("objc - \(dict)")
print("jsonStr - \(json ?? "nil")")
终于,你会得到它:
jsonStr - {"key": "Value"}
objc - ["key": "Value"]
jsonStr - {"key":"Value"}
如果第二个 JSON 出现,您首先需要决定要做什么。第二种 JSON 格式的信息较少。您想如何处理丢失的这些数据 (config_title
)?你真的需要它们吗?
如果确实需要存储 config_title
,那么我建议您创建一个 ConfigItem
结构,如下所示:
struct ConfigItem: Codable {
let name: String
let configTitle: String?
init(name: String, configTitle: String? = nil) {
self.name = name
self.configTitle = configTitle
}
// encode and init(decoder:) here...
// ...
}
实施所需的 encode
和 init(decoder:)
方法。你懂的。
现在,当您解码 JSON 时,照常解码 config_data
密钥。但是这次,您可以解码为 [ConfigItem]
,而不是使用 [Any]
!显然这并不总是有效,因为 JSON 有时可能是第二种形式。因此,您捕获由此引发的任何错误并使用 [String]
解码 config_data
。然后,将字符串数组映射到一堆 ConfigItem
s!
我使用 quicktype 来推断 config_data
的类型,它为您的对象、字符串和整数值建议了一个具有不同大小写的枚举:
struct ConfigData {
let configData: [ConfigDatumElement]
}
enum ConfigDatumElement {
case configDatumClass(ConfigDatumClass)
case integer(Int)
case string(String)
}
struct ConfigDatumClass {
let name, configTitle: String
}
这是the complete code example。解码 enum
有点棘手,但 quicktype 可以帮助您:
// To parse the JSON, add this file to your project and do:
//
// let configData = try? JSONDecoder().decode(ConfigData.self, from: jsonData)
import Foundation
struct ConfigData: Codable {
let configData: [ConfigDatumElement]
enum CodingKeys: String, CodingKey {
case configData = "config_data"
}
}
enum ConfigDatumElement: Codable {
case configDatumClass(ConfigDatumClass)
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(ConfigDatumClass.self) {
self = .configDatumClass(x)
return
}
throw DecodingError.typeMismatch(ConfigDatumElement.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ConfigDatumElement"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .configDatumClass(let x):
try container.encode(x)
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
struct ConfigDatumClass: Codable {
let name, configTitle: String
enum CodingKeys: String, CodingKey {
case name
case configTitle = "config_title"
}
}
使用 enum
很好,因为这样您可以获得最多的 type-safety。其他答案似乎失去了这一点。
使用 quicktype 的便捷初始化选项,一个有效的代码示例是:
let data = try ConfigData("""
{
"config_data": [
{
"name": "illuminate",
"config_title": "Blink"
},
{
"name": "shoot",
"config_title": "Fire"
},
"illuminate",
"shoot",
25,
100
]
}
""")
for item in data.configData {
switch item {
case .configDatumClass(let d):
print("It's a class:", d)
case .integer(let i):
print("It's an int:", i)
case .string(let s):
print("It's a string:", s)
}
}
这会打印:
It's a class: ConfigDatumClass(name: "illuminate", configTitle: "Blink")
It's a class: ConfigDatumClass(name: "shoot", configTitle: "Fire")
It's a string: illuminate
It's a string: shoot
It's an int: 25
It's an int: 100