It's not about decoding a property value with multiple types (int, string)..
I have an object called data
it can return multiple types, What could be done at this point may look something like this :
enum MyData: Codable {
case ObjOne(groupObject)
case ObjTwo(imageObject)
init(from decoder: Decoder) throws {
let value = try decoder.singleValueContainer()
if let v = try? value.decode(groupObject.self) {
self = .ObjOne(v)
return
} else if let v = try? value.decode(imageObject.self) {
self = .ObjTwo(v)
return
}
throw Rating.ParseError.notRecognizedType(value)
}
enum ParseError: Error {
case notRecognizedType(Any)
}
}
The issue here is that i try to make MyData
decode the object based on another value that was used in the previous decoding process, in short words, i want to pass a value to MyData
so it can determine which to decode
I have this
enum ContentType: String, Codable {
case linear
case grid
case slider
}
And i want MyData
to know about this ContentType
value so MyData
can determine how the flow will go,
So where did ContentType come from ? it's in the same list of properties in the previous main object, coming from something that looks like this
struct Catalog: Codable {
var dataType: ContentType?
var data: MyData?
}
What i want to achieve in more simple words ?
struct Catalog: Codable {
var dataType: ContentType?
var data: MyData<dataType>? <--// i know this is not possible,
// -- but i want MyData to know about the dataType value that will be decoded
}
--------- JSON i want to parse
[{
"data_type": "group",
"data": {
"group_id": 127 // this refers to object : groupObject
}
},
{
"data_type": "image",
"data": {
"image": "http://google.com/favicon" // this is referring : imageObject
}
}
]
You see the point above, is that "data" can return different objects, based on the value of data_type
Rather than using generics I created an empty protocol that conforms to Decodable
and used that as the type for data
. Then the content structs needs to conform to this protocol.
protocol MyData: Decodable {}
struct Group: MyData {
let groupId: Int
}
struct Image: MyData {
let image: String
}
struct Catalog: Decodable {
var dataType: String
var data: MyData
enum CodingKeys: String, CodingKey {
case dataType, data
}
enum ParseError: Error {
case notRecognizedType(Any)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
dataType = try container.decode(String.self, forKey: .dataType)
switch dataType {
case "group":
data = try container.decode(Group.self, forKey: .data)
case "image":
data = try container.decode(Image.self, forKey: .data)
default:
throw ParseError.notRecognizedType(dataType)
}
}
}
Note that I didn't use the enum ContentType
in the init
because it didn't match the sample json data but that should be easily fixed.
Standard code for using this solution
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode([Catalog].self, from: data)
print(result)
} catch {
print(error)
}
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