Как отфильтровать данные JSON в API со смешанными типами?


#1

Иногда в API попадаются элементы с другого раздела или со значениям nil.

Я сделал init структуры как опционал, и туда стали приходить значения nil.

struct ResultItem: Codable {
let data: DataData?

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    data = (try? values.decode(DataData.self, forKey: .data))
}
}

Далее пытался использовать compactMap, чтоб убрать пустые значения из json и не создавать для них ячейку в tableVIew. Но попытка была неудачной.

Далее решил сделать инициализаторы в структурах, но тоже не помогло, первый элемент все равно приходит пустой и идет в tableView.

final class APICaller {
static let shared = APICaller()
var isPagination = false

struct Constants {
    static let topHeadlinesURL = URL(string: "https://api.tjournal.ru/v2.0/timeline")
}

private init() {}
                                                // пробовал и Result<ResultItem, Error>
public func getTopStories(completion: @escaping (Result<[ResultItem], Error>) -> Void) {
    
    guard let url = Constants.topHeadlinesURL else {
        return
    }
    
    let task = URLSession.shared.dataTask(with: url) {data, _, error in
        
        if let error = error {
            completion(.failure(error))
        }
        else if let data = data {
            
            do {
                let json = try JSONDecoder().decode(APIResponce.self, from: data)
                completion(.success(json.result.items))
                print("news: \(json.result.items.count)")
                dump(json.result)
            }
            catch {
                completion(.failure(error))
            }
        }
    }
    task.resume()
}
 }

  struct APIResponce: Codable {
    let result: IsResult
  }

struct IsResult: Codable {
    let items: [ResultItem]
    let lastID, lastSortingValue: Int
    
    enum CodingKeys: String, CodingKey {
        case items
        case lastID = "lastId"
        case lastSortingValue
    }

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    items = (try values.decode([ResultItem].self, forKey: .items))
    lastID = (try values.decode(Int.self, forKey: .lastID))
    lastSortingValue = (try values.decode(Int.self, forKey: .lastSortingValue))
//        items = items.compactMap{$0.data}
    }
}

struct ResultItem: Codable {
    let data: DataData?
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        data = (try? values.decode(DataData.self, forKey: .data))
    }
}
struct DataData: Codable {
    let author: SubsiteClass?
    let title: String
    let blocks: [Block]?
    let subsite: SubsiteClass?
    
    enum CodingKeys: String, CodingKey {
        case author
        case subsite, title, blocks
    }

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    author = (try? values.decode(SubsiteClass.self, forKey: .author))
    title = (try values.decode(String.self, forKey: .title))
    blocks = (try? values.decode([Block].self, forKey: .blocks))
    subsite = (try? values.decode(SubsiteClass.self, forKey: .subsite))
 }
}

// MARK: - SubsiteClass / Раздел. (ex. "Новости")
struct SubsiteClass: Codable {
    let name: String
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = (try values.decode(String.self, forKey: .name))
    }
}

struct Block: Codable {
    let data: BlockData
//    let cover: Bool
}

   struct BlockData: Codable {
    let text : String
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        text = (try values.decode(String.self, forKey: .text))
    }
}

#2

потому что data у вас должен быть массив, а не объект и не нужно его делать опциональным, просто изначально пустой массив.
так, что-то я запутался. почему сперва data идет как массив, а потом как объект…

я такое не пробывал, но попробуйте вы
создайте 2 параметра

let dataObject: DataData?
let dataArray: [DataData]

а в CodingKeys оба параметра привяжите на одно свойство “data” из JSON’a


#3

ну потому что в json два раза идет data, вложененные типы же.
в той data про которую вы говорили , не надо массива использовать, и это разные ветки, почему тут DataData, немного неясно?
Мне кажется, такая структура до добра не доведет.
Я временно поставил .filter просто в функции парсера, но я надеелся на ответ как это реализовать в init()

По типу items, которые ResultItem:
let newItems = items.compactMap { ... guard let data = data else { return nil } }
и их записать как - то в items в инициализаторе


#4

Что-то вы все усложняете. Посмотрите на библиотеку ObjectMapper. Я пользуюсь только ей. Возможности огромные и все просто.