I would like to decode JSON into Objects with Codable protocol.
The outcome that I want to achieve is:
[
[ Collection
< collectionType = item
< collectionName = some name`
< data = [ Item
< itemTitle = title
< itemSubtitle = subtitle,
Item
< itemTitle = title
< itemSubtitle = subtitle ],
[ Collection
< collectionType = location
< collectionName = some name`
< data = [ Location
< locationName = someName,
Location
< locationName = someName ],
[ Collection
< collectionType = item
< collectionName = some name`
< data = [ Item
< itemTitle = title
< itemSubtitle = subtitle,
Item
< itemTitle = title
< itemSubtitle = subtitle ],
[ Collection
< collectionType = location
< collectionName = some name`
< data = [ Location
< locationName = someName,
Location
< locationName = someName ]]
The JSON is as follows:
[{
"collectionType": "item",
"collectionName": "some name",
"data": [
{
"itemTitle": "title",
"itemSubtitle": "subtitle",
},
{
"itemTitle": "title",
"itemSubtitle": "subtitle",
}
]
},
{
"collectionType": "location",
"collectionName": "some name",
"data": [
{
"locationName": "a name",
},
{
"locationName": "a name",
}
]
},
{
"collectionType": "item",
"collectionName": "some name",
"data": [
{
"itemTitle": "title",
"itemSubtitle": "subtitle",
},
{
"itemTitle": "title",
"itemSubtitle": "subtitle",
}
]
},
{
"collectionType": "location",
"collectionName": "some name",
"data": [
{
"locationName": "a name",
},
{
"locationName": "a name",
}
]
}
]
As you can see the Collection will be of type item or location. And the data will be according to that type. How should I achieve that with Codable?
My objects are as follows:
class Collection: NSObject, Codable {
// MARK: - Properties
let collectionType: String
let collectionName: String
let data????
// MARK: - Keyes
private enum CodingKeys: String, CodingKey {
case collectionType
case collectionName
}
}
class Item: NSObject, Codable {
// MARK: - Properties
let itemTitle: String
let itemSubtitle: String
// MARK: - Keyes
private enum CodingKeys: String, CodingKey {
case itemTitle
case itemSubtitle
}
}
class Location: NSObject, Codable {
// MARK: - Properties
let locationName: String
// MARK: - Keyes
private enum CodingKeys: String, CodingKey {
case locationName
}
}
How can I propagate data with the appropriate objects?
I suggest two approaches:
Change your data structure to remove the ambiguity of whether data
describes an item or a location:
[{
"collectionName": "some name",
"items": [
{
"itemTitle": "title",
"itemSubtitle": "subtitle",
},
{
"itemTitle": "title",
"itemSubtitle": "subtitle",
}
]
},
{
"collectionName": "some name",
"locations": [
{
"locationName": "a name",
},
{
"locationName": "another name",
}
]
}]
... and modify your Collection
to have an optional locations
and optional items
.
If changing your JSON structure is not an option, then I suggest changing your Collection
class to:
class Collection: Codable {
let collectionType: String
let collectionName: String
let data: [CollectionData]
}
... and creating an enum CollectionData
:
enum CollectionError: Error {
case invalidData
}
enum CollectionData {
case item(Item)
case location(Location)
}
extension CollectionData: Codable {
init(from decoder: Decoder) throws {
if let item = try? Item(from: decoder) {
self = .item(item)
return
}
if let location = try? Location(from: decoder) {
self = .location(location)
return
}
throw CollectionError.invalidData
}
func encode(to encoder: Encoder) throws {
switch self {
case .item(let item):
try item.encode(to: encoder)
case .location(let location):
try location.encode(to: encoder)
}
}
}
Pros and cons of the two approaches:
Pro: Makes the data more self-descriptive
Con: Allows a collection with neither items
nor locations
Pro: Works with existing data structure
Con: Would allow a data
array that was partly Location
and partly Item
Unless there's more to your real code, you seem to be defining CodingKeys
exactly as the default would be, so you can probably remove that.