Swift декодируемый гетерогенный массив json

Я рассмотрел пару вопросов и ответов здесь, по SO, и хотя они похожи на мой вопрос, они не совсем решают мою проблему, поскольку я пробовал их кучу, но это не работает. Вот мой json и метод, который я пробовал, и я все время получаю сообщение об ошибке «Данные не могут быть прочитаны, потому что они имеют неправильный формат».

 {
   "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)
 }
 } 

Это много json и много кода. Вы пытались отладить его, чтобы определить, где возникает ошибка? Вы дважды проверили, что все переменные определены с правильным типом?

Joakim Danielson 11.10.2018 08:37

Да, я проверил и просто не мог понять, откуда взялась проблема. Я знаю это многое, но любая помощь будет принята с благодарностью @JoakimDanielson

codeperfect 11.10.2018 08:41

Ваши данные, подобные JSON, не являются действительным JSON. Многие брекеты {} и брекеты [] не уравновешивают. Если это действительно те данные, которые вы получаете, вы не можете проанализировать их с помощью других библиотек JSON. Пожалуйста, попробуйте найти правильный JSON, который у вас должен быть. С текущими данными, подобными JSON, вам очень сложно помочь.

OOPer 11.10.2018 08:52

Ваш JSON недействителен. Где код для EventLocation, HomeFeedFeaturedData и Price?

subdan 11.10.2018 08:59

Ваш JSON действителен. Как я вижу, «данные» различаются для каждого объекта. В первой записи указаны FeatureData, а во второй - HomeFeedFeatureData. В своем коде вы определяете EventCalendar, но где вы его используете? Вы должны проверить, является ли это FeatureData или HomeFeedFeatureData.

tspentzas 11.10.2018 09:01

Не печатайте error.localizedDescription, распечатайте экземпляр error. Ошибка сообщает вам, что именно не так. И вы можете удалить CodingKeys, если они соответствуют именам членов структуры.

vadian 11.10.2018 09:21

@codeperfect, можете ли вы предоставить код, который вызывает метод decode экземпляра JSONDecoder?

Sviatoslav Yakymiv 11.10.2018 10:40
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
3
7
68
1

Ответы 1

В вашем коде много неправильного. Просмотрите мой список ниже, делайте это шаг за шагом, и вы обнаружите, что это намного проще, чем вы это делаете.

  1. Используйте структуры, а не классы, где вы можете (особенно если данные неизменяемы)
  2. Используйте let, а не var, и не задавайте им начальные значения.
  3. Тщательно подумайте о том, что делает ваши данные действительными / недействительными. Если значение должен присутствует для действительных данных, сделайте свойство необязательным (например, если FeedFeature должен иметь id, чтобы быть действительным, объявите let id: String.
  4. Если значение может отсутствовать (и данные все еще действительны), сделайте свойство необязательным, но не установит для него значение по умолчанию (например, let subTitle: String?)
  5. Используйте правильный тип для ваших свойств - startDT - это Date, поэтому объявите как let startDT: Date (или Date?, если это необязательно). Вы можете преобразовать форматированные даты String в значения Date, установив dateDecodingStrategy. Используйте URL, а не String, например, let thumbnail: URL?,
  6. Не объявляйте CodingKeys, если все имена соответствуют свойствам
  7. Если вы правильно настроили свойства, вы можете удалить свои собственные методы init(from decoder: Decoder).
  8. Если вы удалите логику Удалить повторяющиеся / недействительные цены из вашего init - вы сможете делать все вышеперечисленное без кода любой, кроме деклараций. Вы можете сделать это после инициализации структуры (возможно, использовать временный объект для декодирования, а затем скопировать в другой вместе с де-дублированием)

Вы можете легко проверить это на детской площадке. Просто начни с…

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)

а затем постепенно наращивайте свои объекты, добавляя свойства по одному или несколько за раз.

Другие вопросы по теме