How to parse local JSON data where nested (optional) property is same as main.
Items data may be available or may not be available.
struct Category: Identifiable, Codable {
let id: Int
let name: String
let image: String
var items: [Category]?
}
I am using common Bundle extension to parse JSON data.
extension Bundle {
func decode<T: Codable>(_ file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "y-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
return loaded
}
}
For eg data :
[
{
"id": 1,
"name": "Apple",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPhone",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPhone 11 Pro",
"image": "img_url"
},
{
"id": 2,
"name": "iPhone 11 Pro Max",
"image": "img_url"
}
]
},
{
"id": 2,
"name": "iPad",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPad mini",
"image": "img_url"
},
{
"id": 2,
"name": "iPad Air",
"image": "img_url"
},
{
"id": 3,
"name": "iPad Pro",
"image": "img_url"
}
]
}
]
},
{
"id": 2,
"name": "Samsung",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "Phone",
"image": "img_url"
},
{
"id": 2,
"name": "Tablet",
"image": "img_url"
}
]
}
]
Nesting is not the issue here, You are facing an Array of Contents. so you should pass [Content] to the decoder like:
let jsonDecoder = JSONDecoder()
try! jsonDecoder.decode([Category].self, from: json)
đ Property Wrapper
You can implement a simple property wrapper for loading and decoding all of your properties:
#propertyWrapper struct BundleFile<DataType: Decodable> {
let name: String
let type: String = "json"
let fileManager: FileManager = .default
let bundle: Bundle = .main
let decoder = JSONDecoder()
var wrappedValue: DataType {
guard let path = bundle.path(forResource: name, ofType: type) else { fatalError("Resource not found") }
guard let data = fileManager.contents(atPath: path) else { fatalError("File not loaded") }
return try! decoder.decode(DataType.self, from: data)
}
}
Now you can have any property that should be loaded from a file in a Bundle like:
#BundleFile(name: "MyFile")
var contents: [Content]
Note that since the property should be loaded from the bundle, I raised a FatalError. Because the only person should be responsible for these errors is the developer at the code time (not the run time).
Related
I am new to swift . I created simple playground and added the file with extension json into playground . I am trying to decode the result and print the ID into console but any reason ,it not printing the result into console , I do not see error into console window ..
Here is the playground project structure ..
Here is my json file ..
let json = """
{
"id": "1",
"options": [
{
"id": "11",
"options": [
{
"id": "111",
"options": []
}
]
},
{
"id": "2",
"options": [
{
"id": "21",
"options": []
},
{
"id": "22",
"options": [
{
"id": "221",
"options": []
}
]
}
]
}
]
}
Here is the code .. I tried ..
struct People: Codable {
let id: String
let options: [People]
}
func loadJson(filename fileName: String) -> People? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let jsonData = try decoder.decode(People.self, from: data)
print(jsonData.id)
return jsonData
} catch {
print("error:\(error)")
}
}
return nil
}
It not printing the ID of the decode json ..
So I did get it to print the ID, I changed the people file name to people.json and changed the contents to:
{
"id": "1",
"options": [{
"id": "11",
"options": [{
"id": "111",
"options": []
}]
},
{
"id": "2",
"options": [{
"id": "21",
"options": []
},
{
"id": "22",
"options": [{
"id": "221",
"options": []
}]
}
]
}
]
}
(Notice I removed the let json = statement.)
After that in the playground where define the People struct and the loadJson method you can call it like so:
loadJson(filename: "people")
So now you end up with:
struct People: Codable {
let id: String
let options: [People]
}
func loadJson(filename fileName: String) -> People? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let jsonData = try decoder.decode(People.self, from: data)
print(jsonData.id)
return jsonData
} catch {
print("error:\(error)")
}
}
return nil
}
loadJson(filename: "people")
How can I decode this JSON by using JSONDecoder? I'm trying, but I always ended up crashing. I have another post talking about it but the error was bigger before, now I'm stucked only on that. I also tried by using JSONSerialization, but I think using JSONDecoder is more clean.
I got this error message when the compiler pass by JSONDecoder part:
Could not get API data. typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil)), The data couldnât be read because it isnât in the correct format.
This is how I'm trying to parse all my data:
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 posts = try JSONDecoder().decode([Audiobook].self, from: unwrappedDAta)
print(posts)
completion(nil)
} catch {
print("Could not get API data. \(error), \(error.localizedDescription)")
}
}
dataTask.resume()
}
}
Struct that I'm using:
import Foundation
struct Data : Decodable {
let data : [Audiobook]
}
struct Audiobook: Decodable {
let id : Int?
let descricao : String?
let urlImagem : String?
// init(dictionary: Audiobook) {
// self.descricao = dictionary["descricao"] as! String
// self.urlImagem = dictionary["urlImagem"] as! String
//
// }
}
JSON to be parsed:
{
"data": [
{
"id": 1,
"descricao": "Games",
"urlImagem": "http:\/\/39ahd9aq5l9101brf3b8dq58.wpengine.netdna-cdn.com\/wp-content\/uploads\/2013\/06\/3D-Gaming.png"
},
{
"id": 2,
"descricao": "Livros",
"urlImagem": "http:\/\/4.bp.blogspot.com\/-6Bta1H9d22g\/UJAIJbqcHhI\/AAAAAAAAKi4\/hvgjWrlFc64\/s1600\/resenha-missiologia.png"
},
{
"id": 3,
"descricao": "Celulares",
"urlImagem": "http:\/\/pt.seaicons.com\/wp-content\/uploads\/2015\/11\/Mobile-Smartphone-icon.png"
},
{
"id": 4,
"descricao": "Inform\u00e1tica",
"urlImagem": "http:\/\/portal.ifrn.edu.br\/campus\/ceara-mirim\/noticias\/ifrn-oferece-curso-de-informatica-basica-para-pais-dos-estudantes\/image_preview"
},
{
"id": 5,
"descricao": "Eletrodom\u00e9stico",
"urlImagem": "http:\/\/classificados.folharegiao.com.br\/files\/classificados_categoria\/photo\/8\/sm_4d5ed3beb0f31b61cb9a01e46ecd0cf9.png"
},
{
"id": 6,
"descricao": "TVs",
"urlImagem": "http:\/\/i.utdstc.com\/icons\/256\/terrarium-tv-android.png"
},
{
"id": 7,
"descricao": "Filmes e S\u00e9ries",
"urlImagem": "https:\/\/pbs.twimg.com\/profile_images\/801033586438733824\/91Y_N91t_reasonably_small.jpg"
},
{
"id": 8,
"descricao": "M\u00f3veis e Decora\u00e7\u00f5es",
"urlImagem": "https:\/\/image.flaticon.com\/icons\/png\/128\/148\/148188.png"
},
{
"id": 9,
"descricao": "Moda, Beleza e Perfumaria",
"urlImagem": "http:\/\/icon-icons.com\/icons2\/196\/PNG\/128\/fashion_23852.png"
},
{
"id": 10,
"descricao": "Papelaria",
"urlImagem": "http:\/\/esen.pt\/in\/images\/stories\/skills_256.png"
}
]
}
You're trying to decode it as [AudioBook] when the data is actually a dictionary wrapping the array, as in your Data structure. Just change it to:
let data = try JSONDecoder().decode(Data.self, from: unwrappedDAta)
and you should be good to go.
I am trying out the new JSONDecoder() in swift 4 and am trying to parse the following flickr API response : (https://api.flickr.com/services/rest/?api_key=api_key&method=flickr.photos.search&format=json&per_page=25&text=hello&nojsoncallback=1). However I get an error saying that "error processing json data: The data couldnât be read because it isnât in the correct format." However the data seems to be in the correct format to me. Am I doing anything wrong?
let dataTask = session.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print("json error: \(error.localizedDescription)")
return
} else if let data = data {
print(response)
do {
let decoder = JSONDecoder()
print(data)
let flickrPhotos = try decoder.decode(FlickrImageResult.self, from: data)
} catch {
dump(data)
print("json error: \(error.localizedDescription)")
}
}
}
dataTask.resume()
My data models are
struct FlickrImageResult : Codable {
var photos : FlickrPhoto? = nil
}
struct FlickrPhoto : Codable {
var photo : [FlickrURLs]? = nil
}
struct FlickrURLs: Codable {
var id : String? = nil
var owner: String? = nil
var secret: String? = nil
var server: String? = nil
var farm: String? = nil
}
I changed the number of images requested to 2 and Printing out the data returns
Optional({"photos":{"page":1,"pages":120197,"perpage":2,"total":"240393","photo":[{"id":"36729752762","owner":"152440263#N02","secret":"e62ba3e18b","server":"4432","farm":5,"title":"Hello there","ispublic":1,"isfriend":0,"isfamily":0},{"id":"36729384952","owner":"141041947#N06","secret":"bc0e5af630","server":"4380","farm":5,"title":"POST\ud83d\udd25 #891 | Hello Tuesday | Krescendo","ispublic":1,"isfriend":0,"isfamily":0}]},"stat":"ok"})
The only problem with your model is that farm is actually an Int. Here's a more complete version of your model accoirding to the docs (https://www.flickr.com/services/api/flickr.photos.search.html):
struct FlickrImageResult: Codable {
let photos : FlickrPagedImageResult?
let stat: String
}
struct FlickrPagedImageResult: Codable {
let photo : [FlickrURLs]
let page: Int
let pages: Int
let perpage: Int
let total: String
}
struct FlickrURLs: Codable {
let id : String
let owner: String
let secret: String
let server: String
let farm: Int
// let title: String
// If needed, camel-case and use CodingKeys enum
//let ispublic: Int
//let isfriend: Int
//let isfamily: Int
}
let jsonData = """
{
"photos": {
"page": 1,
"pages": 13651,
"perpage": 25,
"total": "341263",
"photo": [
{
"id": "36499638920",
"owner": "55126206#N07",
"secret": "7e82dee0ba",
"server": "4346",
"farm": 5,
"title": "[BREATHE]-Ririko,Sayaka&Chiyoko",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36724435032",
"owner": "92807782#N04",
"secret": "6d830d4a75",
"server": "4354",
"farm": 5,
"title": "Serendipity Designs # SWANK August 2017",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36087089863",
"owner": "152685136#N08",
"secret": "a4a3f2fe0a",
"server": "4365",
"farm": 5,
"title": "Hello Kitty Scooter",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36086949593",
"owner": "151818203#N02",
"secret": "fc1207d373",
"server": "4334",
"farm": 5,
"title": "Hip, Hip! It's Chip!",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36498504410",
"owner": "148300038#N02",
"secret": "5c7f6ff3e1",
"server": "4391",
"farm": 5,
"title": "Hello Kotti",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
}
]
},
"stat": "ok"
}
""".data(using: .utf8)!
let flickrPhotos = try! JSONDecoder().decode(FlickrImageResult.self, from: jsonData)
print(flickrPhotos)
P.S: The message error processing json data: The data couldnât be read because it isnât in the correct format. is the localized error message, use print(error) instead of print(errorlocalizedDescription) to obtain all the error data available (in your case, it will print that there's an issue when trying to decode the farm key).
I am having the following web service response in which I need parse the parameters based on "MainServiceCategories_ID"
Here is the response
{
"Entity": {
"ID": 20021,
"UserTypeID": 1,
"UserType": null,
"UserID": 30046,
"Code": "lPx1lTEq",
"Name": "بدعاإ",
"EnglishName": "Badra",
"IconProfileImageID": "",
"MainImageProfileImageID": "EED372C3-5C4F-E711-8100-0CC47A343427",
"LocationLng": 78.37021,
"LocationLat": 17.432563,
"Services": [
{
"ID": 11788,
"MainServiceCategories_ID": 3,
"ServiceCategories_ID": 6,
"Children": [
{
"Parent_ID": 6,
"Name": âJohnâ,
"EnglishName": âJohnnyâ,
},
{
"Parent_ID": 6,
"Name": âRonnyâ,
"EnglishName": âRonnyâ,
}]
},
{
"ID": 11788,
"MainServiceCategories_ID": 2,
"ServiceCategories_ID": 6,
"Children": [
{
"Parent_ID": 6,
"Name": âSamuelâ,
"EnglishName": âSamuelâ,
},
{
"Parent_ID": 6,
"Name": âBadriâ,
"EnglishName": âBadriâ,
}]
},
{
"ID": 11788,
"MainServiceCategories_ID": 3,
"ServiceCategories_ID": 6,
"Children": [
{
"Parent_ID": 6,
"Name": âemmaâ,
"EnglishName": âEmmaâ,
},
{
"Parent_ID": 6,
"Name": âSeanâ,
"EnglishName": âSeanâ,
}]
}]
this is the code I am writing to do it
do{
let entityDic = responseDictionary["Entity"] as? [String: Any]
let servicesDic = entityDic?["Services"] as? [Any]
for i in 0 ..< servicesDic!.count {
let services = servicesDic?[i] as? [String: Any]
let availableServices = services!["EnglishName"]
servicesNamesArr.append(availableServices! as! String)
let children = services!["Children"]
childrenServices.append(children!)
let MainServiceCategories_ID = services!["MainServiceCategories_ID"]
MainServiceCategoriesID.append(MainServiceCategories_ID!)
}
print("childrenServices \(childrenServices)")
}
catch let error{
print(error)
}
I am able to get upto children dictionaries. Now the thing is I am trying to parse "English name" under children section and separate them based on "MainServiceCategories_ID" where I am getting fatal error in unwrapping the value.
for example if MainServiceCategories_ID =1 then that "English Name" need to be store in an array and if MainServiceCategories_ID =2 then into another array
The problem is, you are trying to parse "EnglishName" as a key of an element of the "Services" array and not as a key of a "Children" element. "Services" does not have a key "EnglishName", so you can't store that in servicesNamesArr. servicesDic should also be an Array of Dictionaries and not an array of Any objects.
See below code using guard statements using safe optional unwrapping:
do{
guard let entityDic = responseDictionary["Entity"] as? [String: Any] else {return}
guard let servicesDic = entityDic?["Services"] as? [[String:Any]] else {return}
for service in services {
guard let MainServiceCategoryID = service["MainServiceCategories_ID"] as? Int else {return}
MainServiceCategoriesID.append(MainServiceCategoryID)
guard let children = service["Children"] as? [[String:Any]] else {return}
childrenServices.append(children)
if MainServiceCategoryID == 3 {
for child in children {
if let availableService = child["EnglishName"] as? String {
servicesNamesArr.append(availableService)
}
}
}
}
print("childrenServices \(childrenServices)")
} catch let error{
print(error)
}
I can successfully parse all data from my json file. In my application in the collection view i try to parse only 1 image data to cell but i got related all data with it. I'll share the JSON code and the parse code with you and lastly screen shot of simulator. I hope you can help me about it.
Thank you,
JSON code
{
"retcode": "200",
"content": [{
"id": 3,
"name": "X Treme",
"desc": "Polikarbon G\u00f6vde",
"category": "Design",
"thumbnail": [{
"id": 2,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/xtreme_red.jpg"
}, {
"id": 3,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/xtreme_orange.jpg"
}, {
"id": 4,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/xtreme_blue.jpg"
}, {
"id": 5,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/xtreme_green.jpg"
}, {
"id": 6,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/xtreme_clear.jpg"
}, {
"id": 7,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/xtreme_grey.jpg"
}, {
"id": 8,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/xtreme_slred.jpg"
}]
}, {
"id": 4,
"name": "Opal",
"desc": "Polikarbon Sandalye",
"category": "Design",
"thumbnail": [{
"id": 9,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/opal_orange.jpg"
}, {
"id": 10,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/opal_blue.jpg"
}, {
"id": 11,
"thumbnail": "http:\/\/www.ertonga.com\/product_images\/opal_green.jpg"
}]
}],
"error_msg": ""
}
Swift Code
if let url = NSURL(string: urlString) {
if let data = try? NSData(contentsOfURL: url, options: [])
{
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
var valueforname:Int = 0
if let blogs = json["content"] as? [[String: AnyObject]] {
for blog in blogs {
if let blog2 = blog["thumbnail"] as? [[String: AnyObject]] {
for blog3 in blog2 {
if let blog4 = blog3["thumbnail"] as? String {
var checkvalue1 = blog3["id"] as? Int
if Imagearray.contains(String(checkvalue1!)) {
}
else {
Imagearray.append(blog4)
}
}
}
}
}
}
}
catch {
print("error serializing JSON: \(error)")
}
}
}
and here is the screen shots. You can see the different colors of chairs I want to only 1 color for each item
Try this one:
NSURL(string: urlString) {
if let data = try? NSData(contentsOfURL: url, options: [])
{
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
var valueforname:Int = 0
if let blogs = json["content"] as? [[String: AnyObject]] {
for blog in blogs {
if let blog2 = blog["thumbnail"] as? [[String: AnyObject]] {
if let blog4 = blog2["thumbnail"] as? String
{
Imagearray.append(blog4)
}
}
}
}
}
catch {
print("error serializing JSON: \(error)")
}
}
}