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)
}
Related
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
}
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?
}
How to access JSON using Codable. this is my sample json.
{
"status": "success",
"message": "Data received successfully",
"course": {
"id": 1,
"description": "something",
"name": "ielts",
"attachments": [
{
"id": 809,
"attachment": "https:--",
"file_name": "syllabus.pdf",
"description": "course",
},
{
"id": 809,
"attachment": "https:--",
"file_name": "syllabus.pdf",
"description": "course",
}]
"upcased_name": "IELTS"
}
}
This is my code.
struct ResponseObject: Codable {
let course: [Course]
}
struct Course: Codable {
let id: Int
let name: String
let description: String
let attachments: [Attachments]
}
struct Attachments: Codable {
let id: Int
let attachment: String
let file_name: String
let description: String
let about: String
}
var course: [Course] = []
This is my api call.
func fetchUserData() {
let headers: HTTPHeaders = [
"Authorization": "Token token="+UserDefaults.standard.string(forKey: "auth_token")!,
"Accept": "application/json"
]
let params = ["course_id" : "1"] as [String : AnyObject]
self.showSpinner("Loading...", "Please wait!!")
DispatchQueue.global(qos: .background).async {
AF.request(SMAConstants.courses_get_course_details , parameters: params, headers:headers ).responseDecodable(of: ResponseObject.self, decoder: self.decoder) { response in
DispatchQueue.main.async {
self.hideSpinner()
guard let value = response.value else {
print(response.error ?? "Unknown error")
return
}
self.course = value.course
}
}
}
}
}
I am getting following error.
responseSerializationFailed(reason:
Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error:
Swift.DecodingError.typeMismatch(Swift.Array,
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue:
"course", intValue: nil)], debugDescription: "Expected to decode
Array but found a dictionary instead.", underlyingError: nil))))
Your model object does not match the JSON structure. Try this instead:
struct ResponseObject: Codable {
let status, message: String
let course: Course
}
struct Course: Codable {
let id: Int
let courseDescription, name: String
let attachments: [Attachment]
let upcasedName: String
enum CodingKeys: String, CodingKey {
case id
case courseDescription = "description"
case name, attachments
case upcasedName = "upcased_name"
}
}
struct Attachment: Codable {
let id: Int
let attachment, fileName, attachmentDescription: String
enum CodingKeys: String, CodingKey {
case id, attachment
case fileName = "file_name"
case attachmentDescription = "description"
}
}
and to download and parse this with plain Swift and Foundation, use code like this:
let url = URL(string: SMAConstants.courses_get_course_details)!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
return
}
if let data = data {
do {
let response = try JSONDecoder().decode(ResponseObject.self, from: data)
// access your data here
} catch {
print(error)
}
}
}
task.resume()
I am accessing an API and decoding the json response into a User object, but I am attempting to change the JSON API strcuture. If I return a basic JSON object using this code
let httpURL = "https://dev.test/api/user"
var request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
do {
let user = try JSONDecoder().decode(User.self, from: data)
DispatchQueue.main.async {
print(user.email)
}
} catch let jsonErr {
print(jsonErr)
}
}
task.resume()
and the following JSON
{
"id": 2,
"email": "test#example.com",
}
This works fine, but I want to changed the API to return a set of nested objects. For example
{
"data": {
"user": {
"id": 2,
"email": "test#example.com"
},
"notifications": [
{
"id": "123",
"notifiable_type": "App\\User"
}
]
}
}
How can I decode the User? I've tried several variations of this let user = try JSONDecoder().decode(User.self, from: data.data.user) and let user = try JSONDecoder().decode(User.self, from: data["data"]["user"])
bt
You can try
struct Root: Codable {
let data: DataClass
}
struct DataClass: Codable {
let user: User
let notifications: [Notification]
}
struct Notification: Codable {
let id, notifiableType: String
enum CodingKeys: String, CodingKey {
case id
case notifiableType = "notifiable_type"
}
}
struct User: Codable {
let id: Int
let email: String
}
let user = try JSONDecoder().decode(Root.self, from:data)
OR
do {
let con = try JSONSerialization.jsonObject(with:data, options: [:]) as! [String:Any]
let data = con["data"] as! [String:Any]
let user = data["user"] as! [String:Any]
let finData = try JSONSerialization.data(withJSONObject:user, options: [:])
let userCon = try JSONDecoder().decode(User.self, from:finData)
print(userCon)
}
catch {
print(error)
}
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")
}