How to get only Array part inside of a json - arrays

Json:
{"photos":{"page":1,"pages":20,"perpage":100,"total":"1941","photo":[{"id":"40270314685","owner":"24843974#N00","secret":"46f5a82bd3","server":"785","farm":1,"title":"Raleigh C Easter 2018-30","ispublic":1,"isfriend":0,"isfamily":0},{"id":"39355840240","owner":"24843974#N00","secret":"eb23dc2e68","server":"816","farm":1,"title":"Raleigh C Easter 2018-31","ispublic":1,"isfriend":0,"isfamily":0},{"id":"40270318535","owner":"24843974#N00","secret":"97c6280b2f","server":"811","farm":1,"title":"Raleigh C Easter 2018-32","ispublic":1,"isfriend":0,"isfamily":0}]},"stat":"ok"}
I only want to get "photo":[{"id":"402703146... part as an array. This is my code
struct GetPhotosOfUser:Decodable{
let id: String
let secret: String
let server: String
let farm: String
enum TopLevelCodingKeys: String, CodingKey {
case photos
}
enum UserCodingKeys: String, CodingKey {
case photo
}
enum SecondCodingKeys: String, CodingKey {
case id
case secret
case server
case farm
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TopLevelCodingKeys.self)
let userContainer = try container.nestedContainer(keyedBy: UserCodingKeys.self, forKey: .photos)
let userContainer1 = try userContainer.nestedContainer(keyedBy: SecondCodingKeys.self, forKey: .photo)
id = try userContainer1.decode(String.self, forKey: .id)
secret = try userContainer1.decode(String.self, forKey: .secret)
server = try userContainer1.decode(String.self, forKey: .server)
farm = try userContainer1.decode(String.self, forKey: .farm)
}
}
var getphotos = [GetPhotosOfUser]()
func downloadJSON(completed: #escaping () -> ()){
let url = URL(string: "https://www.flickr.com/services/rest/?method=flickr.people.getPhotos&api_key=<api-key>&user_id=24843974#N00&format=json&nojsoncallback=1")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
self.getphotos = try JSONDecoder().decode([GetPhotosOfUser].self, from: data!)
DispatchQueue.main.async {
completed()
print("Json Baglandi")
print(self.getphotos.count)
}
}catch{
print("JSON Error")
print(error)
}
}
}.resume()
}
But it says ""Expected to decode Array but found a dictionary instead."
Could you please help me ?

You need to decode from the top level so your structs should be like this
struct TopLevel : Decodable {
let photos: SubLevel
}
struct SubLevel: Decodable {
let photo: [GetPhotosOfUser]
}
struct GetPhotosOfUser: Decodable{
let id: String
let secret: String
let server: String
let farm: Int //Obs! I changed the type here
}
and then when decoding you need to change your code to
let result = try JSONDecoder().decode(TopLevel.self, from: data!)
self.getphotos = result.photos.photo

Related

How to correctly parse JSON with root element as an array in Swift?

I've the following issue, where I don't get the structure right to decode a response from node-red for the status of my sonos players:
[[{"urlHostname":"192.168.1.1","urlPort":1400,"baseUrl":"http://192.168.1.1:1400","sonosName":"Sonos1","uuid":"RINCON_SONOS14001400","invisible":false},{"urlHostname":"192.168.1.2","urlPort":"1400","baseUrl":"http://192.168.1.2:1400","sonosName":"Sonos2","uuid":"RINCON_SONOS21400","invisible":false},{"urlHostname":"192.168.1.3","urlPort":"1400","baseUrl":"http://192.168.1.3:1400","sonosName":"Sonos3","uuid":"RINCON_SONOS31400","invisible":false},{"urlHostname":"192.168.1.4","urlPort":"1400","baseUrl":"http://192.168.1.4:1400","sonosName":"Sonos4","uuid":"RINCON_SONOS41400","invisible":false},{"urlHostname":"192.168.1.5","urlPort":"1400","baseUrl":"http://192.168.1.5:1400","sonosName":"Sonos5","uuid":"RINCON_SONOS51400","invisible":false},{"urlHostname":"192.168.1.6","urlPort":"1400","baseUrl":"http://192.168.1.6:1400","sonosName":"Sonos6","uuid":"RINCON_SONOS61400","invisible":false}]]
My structure & the call to decode looks as follows:
typealias Response = [sonosData]
struct sonosData: Codable {
let sonos: [sonosStatusEntry]?
}
struct sonosStatusEntry: Codable {
let status: [sonosStatus]?
}
struct sonosStatus: Codable {
let urlHostname: String
let urlPort: Int
let baseUrl: String
let sonosName: String
let uuid: String
let invisible: Bool
}
let response = try JSONDecoder().decode(Response.self, from: data)
I get the following error in Swift:
Failed to load: The data couldn’t be read because it isn’t in the correct format.
Any suggestions?
You have 2 errors with your code:
The data you are tring to decode is [[sonosStatus]]. so you need to decode as:
let response = try JSONDecoder().decode([[sonosStatus]].self, from: data)
urlPort represents both String and Int. So you need to either correct your JSON format or support both formats with a custom decoder.
You can achieve that with this simple Property wrapper:
#propertyWrapper
struct SomeKindOfInt: Codable {
var wrappedValue: Int?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringifiedValue = try? container.decode(String.self) {
wrappedValue = Int(stringifiedValue)
} else {
wrappedValue = try container.decode(Int.self)
}
}
}
And then using it like:
struct sonosStatus: Codable {
let urlHostname: String
#SomeKindOfInt var urlPort: Int?
let baseUrl: String
let sonosName: String
let uuid: String
let invisible: Bool
}
One of the the problem is related to urlPort. Your json have both Int and String values for urlPort. You can define custom init to handle that.
struct sonosStatus: Codable {
let urlHostname: String
let urlPort: Int
let baseUrl: String
let sonosName: String
let uuid: String
let invisible: Bool
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
urlHostname = try values.decode(String.self, forKey: .urlHostname)
baseUrl = try values.decode(String.self, forKey: .baseUrl)
sonosName = try values.decode(String.self, forKey: .sonosName)
uuid = try values.decode(String.self, forKey: .uuid)
invisible = try values.decode(Bool.self, forKey: .invisible)
// you can define urlPort as optional or unwrap it.
if let intVal = try? values.decode(Int.self, forKey: .urlPort) {
urlPort = intVal
} else if let stringVal = try? values.decode(String.self, forKey: .urlPort) {
urlPort = Int(stringVal) ?? 0
} else {
urlPort = ""
}
}
}
And also your json is an array of sonosStatus array.
So you need yo modify Response typealias like below:
typealias Response = [[sonosStatus]]
You can use JSONSerialization with the .allowFragments reading option.
let objects = (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? [[NSDictionary]]
And it was fixed on latest Swift version: https://bugs.swift.org/browse/SR-6163

Use JSONDecoder in swift 4

I am using swift 4 xcode 9.2, I got the below error when using JSONDecoder.
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [],
debugDescription: "Expected to decode Array but found a dictionary instead.",
underlyingError: nil))
class Hits: Codable {
let hits: [Hit]
init(hits: [Hit]) {
self.hits = hits
}
}
class Hit: Codable {
let recipe: String
let uri: String
let label: String
let image: String
let source: String
let url: String
let shareAs: String
let yield: String
init(recipe: String, uri: String, label: String, image: String, source: String, url: String, shareAs: String, yield: String) {
self.recipe = recipe
self.uri = uri
self.label = label
self.image = image
self.source = source
self.url = url
self.shareAs = shareAs
self.yield = yield
}
}
func downloadJSON() {
guard let downloadURL = url else {return}
URLSession.shared.dataTask(with: downloadURL) { (data, urlResponse, error) in
guard let data = data, error == nil, urlResponse != nil else { print("Something is wrong"); return }
print("download completed")
do {
let decoder = JSONDecoder()
let foods = try decoder.decode([Hits].self, from: data)
print(foods)
} catch {
print(error)
}
}.resume()
}
Here is the JSON:
https://api.edamam.com/search?q=chicken&app_id=110d8671&app_key=3f01522208d512f592625dfcd163ff5c&from=0&to=10
The error clears states that you are trying to decode an array but the actual type is a dictionary (single object).
Replace
let foods = try decoder.decode([Hits].self, from: data)
with
let foods = try decoder.decode(Hits.self, from: data)
And your classes (actually structs are sufficient) are
struct Recipe : Decodable {
let uri : URL
let label : String
let image : URL
let source : String
let url : URL
let shareAs : URL
let yield : Double
let calories, totalWeight, totalTime : Double
}
struct Hits: Decodable {
let hits: [Hit]
}
struct Hit: Decodable {
let recipe: Recipe
}
hits = try decoder.decode(Hits.self from: data)

create an array from JSON in swift

I am trying to convert JSON data into an array but I do not really have any idea how to do it.
I get the data and save it in strings and I can also show it on display.
struct User_Hosting: Codable {
let company_name: String
let website: String
let street: String
let housenumber: String
let zip: String
let city: String
enum CodingKeys: String, CodingKey {
case company_name = "company_name"
case website = "website"
case street = "street"
case housenumber = "housenumber"
case zip = "zip"
case city = "city"
}
}
And here some other codes:
let url = URL(string: "myURL.com")
URLSession.shared.dataTask(with: url!, completionHandler: { [weak self] (data, response, error) in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "An error occurred")
return
}
DispatchQueue.main.async {
self?.dataSource = try! JSONDecoder().decode([User_Hosting].self, from: data)
}
}).resume()
}
Your CodingKeys match the property names, so you can get rid of the enum at all
struct UserHosting: Codable {
let companyName: String
let website: String
let street: String
let housenumber: String
let zip: String
let city: String
}
Since you have a some snake case keys in JSON, you can change the JSONDecoder.keyDecodingStrategy to convertFromSnakeCase, like so
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Above decoder will treat keys such as company_name to be assigned to companyName property of your struct.
Finally you can decode your JSON in a do-catch block, so in case of an error we will have a message as to what went wrong.
do {
self.dataSource = try decoder.decode([UserHosting].self, from: data)
} catch {
print("JSON Decoding Error \(error)")
}

typeMismatch Error While parsing JSON data in Swift 4.2

Type mismatch Error - expected to decode Array but found dictionary.
I am using local json file embedded in the code. Json file contains the information about packages. I need to pick values and show them accordingly into tableview controller. Please identify what am I doing wrong in codable model or in a code. JSON Parsing not properly handled.
File Format:
{
"packages": [
{
"name": "Platinum Maksi 6 GB",
"desc": "Zengin içerikli Platinum Maksi Paketi ile Turkcell Uygulamalarının keyfini sürün!",
"subscriptionType": "monthly",
"didUseBefore": true,
"benefits": [
"TV+",
"Fizy",
"BiP",
"lifebox",
"Platinum",
"Dergilik"
],
"price": 109.90,
"tariff": {
"data": "6144",
"talk": "2000",
"sms": "100"
},
"availableUntil": "1558131150"
}
]
}
Models:
Base Model
struct Base : Codable {
let packages : [Package]?
enum CodingKeys: String, CodingKey {
case packages = "packages"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
packages = try values.decodeIfPresent([Package].self, forKey: .packages)
}
2) Package Model:
struct Package : Codable {
let availableUntil : String?
let benefits : String?
let desc : String?
let didUseBefore : Bool?
let name : String?
let price : Int?
let subscriptionType : String?
let tariff : Tariff?
enum CodingKeys: String, CodingKey {
case availableUntil = "availableUntil"
case benefits = "benefits"
case desc = "desc"
case didUseBefore = "didUseBefore"
case name = "name"
case price = "price"
case subscriptionType = "subscriptionType"
case tariff = "tariff"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
availableUntil = try values.decodeIfPresent(String.self, forKey: .availableUntil)
benefits = try values.decodeIfPresent(String.self, forKey: .benefits)
desc = try values.decodeIfPresent(String.self, forKey: .desc)
didUseBefore = try values.decodeIfPresent(Bool.self, forKey: .didUseBefore)
name = try values.decodeIfPresent(String.self, forKey: .name)
price = try values.decodeIfPresent(Int.self, forKey: .price)
subscriptionType = try values.decodeIfPresent(String.self, forKey: .subscriptionType)
tariff = try Tariff(from: decoder)
}
}
Tariff Model:
struct Tariff : Codable {
let data : String?
let sms : String?
let talk : String?
enum CodingKeys: String, CodingKey {
case data = "data"
case sms = "sms"
case talk = "talk"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(String.self, forKey: .data)
sms = try values.decodeIfPresent(String.self, forKey: .sms)
talk = try values.decodeIfPresent(String.self, forKey: .talk)
}
}
My code:
var pack = [Package]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = Bundle.main.url(forResource: "packageList", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
pack = try JSONDecoder().decode([Package].self, from: data)
print(pack)
} catch {
print(error)
}
You are decoding the wrong object. You have to decode always the root object of the JSON which is Base.
Get the Package array with base.packages
let base = try JSONDecoder().decode(Base.self, from: data)
pack = base.packages
Now you will get two other type mismatch errors, change the type of benefits to [String] and price to Double.
You can reduce your structs dramatically: Remove all CodingKeys and all initializers and declare all struct members as non-optional (remove the question marks).
Edit:
To decode availableUntil as Date declare it as Date
let availableUntil: Date
and add a custom date decoding strategy because the value is a string (It would be easier if the value was Int)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom{ decoder -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
guard let interval = TimeInterval(dateStr) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Date string cannot be converted to TimeInterval") }
return Date(timeIntervalSince1970: interval)
}
let data = try Data(contentsOf: url)
let base = try decoder.decode(Base.self, from: data)
pack = base.packages

Error on Serialization

I'm getting this error when I try to proceed on parsing the JSON. Does anyone know what I gotta do to fix it?
Error 1: Cannot subscript a value of type 'AudiobookJSON' (aka 'Array<Dictionary<String, Any>>') with an index of type 'String'
Error on Print
File Model: Audiobook.swift:
import Foundation
struct Audiobook : Codable {
let id: Int
let descricao: String
let urlImagem: String
init(dictionary: AudiobookJSON) {
self.id = dictionary["id"] as! Int//////ERROR MESSAGE ////////
self.descricao = dictionary["descricao"] as! String/////ERROR MESSAGE
self.urlImagem = dictionary["urlImagem"] as! String////ERROR MESSAGE
}
}
Folder Networking: APIClient.swift:
import Foundation
typealias AudiobookJSON = [[String: Any]]
struct APIClient {
static func getAudiobooksAPI(completion: #escaping ([Audiobook]?) -> Void) {
let url = URL(string: "https://alodjinha.herokuapp.com/categoria")
let session = URLSession.shared
guard let unwrappedURL = url else { print("Error unwrapping URL"); return }
let dataTask = session.dataTask(with: unwrappedURL) { (data, response, error) in
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
//let json = try JSONSerialization.jsonObject(with: unwrappedDAta, options: .allowFragments) as! [String:Any]
//let posts = json["data"] as? AudiobookJSON
let posts = try JSONDecoder().decode([Audiobook].self, from: unwrappedDAta)
print(posts)
completion(posts)
} catch {
print("Could not get API data. \(error), \(error.localizedDescription)")
}
}
dataTask.resume()
}
}
As I can see that AudiobookJSON is an array of key-value pairs that's why you are getting error: So you have to use codable like that:
First: you have to make Codable type struct like that(your codable struct variable names should be same as you are getting in response):
struct Audiobook: Codable {
let id: Int?
let descricao: String?
let urlImagem: String?
}
Second: when you get the response then parse directly using codale like that:
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
let posts = try JSONDecoder().decode([Audiobook].self, from: unwrappedDAta)
print(posts)
completion(posts)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
You can directly use the response like:
for audio in posts {
print("audio.id")
print("audio.descricao")
print("audio.urlImagem")
}

Resources