Luda
Luda

Reputation: 7078

Decoding JSON into Codable Objects - with condition

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?

Upvotes: 0

Views: 414

Answers (2)

Chris
Chris

Reputation: 3817

I suggest two approaches:

Approach 1

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.

Approach 2

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:

Approach 1

Pro: Makes the data more self-descriptive

Con: Allows a collection with neither items nor locations

Approach 2

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.

Upvotes: 3

Hitesh Surani
Hitesh Surani

Reputation: 13567

Instead of going with conditional parsing, I suggest you can use the common class with multiple attributes with optional value and used it according to requirement. Please refer below code.

For example if itemTitle is nil then execute logic of locationName so on.

class Collection: NSObject, Codable {
    let collectionType: String
    let collectionName: String
    let data:data?

    private enum CodingKeys: String, CodingKey {
        case collectionType
        case collectionName
    }
}

class data: NSObject, Codable {

    let itemTitle: String?
    let itemSubtitle: String?
    let locationName: String?

    private enum CodingKeys: String, CodingKey {
        case itemTitle
        case itemSubtitle
        case locationName
    }
}

Upvotes: 0

Related Questions