Fail to decode JSON with swift Combine - json

I'm trying to read this JSON data with Combine but I cannot make it work if I try to read the release field.
The address https://amiiboapi.com/api/ returns something like this:
{
"amiibo": [
{
"amiiboSeries": "Super Smash Bros.",
"character": "Mario",
"gameSeries": "Super Mario",
"head": "00000000",
"image": "https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00000002.png",
"name": "Mario",
"release": {
"au": "2014-11-29",
"eu": "2014-11-28",
"jp": "2014-12-06",
"na": "2014-11-21"
},
"tail": "00000002",
"type": "Figure"
},
{
"amiiboSeries": "Super Mario Bros.",
"character": "Mario",
"gameSeries": "Super Mario",
"head": "00000000",
"image": "https://raw.githubusercontent.com/N3evin/AmiiboAPI/master/images/icon_00000000-00340102.png",
"name": "Mario",
"release": {
"au": "2015-03-21",
"eu": "2015-03-20",
"jp": "2015-03-12",
"na": "2015-03-20"
},
"tail": "00340102",
"type": "Figure"
}
]
}
I have my model like this:
// MARK: - Amiibo List
struct AmiibosList: Codable {
let amiibo: [Amiibo]
}
// MARK: - Amiibo
struct Amiibo: Codable {
let amiiboSeries: String
let character: String
let gameSeries: String
let head: String
let image: String
let name: String
let release: Release
let tail: String
let type: String
}
// MARK: - Release
struct Release: Codable {
let au : String?
let eu : String?
let jp : String?
let na : String?
}
And I'm trying to fetch the data like this:
guard let url = URL(string: "https://amiiboapi.com/api/") else {
fatalError("Invalid URL")
}
var publisher = URLSession.shared.dataTaskPublisher(for: url)
.receive(on: RunLoop.main)
.map(\.data)
.decode(type: AmiiboList.self, decoder: JSONDecoder())
.sink(receiveCompletion: { completion in
if case .failure(let err) = completion {
print("Failed with error \(err)")
}
}, receiveValue: { value in
print("Received \(value)")
// print(" Received \(value.amiibo[0].release)")
})
If I comment/remove the release from my amiibo struct, everything works. For some reason I cannot retrieve the data with the release dates and I can't figure why.
Do I need to do anything else for nested JSON data?

With the below, you can simply do: response.amiibo[0].release to get the release object. Note that the release object contains Date objects rather than Strings. That should be helpful.
func example(data: Data) throws -> Response {
let data = jsonString.data(using: .utf8)!
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
return try decoder.decode(Response.self, from: data)
}
struct Response: Codable {
let amiibo: [Amiibo]
}
struct Amiibo: Codable {
let amiiboSeries: String
let character: String
let gameSeries: String
let head: String
let image: URL?
let name: String
let release: Release
let tail: String
let type: String
}
struct Release: Codable {
let au: Date?
let eu: Date?
let jp: Date?
let na: Date?
}

Related

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

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
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
}

Parsing Dynamic Json in Swift

{
"AAPL" : {
"quote": {...},
"news": [...],
"chart": [...]
},
"FB" : {
"quote": {...},
"news": [...],
"chart": [...]
},
}
How would you decode this in swift. The stocks change but the underlying quote, news, and chart stay the same. Also to mention this json of stocks could be 500 long with unknown sorting order.
For the information in quote it would look like:
{
"calculationPrice": "tops",
"open": 154,
ect...
}
inside news:
[
{
"datetime": 1545215400000,
"headline": "Voice Search Technology Creates A New Paradigm For
Marketers",
ect...
}
]
Inside charts:
[
{
"date": "2017-04-03",
"open": 143.1192,
ect...
}
]
What I have been trying is something along the lines of this as an example...
Json Response:
{
"kolsh" : {
"description" : "First only brewed in Köln, Germany, now many American brewpubs..."
},
"stout" : {
"description" : "As mysterious as they look, stouts are typically dark brown to pitch black in color..."
}
}
Struct/Model for codable:
struct BeerStyles : Codable {
struct BeerStyleKey : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
static let description = BeerStyleKey(stringValue: "description")!
}
struct BeerStyle : Codable {
let name: String
let description: String
}
let beerStyles : [BeerStyle]
}
Decoder:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: BeerStyleKey.self)
var styles: [BeerStyle] = []
for key in container.allKeys {
let nested = try container.nestedContainer(keyedBy: BeerStyleKey.self,
forKey: key)
let description = try nested.decode(String.self,
forKey: .description)
styles.append(BeerStyle(name: key.stringValue,
description: description))
}
self.beerStyles = styles
}
This example is from https://benscheirman.com/2017/06/swift-json/ and I'm trying to apply it to my json structure.
Try this code ...:)
Alamofire.request("", method: .get, encoding: JSONEncoding.default) .responseJSON { response in
if response.result.isSuccess{
let json = response.result.value! as? [String : Any] ?? [:]
for (key, value) in json {
//here key will be your apple , fb
let valueofkey = value as? [String:Any] ?? [:]
let quote = valueofkey["quote"] as? [String:Any] ?? [:]
let news = valueofkey["news"] as? [Any] ?? []
let chart = valueofkey["chart"] as? [Any] ?? []
}
}
}
I hope it will work for you ... :)
If the contents of quote, news and chart have same type, i.e. assuming that quote is of type [String:String] and news and chart are of type [String], you can use Codable as well.
Example:
With the below model,
struct Model: Decodable {
let quote: [String:String]
let news: [String]
let chart: [ String]
}
Now, you can parse the JSON like so,
do {
let response = try JSONDecoder().decode([String:Model].self, from: data)
print(response)
} catch {
print(error)
}

How to parse and get data from little complicated json in swift4?

I need help with parsing json file called weather.json.
weather.json
{
"weatherinfo": {
"local": [
{
"country": "Korea",
"weather": "rainy",
"temperature": "20"
},
{
"country": "US",
"weather": "sunny",
"temperature": "30"
}
]
}
}
And here's my code
struct Weather: Decodable {
var weatherInfo: WeatherInfo?
}
struct WeatherInfo: Decodable {
let local: [Local]?
}
struct Local: Decodable {
let country: String?
let weather: String?
let temperature: String?
}
inside func viewDidLoad() in UIViewController
let decoder = JSONDecoder()
guard let path: String = Bundle.main.path( forResource: "weather", ofType: "json") else { return }
let jsonURL = URL(fileURLWithPath: path)
URLSession.shared.dataTask(with: jsonURL) { (data, response, error) in
guard let data = data else { return }
print("pass1")
do {
let weather = try decoder.decode(Weather.self, from: data)
print("parsing pass..")
print(weather) // Weather(weatherInfo: nil)
print(weather.weatherInfo?.local?[0].country) // nil
} catch let jsonErr{
print("Error: \(jsonErr)")
}
}.resume()
I succeed parsing but I can't get data from weather constant..
How can I get country value from that json file..?
Can anyone fix my code please?..
First of all URLSession for reading a file in the bundle is overkill. Just get the Data.
Second of all declare everything non-optional since you clearly know that all keys are available
struct Weather: Decodable {
let weatherinfo: WeatherInfo // note the lowercase spelling
}
struct WeatherInfo: Decodable {
let local: [Local]
}
struct Local: Decodable {
let country: String
let weather: String
let temperature: String
}
The countries are in the array local in weatherInfo
let url = Bundle.main.url(forResource: "weather", withExtension: "json")!
let data = try! Data(contentsOf: url)
let result = try! JSONDecoder().decode(Weather.self, from: data)
for location in result.weatherinfo.local {
print("In \(location.country) the weather is \(location.weather) and the temperature is \(location.temperature) degrees")
}

Swift: Parse data Codable protocol not working

I have a link that returns a json file, I try to print the data but it does not work it is always nil, here is the link:
http://heroapps.co.il/employee-tests/ios/logan.json
And my code:
struct DataClass: Codable {
let name: String?
let nickname: String?
let image: URL?
let dateOfBirth: Int?
let powers: [String]?
let actorName: String?
let movies: [Movie]?
enum CodingKeys: String, CodingKey {
case name = "name"
case nickname = "nickname"
case image = "image"
case dateOfBirth = "dateOfBirth"
case powers = "powers"
case actorName = "actorName"
case movies = "movies"
}
}
struct Movie: Codable {
let name: String?
let year: Int?
enum CodingKeys: String, CodingKey {
case name = "name"
case year = "year"
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let gitUrl = URL(string: "http://heroapps.co.il/employee-tests/ios/logan.json") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(Movie.self, from: data)
print(gitData.name ?? "") //Print nil
} catch let err {
print("Err", err)
}
}.resume()
}
Thank you for helping me find where my error comes from, this is the first time I use this method to retrieve JSON data
You are not parsing the top level of the JSON. (success, errorCode, message and data).
Playground code for testing...
import Foundation
let jsonData = """
{
"success": true,
"errorCode": 0,
"message": "Succcess",
"data": {
"name": "Logan Howlett",
"nickname": "The Wolverine",
"image": "http://heroapps.co.il/employee-tests/ios/logan.jpg",
"dateOfBirth": 1880,
"powers": [
"Adamantium Bones",
"Self-Healing",
"Adamantium Claws"
],
"actorName": "Hugh Jackman",
"movies": [
{
"name": "X-Men Origins: Wolverine",
"year": 2009
},
{
"name": "The Wolverine",
"year": 2013
},
{
"name": "X-Men: Days of Future Past",
"year": 2014
},
{
"name": "Logan",
"year": 2017
}
]
}
}
""".data(using: .utf8)!
struct JSONResponse: Codable {
let success: Bool
let errorCode: Int
let message: String
let data: DataClass
}
struct DataClass: Codable {
let name: String?
let nickname: String?
let image: URL?
let dateOfBirth: Int?
let powers: [String]?
let actorName: String?
let movies: [Movie]?
enum CodingKeys: String, CodingKey {
case name = "name"
case nickname = "nickname"
case image = "image"
case dateOfBirth = "dateOfBirth"
case powers = "powers"
case actorName = "actorName"
case movies = "movies"
}
}
struct Movie: Codable {
let name: String?
let year: Int?
enum CodingKeys: String, CodingKey {
case name = "name"
case year = "year"
}
}
do {
let result = try JSONDecoder().decode(JSONResponse.self, from: jsonData)
print(result)
} catch {
print(error)
}

Decoding nested JSON with Swift 4

JSON =
{
"html_attributions": [],
"results": [
{
"geometry": {},
"name": "Cruise Bar, Restaurant & Events",
"vicinity": "Circular Quay W, Sydney"
},
{}
],
"status": "OK"
}
How do I retrieve name if it is nested within results?
Sam Try this i write sample code in playground using your Json. root.results will give you array of dictionary, you can easily traverse and get your desired name from it.
import UIKit
struct Root: Codable {
let results: [Results]?
private enum CodingKeys: String, CodingKey {
case results = "results"
}
}
struct Results: Codable {
let name: String?
let vicinity: String?
}
let url = Bundle.main.url(forResource: "data", withExtension: "json")
let data = NSData(contentsOf: url!)
do {
let root = try JSONDecoder().decode(Root.self, from: data as! Data)
if let name = root.results?.first?.name {
print(name)
}
} catch let error as NSError {
print(error.description)
}
Here is the json i have used.
{
"results": [{
"name": "Cruise Bar, Restaurant & Events",
"vicinity": "Circular Quay W, Sydney"
}]
}
You can do it like that:
Model:
import Foundation
struct HtmlInitial: Codable {
let results: [Result]?
let status: String
enum CodingKeys: String, CodingKey {
case results, status
}
}
struct Result: Codable {
let name, vicinity: String?
}
extension HtmlInitial {
init(data: Data) throws {
self = try JSONDecoder().decode(HtmlInitial.self, from: data)
}
}
use model Like that :
let url = Bundle.main.url(forResource: "APIResponse", withExtension: "json")!
if let data = try? Data.init(contentsOf: url) ,
let initial = try? HtmlInitial.init(data: data),
let result = initial.results?[0] {
print(result.name)
}