Parsing a specific content out of a dictionary in a json file - json

I'm getting the error "Cannot cast value of type dictionary to array" when I run my code. I'm tryna parse from a json file but its not working. Please help.
Here's the json file and i'm specifically tryna print out just the name values:
{
"data": [
{
"id": "2f4628f7ef6dce33d856121f",
"name": "Top products",
"description": "This category contains all the top products on"
},
{
"id": "3f4628f7ef6dce33d856121d",
"name": "Trending products",
"description": "This category contains all the trending products"
}
],
"page": 0,
"size": 2,
"count": 2,
"status": 200,
"isSuccess": true
}
func fetchFrontPageSections() {
let urlString = APIConstants.baseurl + APIConstants.frontpageSections
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
do {
let jsonData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
for dictionary in jsonData as! [[String: AnyObject]] {
print(dictionary["name"])
}
} catch let jsonError {
print(jsonError)
}
}.resume()
}
When I print the dictionary I'm getting that whole son file in my console which is fine but Im expecting to get a result like:
name: Top Products
name: Trending Products

In your constant jsonData you don't have an array, it's a dictionary as your json file explicitly show it. It contains multiple keys such as data, page, size, ...
You shoud access first the value related to the key data that contains an at this point the array of dictionary that you are looking for.

Parsing Json in Swift has never been easier since Codable.
here's an Excellent tutorial from the great guys at Ray Wenderlich: https://www.raywenderlich.com/3418439-encoding-and-decoding-in-swift
for your specific problem try this:
struct Info: Decodable {
let id: String
let name: String
let description: String
}
struct Dictionary: Decodable {
let data: [Info]
let page: Int
let size: Int
let count: Int
let status: Int
let isSuccess: Bool
}
let decoder = JSONDecoder()
let dictionary = try decoder.decode(Dictionary.self, from: json)

Related

Decoder not decoding json at keypath

Im trying to decode some JSON, but it's not parsing it. I think it may have something to to with either an incorrect KeyPath or the object itself. But I cannot figure it out.
This is the JSON that I want to decode (I want the array inside the docs path):
{
"status": 200,
"data": {
"docs": [
{
"_id": "60418a6ce349d03b9ae0669e",
"title": "Note title",
"date": "2015-03-25T00:00:00.000Z",
"body": "this is the body of my note.....",
"userEmail": "myemail#gmail.com"
}
],
"total": 1,
"limit": 20,
"page": 1,
"pages": 1
},
"message": "Notes succesfully Recieved"
}
Here's my decode function:
extension JSONDecoder {
func decode<T: Decodable>(_ type: T.Type, from data: Data, keyPath: String) throws -> T {
let toplevel = try JSONSerialization.jsonObject(with: data)
if let nestedJson = (toplevel as AnyObject).value(forKeyPath: keyPath) {
let nestedJsonData = try JSONSerialization.data(withJSONObject: nestedJson)
return try decode(type, from: nestedJsonData)
} else {
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Nested json not found for key path \"\(keyPath)\""))
}
}
}
And i'm calling it like this:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let notes = try decoder.decode([Note].self, from: data, keyPath: "data.docs")
Finally, this is my Note Struct:
struct Note: Codable {
var title: String?
let date: Date?
var body: String?
let userEmail: String?
}
The problem was that I was trying to decode date as a Date object instead of a String as is shown on the JSON.
Thanks #vadian!

Deserialization of JSON in swift 5 with JSONSerialization.jsonObject

I'm trying to deserialize a json object in swift. The JSON looks like this:
let data = """
{
"code": 200,
"message": "OK",
"results": [
{ "id": 111, "name": "Tony"},
{ "id": 112, "name": "Bill"},
{ "id": 112, "name": "John"}
]
}
""".data(using: .utf8)!
I'm using this to deserialize the JSON
var json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
print (json!["code"]!)
print (json!["message"]!)
print (json!["results"]!)
The correct values are printed in each case, but I can not figure out how to iterate across the value returned by
json!["reults"]
The error message is:
Type 'Any' does not conform to protocol 'Sequence'
Added- after first answer
First answer solves this problem. However, I'm following code from Apple Deveoper's site they do this:
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
for case let result in json["results"] {
if let restaurant = Restaurant(json: result) {
restaurants.append(restaurant)
}
}
And the result they pass in is a String, is this just an old example? Can I continue down this path?
You have to downcast the value of results to an array ([]) of dictionaries ({}). Then iterate the array
let results = json!["results"] as! [[String:Any]]
for item in results {
print(item["name"] as! String, item["id"] as! Int)
}
Side note: In Swift 4+ the Codable protocol is the better choice
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] ?? [:]
if let dictJson = json { //If json is not nil
if let arrResults = dictJson["results"] as? Array<Dictionary<String,Any>>{ //If results is not nil
for result in arrResults {
print(result) //Prints result
}
}
}
Try this. It will iterate result.

Decoding a nested JSON Swift

I have to decode this type of JSON that is downloaded by a response.
The JSON is this, I need retrieve the "gallery" of all items
JSON: https://pastebin.com/KnEwZzxd
I have tried many solution but I am not able to create a Decode of this son.
I have posted the full code on pastebin, too.
{
"status": 200,
"data": {
"date": "2018-07-29T00:00:00.300Z",
"featured": [
{
"id": "5b56298d781e197186378f50",
"name": "Sun Tan Specialist",
"price": "1,500",
"priceIcon": "vbucks",
"priceIconLink": "https://image.fnbr.co/price/icon_vbucks.png",
"images": {
"icon": "https://image.fnbr.co/outfit/5b56298d781e197186378f50/icon.png",
"png": "https://image.fnbr.co/outfit/5b56298d781e197186378f50/png.png",
"gallery": "https://image.fnbr.co/outfit/5b56298d781e197186378f50/gallery.jpg",
"featured": "https://image.fnbr.co/outfit/5b56298d781e197186378f50/featured.png"
},
"rarity": "epic",
"type": "outfit",
"readableType": "Outfit"
},
{
"id": "5b562af2781e19db65378f5c",
"name": "Rescue Paddle",
"price": "800",
"priceIcon": "vbucks",
"priceIconLink": "https://image.fnbr.co/price/icon_vbucks.png",
"images": {
"icon": "https://image.fnbr.co/pickaxe/5b562af2781e19db65378f5c/icon.png",
"png": "https://image.fnbr.co/pickaxe/5b562af2781e19db65378f5c/png.png",
"gallery": "https://image.fnbr.co/pickaxe/5b562af2781e19db65378f5c/gallery.jpg",
"featured": false
},
"rarity": "rare",
"type": "pickaxe",
"readableType": "Pickaxe"
}
],
"daily": [
{
"id": "5ab1723e5f957f27504aa502",
"name": "Rusty Rider",
"price": "1,200",
"priceIcon": "vbucks",
"priceIconLink": "https://image.fnbr.co/price/icon_vbucks.png",
"images": {
"icon": "https://image.fnbr.co/glider/5ab1723e5f957f27504aa502/icon.png",
"png": "https://image.fnbr.co/glider/5ab1723e5f957f27504aa502/png.png",
"gallery": "https://image.fnbr.co/glider/5ab1723e5f957f27504aa502/gallery.jpg",
"featured": "https://image.fnbr.co/glider/5ab1723e5f957f27504aa502/featured.png"
},
"rarity": "epic",
"type": "glider",
"readableType": "Glider"
},
{
"id": "5b0e944bdb94f1a4bbc0a8e4",
"name": "Rambunctious",
"price": "500",
"priceIcon": "vbucks",
"priceIconLink": "https://image.fnbr.co/price/icon_vbucks.png",
"images": {
"icon": "https://image.fnbr.co/emote/5b0e944bdb94f1a4bbc0a8e4/icon.png",
"png": "https://image.fnbr.co/emote/5b0e944bdb94f1a4bbc0a8e4/png.png",
"gallery": "https://image.fnbr.co/emote/5b0e944bdb94f1a4bbc0a8e4/gallery.jpg",
"featured": false
}
]
}
}
Aside from posting the JSON Code itself, it would be useful to actually show an attempt as to how you have attempted to decode it as well ^_________^.
Anyway, the best way to tackle this issue is to use custom Structs and the Decodable Protocol to handle the JSON response.
From your JSON you will initially get a two values:
/// The Initial Response From The Server
struct Response: Decodable {
let status: Int
let data: ResponseData
}
From this we then map the 'data' to a struct called ResponseData:
/// The Data Object
struct ResponseData: Decodable{
let date: String
let featured: [Product]
let daily: [Product]
}
In this we have two variables which contain an array of identical struct which I have called Product:
/// The Product Structure
struct Product: Decodable{
let id: String
let name: String
let price: String
let priceIcon: String
let priceIconLink: String
let images: ProductImages
let rarity: String
let type: String
let readableType: String
}
Within this we have one variable which is a dictionary (images) which we then map to another struct:
/// The Data From The Product Images Dictionary
struct ProductImages: Decodable{
let icon: String
let png: String
let gallery: String
///The Featured Variable For The Product Images Can Contain Either A String Or A Boolean Value
let featured: FeaturedType
}
The issue you have with the ProductImages, is that the featured var sometimes contains a String but on others it contains a Bool value. As such we need to create a custom struct to handle the decoding of this to ensure we always get a String (I am probably not doing this right way so if someone has a better solution please say so):
/// Featured Type Returns A String Of Either The Boolean Value Or The Link To The JPG
struct FeaturedType : Codable {
let formatted: String
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
//1. If We Get A Standard Response We Have A String
let stringResult = try container.decode(String.self)
formatted = stringResult
} catch {
//2. On Occassions We Get An Bool
let boolResult = try container.decode(Bool.self)
formatted = String(boolResult)
}
}
}
Now that is the basic structure of your JSON so now you need to handle it. In this example I am loading the JSON from the MainBundle as I dont have the actual URL.
/// Loads & Decodes The JSON File
func retreiveJSON(){
//1. Load The JSON File From The Main Bundle
guard let jsonURL = Bundle.main.url(forResource: "sample", withExtension: ".json") else { return }
do{
//2. Get The Data From The URL
let data = try Data(contentsOf: jsonURL)
//3. Decode The JSON
let jsonData = try JSONDecoder().decode(Response.self, from: data)
//4. Extract The Data
extractDataFrom(jsonData)
}catch{
print("Error Processing JSON == \(error)")
}
}
In the above function you will notice I am calling the function extractDataFrom() which allows you to then do what you need to do with your data:
/// Extracts The Daily & Featured Products From The JSON
///
/// - Parameter jsonData: Response
func extractDataFrom(_ jsonData: Response){
//1. Get The Daily Products
let dailyProducts = jsonData.data.daily
dailyProducts.forEach { (product) in
print(product.id)
print(product.name)
print(product.price)
print(product.priceIcon)
print(product.priceIconLink)
print(product.images)
print(product.rarity)
print(product.type)
print(product.readableType)
}
//2. Get The Featured Products
let featuredProducts = jsonData.data.featured
featuredProducts.forEach { (product) in
print(product.id)
print(product.name)
print(product.price)
print(product.priceIcon)
print(product.priceIconLink)
print(product.images)
print(product.rarity)
print(product.type)
print(product.readableType)
}
}
If you wanted to save this data then all you would need to do is add the following variables under your class declaration e.g:
var featuredProducts = [Product]()
var dailyProducts = [Product]()
And in the extractDataFrom() function change the:
let dailyProducts
let featuredProducts
To:
dailyProducts = jsonData.data.daily
featuredProducts = jsonData.data.featured
Please note that this is a very crude example, and as noted, I may not be handling the 'featured' variable correctly.
Hope it helps...
Thanks to quicktype and other services converting valid json to Swift and other languages is simple. Editing it to fit your needs should be simple enough.
// To parse the JSON, add this file to your project and do:
//
// let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
import Foundation
struct Welcome: Codable {
let status: Int
let data: DataClass
}
struct DataClass: Codable {
let date: String
let featured, daily: [Daily]
}
struct Daily: Codable {
let id, name, price, priceIcon: String
let priceIconLink: String
let images: Images
let rarity, type, readableType: String?
}
struct Images: Codable {
let icon, png, gallery: String
let featured: Featured
}
enum Featured: Codable {
case bool(Bool)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Featured.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Featured"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}

Decode Custom Json with Decodable

I have this Json:
{ "first": {
"house": [
"small"
]
}, "second": {
"house": [
"small"
] }, "third": {
"car": [
"fast",
"economic"
] }, "fourth": {
"car": [
"fast",
"economic"
] }, "fifth": {
"car": [
"fast",
"economic"
],
"ice": [
"round",
"tasty"
],
"tree": [
"big",
"small"
] } }
I tried to set up a structure with Decodable but I do not get it to work:
struct secondLayer: Codable {
let exchange: [String: [String]]
}
struct decodeJson: Codable {
let symbol: [String: [secondLayer]]
static func decode(jsonString: String) - [decodeJson] {
var output = [decodeJson]()
let decode = JSONDecoder()
do {
let json = jsonString.data(using: .utf8)
output = try! decode.decode([decodeJson].self, from: json!)
} catch {
print(error.localizedDescription)
}
return output
}
}
I get this Error:
Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.typeMismatch(Swift.Array<Any>,
Swift.DecodingError.Context(codingPath: [], debugDescription:
"Expected to decode Array<Any but found a dictionary instead.",
underlyingError: nil)): file
/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.74.1/src/swift/stdlib/public/core/ErrorType.swift,
line 181
I tried some modification but I do not get it to work.
The error message
"Expected to decode Array<Any> but found a dictionary instead."
is very clear. You want to decode an array ([decodeJson]) but the root object is a dictionary (starting with {)
Your code cannot work anyway. There are no keys exchange and symbol in the JSON.
Basically there are two ways to decode that JSON:
If all keys are dynamic you cannot decode the JSON to structs. You have to decode it to [String:[String:[String]]]. In this case Codable has no benefit over traditional JSONSerialization.
struct DecodeJson: Codable {
static func decode(jsonString: String) -> [String:[String:[String]]] {
var output = [String:[String:[String]]]()
let decoder = JSONDecoder()
do {
let json = Data(jsonString.utf8)
output = try decoder.decode([String:[String:[String]]].self, from: json)
print(output)
} catch {
print(error.localizedDescription)
}
return output
}
}
Or if the ordinal keys first, second etc are static use an umbrella struct
struct Root : Codable {
let first : [String:[String]]
let second : [String:[String]]
let third : [String:[String]]
let fourth : [String:[String]]
let fifth : [String:[String]]
}
struct DecodeJson {
static func decode(jsonString: String) -> Root? {
let decoder = JSONDecoder()
do {
let json = Data(jsonString.utf8)
let output = try decoder.decode(Root.self, from: json)
return output
} catch {
print(error.localizedDescription)
return nil
}
}
}
Of course you can decode house, car etc into a struct but this requires a custom initializer for each struct because you have to decode a single array manually with unkeyedContainer

How to get values nested within dictionary in NSDictionary?

I apologise if the title is unclear. I am not quite sure how exactly NSDictionary and JSON works.
I am trying to make a simple app using Swift that takes values from a .JSON file, and prints them out to the console. Here is my JSON file:
{
"students": [
{
"name": "Stew Dent",
"age": "10",
"year": 6,
"grades": [
{
"english": "a-plus",
"math": "b-plus"
},
{
"english": "a",
"math": "a-minus"
}
]
},
{
"name": "Mary Brown",
"age": "14",
"year": 10,
"grades": [
{
"english": "a-plus",
"math": "a-plus"
},
{
"english": "a-plus",
"math": "a"
}
]
}
]
}
Currently, I am able to retrieve the values of everything except the values in "grades". Here is my Swift code:
if let path = Bundle.main.path(forResource: "school", ofType: "json")
{
do {
// Retrieve data from JSON file
let jsonData = try NSData(contentsOfFile: path, options: .mappedIfSafe)
// Convert data into NSDictionary
let jsonResult = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
if let students : [NSDictionary] = jsonResult?["students"] as? [NSDictionary]
{
if let grades : [NSDictionary] = students["grades"] as? [NSDictionary]
{
for grade: NSDictionary in grades
{
for (subject, gradevalue) in grade
{
print ("\(subject): \(gradevalue)")
}
}
}
}
} catch
{
}
}
If I am not wrong, the "grades" is a dictionary inside the "students" dictionary. However, it fails to run the above code. On the "if let grades..." line, it gives the error message: "Cannot subscript a value of type '[NSDictionary]' with an index type of 'String'.
So what I am I doing wrong? Is there even any way to retrieve those values? Am I missing something? Any help would be greatly appreciated. Thanks :)
You should use Swift native types Data instead of NSData and Dictionary [String:Any] instead of NSDictionary. You also forgot to iterate through each student. Try like this:
do {
if let dict = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
print(dict)
if let students = dict["students"] as? [[String: Any]] {
print(students)
for student in students {
print(student["name"] ?? "")
if let grades = student["grades"] as? [[String: Any]] {
for grade in grades {
for (subject, gradevalue) in grade {
print ("student:",student["name"] ?? "", "subject:", subject, "grade:", gradevalue)
}
}
}
}
}
}
} catch {
print(error)
}
Try this code, this should work looking at what you are trying to do
if let path = Bundle.main.path(forResource: "student", ofType: "json")
{
do {
// Retrieve data from JSON file
let jsonData = try NSData(contentsOfFile: path, options: .mappedIfSafe)
// Convert data into NSDictionary
let jsonResult = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
if let students = jsonResult?["students"] as? [NSDictionary]
{
for student in students
{
for grades in student["grades"] as! [[String:String]]{
for eachGrade in grades{
print("\(eachGrade.key) : \(eachGrade.value)")
}
}
}
}
}
catch
{
}
}