-3

I have gone through a couple of questions and answers here on SO, and even though they look similar to my question they don't quite address my issue, as I have tried a bunch of them and yet it is not working. Here is my json and the method I have tried and I keep getting an error of "The data couldn’t be read because it isn’t in the correct format."

 {
   "status": 1,
   "errorMsg": "success",
   "data": [
   {
  "id": null,
  "subMenuId": null,
  "type": "Coming Feat",
  "data": {
    "link": "/google.com",
    "title": "Google",
    "shortDescription": "This is fun",
    "imageUrl": "",
    "openInNewWindow": false
  },
  "datas": null,
  "component": null
},
{
  "id": "wdub208t2ghf0b",
  "subMenuId": "39g3hvb83hb98hv",
  "type": "GoingEvent",
  "data": {
    "eventId": "983gv83hv8hv38",
    "sessionId": null,
    "title": "Fest",
    "iconMarker": "http://google.com/sites.png",
    "isPaid": false,
    "startDT": "2018-07-18T16:00:00Z",
    "endDT": "2018-10-31T22:00:00Z",
    "subTitle": null,
    "startDate": "Oct, 2018",
    "endDate": "Oct, 2018",
    "openTime": "04:00 PM",
    "closeTime": "10:00 PM",
    "thumbnail": "https://static.visit.com/estival23.jpg",
    "verticalFeaturedImageUrl": "",
    "horizontalImageUrl": "",
    "categoryTitle": "Celebration",
    "eventCategories": [
      "394bf3w9fbv93v8",
      "dhvbwuehv80"
    ],
    "locations": [
      {
        "uniqueName": "fest",
        "title": "Got if",
        "area": "",
        "region": "Put it",
        "latitude": 67.14517,
        "longitude": 78.797733,
        "distance": "N/A",
        "startDate": "2018-07-18T16:00:00",
        "endDate": "2018-07-27T22:00:00",
        "distancevalue": 0,
        "duration": "N/A",
        "durationValue": 0,
        "valid": true,
        "hasSet": false
      }
    ],
    "prices": null
  },
  "datas": null,
  "component": null
 }
 ]
}

  class FeatureData: Decodable {

var link: String?
var title: String?
var shortDescription: String?
var imageUrl: String?

enum CodingKeys: String, CodingKey {
    case link
    case title
    case shortDescription
    case imageUrl
}

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    link = try container.decode(String.self, forKey: .link)
    title = try container.decode(String.self, forKey: .title)
    shortDescription = try container.decode(String.self, forKey: 
  .shortDescription)
    imageUrl = try container.decode(String.self, forKey: .imageUrl)
 }

 init() {
 }
 }


 class FeedFeature: Decodable {

var id: String?
var subMenuId: String?
var type: String?
var data = HomeFeedFeatureData()

enum Codingkeys: String, CodingKey {
    case id
    case subMenuId
    case type
    case data
 }

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: Codingkeys.self)
    id = try container.decode(String.self, forKey: .id)
    subMenuId = try container.decode(String.self, forKey: .subMenuId)
    type = try container.decode(String.self, forKey: .type)
    data = try container.decode(HomeFeedFeatureData.self, forKey: 
.data)
}

init() {

 }
 }


   class EventCalendar: Decodable {
// MARK: Properties
var eventId: String = ""
var sessionId: String = ""
var title: String = ""
var iconMarker: String?
var isPaid: Bool = false
var startDT: String = ""
var endDT: String = ""
var subTitle: String = ""
var startDate: String = ""
var endDate: String = ""
var openTime: String = ""
var closeTime: String = ""
var thumbnail: String = ""
var locations: [EventLocation] = []
var prices: [Price]?
var categoryTitle: String = ""

var isLoadingCell: Bool = false
var isSelected: Bool = false

enum CodingKeys: String, CodingKey {
    case eventId = "eventId"
    case sessionId = "sessionId"
    case title = "title"
    case iconMarker = "iconMarker"
    case isPaid = "isPaid"
    case startDT = "startDT"
    case endDT = "endDT"
    case subTitle = "subTitle"
    case startDate = "startDate"
    case endDate = "endDate"
    case openTime = "openTime"
    case closeTime = "closeTime"
    case thumbnail = "thumbnail"
    case locations = "locations"
    case prices = "prices"
    case categoryTitle = "categoryTitle"
  }

init() {}

// MARK: Methods
required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    eventId = try container.decode(String.self, forKey: .eventId)
    sessionId = try container.decodeIfPresent(String.self, forKey: 
   .sessionId) ?? ""
    title = try container.decodeIfPresent(String.self, forKey: .title) 
     ?? ""
    iconMarker = try container.decodeIfPresent(String.self, forKey: 
  .iconMarker) ?? ""
    isPaid = try container.decodeIfPresent(Bool.self, forKey: .isPaid) 
    ?? false
    startDT = try container.decodeIfPresent(String.self, forKey: 
  .startDT) ?? ""
    endDT = try container.decodeIfPresent(String.self, forKey: .endDT) 
  ?? ""
    subTitle = try container.decodeIfPresent(String.self, forKey: 
.subTitle) ?? ""
    startDate = try container.decodeIfPresent(String.self, forKey: 
.startDate) ?? ""
    endDate = try container.decodeIfPresent(String.self, forKey: 
 .endDate) ?? ""
    openTime = try container.decodeIfPresent(String.self, forKey: 
 .openTime) ?? ""
    closeTime = try container.decodeIfPresent(String.self, forKey: 
.closeTime) ?? ""
    thumbnail = try container.decodeIfPresent(String.self, forKey: 
 .thumbnail) ?? ""
    locations = try container.decodeIfPresent([EventLocation].self, 
 forKey: .locations) ?? []
    categoryTitle = try container.decodeIfPresent(String.self, forKey: 
 .categoryTitle) ?? ""

    // Remove duplicate/invaid prices - The same logic as 
 EventMapComponent
    if let tempPrice = try container.decode([Price]?.self, forKey: 
 .prices) {
        var uniquePrices: [Price] = []
        for price in tempPrice {
            if !uniquePrices.contains(where: { (checkPrice) -> Bool in
                    checkPrice.priceInfo == price.priceInfo &&
                        checkPrice.value == price.value &&
                        checkPrice.currencyCode == price.currencyCode
                }),
                price.priceInfo.count > 0 &&
                    price.value.count > 0 &&
                    price.currencyCode.count > 0 &&
                    price.bookingUrl.count > 0 {

                // Filter for 0 value prices
                if let priceValue = Double(price.value), priceValue > 0 
  {
                    uniquePrices.append(price)
                }
            }
        }
        prices = uniquePrices
    }

    isSelected = BookMarkManager.shared.isFavoriteItem(by: eventId)
 }
 } 
user28
  • 89
  • 5
codeperfect
  • 84
  • 1
  • 11
  • That’s a lot of json and a lot of code. Have you tried and debug it to identity where the error occurs? Have you double checked that all variables are defined with the correct type? – Joakim Danielson Oct 11 '18 at 06:37
  • Yes I have checked and i just could not figure where the issue is coming from. I know its a lot but any help will be highly appreciated @JoakimDanielson – codeperfect Oct 11 '18 at 06:41
  • 1
    Your JSON-like data is not a valid JSON. Many braces `{` `}` and brackets `[` `]` are not balancing. If that really is the actual data you get, you cannot parse it with any other JSON libraries. Please try to find the valid JSON you should have. With the current JSON-like data, it is very hard to help you. – OOPer Oct 11 '18 at 06:52
  • Your JSON is invalid. Where is the code for EventLocation, HomeFeedFeaturedData and Price? – Daniil Subbotin Oct 11 '18 at 06:59
  • Your JSON is valid. As I can see the "data" is different per object. In the first entry is FeatureData and in the second entry is HomeFeedFeatureData. In your code, you define EventCalendar but where are you using it? You have to check if it's FeatureData or HomeFeedFeatureData. – tspentzas Oct 11 '18 at 07:01
  • 1
    Don't print `error.localizedDescription`, print the `error` instance. The error tells you exactly what's wrong. And you can delete the CodingKeys if they match the struct member names. – vadian Oct 11 '18 at 07:21
  • @codeperfect can you provide code that calls `decode` method of `JSONDecoder` instance? – Sviatoslav Yakymiv Oct 11 '18 at 08:40

1 Answers1

1

There's a lot wrong with your code. Go through my list below, take it a step at a time, and you'll find that it's a whole lot easier than you're making it

  1. Use structs rather than classes where you can (especially if the data is immutable)
  2. Use let rather than var, and don't give them initial values.
  3. Think carefully about what makes your data valid / invalid. If a value must be there for valid data, make the property non-optional (e.g. if a FeedFeature must have an id to be valid, declare let id: String.
  4. If a value might not be there (and the data would still be valid), make the property optional, but don't set it to a default value (e.g. let subTitle: String?)
  5. Use the correct type for your properties - startDT is a Date, so declare as let startDT: Date (or Date? if it's optional). You can convert formatted String dates to Date values by setting the dateDecodingStrategy. Use URL rather than String e.g .let thumbnail: URL?,
  6. Don't declare CodingKeys when the names all match the properties
  7. If you've set up the properties correctly you can remove your own init(from decoder: Decoder) methods.
  8. If you remove your Remove duplicate/invalid prices logic from your init - you should be able to do all the above without any code other than the declarations. You can do that after the struct has been initialised (maybe use a temp object for decoding and then copy into another along with the de-duping)

You can easily test this in a playground. Just start with…

let data = """
{your json}
"""".data(using: .utf8)!

struct Response: Decodable {
    let status: Int
    let errorMsg: String
    let data: [FeedFeature]
}

struct FeedFeature: Decodable {
}

JSONDecoder().decode([Response.self], from: data)

and then gradually build up your objects, adding properties one or more at a time.

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160