Swift / Combine JSON decodable - decode and receive array of content only 'Invalid top-level type in JSON write'

355 views Asked by At

I am receiving some JSON which looks like the below :

{
    "template": "search",
    "item": "2",
    "contents": [
      {
        "title": "title 1",
        "subtitle": "subtitle 1",
        "imageurl": "/data/dzzxw0177014_325qv.jpg?size=small",
        "fullscreenimageurl": "/data/xw0177014_325qv.jpg?size=large",
        "id": "0177014",
        "detaillink": "/apps/v2/details/programme/177014",
        "duration": "PT2H46M"
      },
      {
        "title": "title2",
        "subtitle": "subtitle 2",
        "imageurl": "/data_p//11436/origin_dzdzdzdzw0046394_43fu1.jpg?size=small",
        "fullscreenimageurl": "/data/11456/w0046394_43fu1.jpg?size=large",
        "id": "0046394",
        "detaillink": "/apps/v2/details/programme/MYW0046394",
        "duration": "PT1H40M46S"
      }
    ]
  }

and I have a corresponding model:

import Foundation

// MARK: - Welcome
struct Welcome {
    let template, item: String
    let contents: [Content]
}

// MARK: - Content
struct Content {
    let title, subtitle, imageurl, fullscreenimageurl: String
    let id, detaillink, duration: String
}

I have an API manager :

import Foundation
import Combine

class APIManager {
    
    static let shared = APIManager()
    
    let baseURL = "https:// ....."
    
    func fetchShows(with query: String) -> AnyPublisher<[Content], Error > {
        
        Future<Any, Error> { promise in
            
            self.loadJson(withQuery: query) { (result) in
                
                promise(.success(result))
                
            }
            
        }
        .tryMap {
            
            try JSONSerialization.data(withJSONObject: $0, options: .prettyPrinted)
            
        }
        .decode(type: [Content].self, decoder: jsonDecoder)
        .eraseToAnyPublisher()
    }
    
    var jsonDecoder: JSONDecoder {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return decoder
        
    }
        
    
    func loadJson(withQuery query: String,
                  completion: @escaping (Result<Data, Error>) -> Void) {
        
        
        let UrlString = baseURL + (query.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "")
        
        if let url = URL(string: UrlString) {
            
            print (UrlString)
            
            let urlSession = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
                
                if let error = error {
                    completion(.failure(error))
                }
                
                if let data = data {
                    
                    completion(.success(data))
                    
                }
            }
            
            
            urlSession.resume()
        }
    }
    
}

At the moment I have a crash with the error "Invalid top-level type in JSON write", I assume this is because the JSON that I am trying to decode isn't an array of Content. It's a Welcome Struct which contains an array of Content.

At what point can I say that I am only interested in the Contents "Array" and decode it ? Should this be defined in the model some how ?

Thanks

1

There are 1 answers

6
YodagamaHeshan On

You can use .map operator to transform your data objects.

.decode(type: Welcome.self, decoder: jsonDecoder) // <- here
.map { $0.contents } // <- here
.eraseToAnyPublisher()

In addition, you have to confirm your data objects to Decodable.

Adding Decodable keyword is enough, Since all the files types are Decodable here,

struct Welcome: Decodable { //<- Here
    let template, item: String
    let contents: [Content]
}

struct Content: Decodable { //<- Here
    let title, subtitle, imageurl, fullscreenimageurl: String
    let id, detaillink, duration: String
}