I am trying to parse a JSON array which can be
{
"config_data": [
{
"name": "illuminate",
"config_title": "Blink"
},
{
"name": "shoot",
"config_title": "Fire"
}
]
}
or it can be of following type
{
"config_data": [
"illuminate",
"shoot"
]
}
or even
{
"config_data": [
25,
100
]
}
So to parse this using JSONDecoder I created a struct as follows -
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)
}
}
But this would not work since Any does not confirm to decodable protocol. What could be the solution for this. The array can contain any kind of data
I used quicktype to infer the type of config_data
and it suggested an enum with separate cases for your object, string, and integer values:
struct ConfigData {
let configData: [ConfigDatumElement]
}
enum ConfigDatumElement {
case configDatumClass(ConfigDatumClass)
case integer(Int)
case string(String)
}
struct ConfigDatumClass {
let name, configTitle: String
}
Here's the complete code example. It's a bit tricky to decode the enum
but quicktype helps you out there:
// 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"
}
}
It's nice to use the enum
because you get the most type-safety that way. The other answers seem to lose this.
Using quicktype's convenience initializers option, a working code sample is:
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)
}
}
This prints:
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
You first need to decide what to do if the second JSON comes up. The second JSON format has way less info. What do you want to do with those data (config_title
) that you lost? Do you actually need them at all?
If you do need to store the config_title
s if they are present, then I suggest you to create a ConfigItem
struct, which looks like this:
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...
// ...
}
Implement the required encode
and init(decoder:)
methods. You know the drill.
Now, when you are decoding your JSON, decode the config_data
key as usual. But this time, instead of using an [Any]
, you can decode to [ConfigItem]
! Obviously this won't always work because the JSON can sometimes be in the second form. So you catch any error thrown from that and decode config_data
using [String]
instead. Then, map the string array to a bunch of ConfigItem
s!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With