read JSON result as array of dictionary - json

how to parse following JSON result
{
"AddedByName": "jhon",
"ApproveAction": 0,
"ApproveActionName": "",
"photos": null,
"Status": 0,
},
{
"AddedByName": "mike",
"ApproveAction": 0,
"ApproveActionName": "",
"photos": null,
"Status": 0,
},
{
"AddedByName": "someone",
"ApproveAction": 0,
"ApproveActionName": "",
"photos": [
{
"Id": 53,
"Serial": 1,
"Url": "0afe88a3-76e1-4bac-a392-173040936300.jpg"
}
],
"Status": 0,
}
how can i reach the "photos" array ?
I already declare local array of dictionary to hold the whole responses as following
var myLocalArray = [[String:Any]]()
and fill it from the JSON response like this
if let Json = response.result.value as? [String:Any] {
if let ActionData = Json["ActionData"] as? [[String:Any]] {
self. myLocalArray = ActionData
}
}
and it works
but i couldn't reach the "photos" array please help

I will give you smart solution than can be useful at each you try to transform you Json to useful data model and make it easier to manipulate .
Using the power of Decodable .
This Model will help you to capture you Json
struct User: Decodable {
var AddedByName: String
var ApproveAction: Int
var ApproveActionName: String
var photos: [Photo]?
var Status: Int
struct Photo: Decodable {
var Id: Int
var Serial: Int
var Url: String
}
}
And now will be just one line to get your Json to structure data:
let responseData = try JSONDecoder().decode([User].self, from: jsonD)

#swiftIos provided the answer with Decodable which is absolutely a better way to handle the situation.
But with your current you can access the photos from self.myLocalArray:
if let jsonData = response.result.value as? [String: Any] {
if let actionData = jsonData["ActionData"] as? [[String:Any]] {
self.myLocalArray = actionData
}
}
Now you have array for actionData, so access the photos by extracting the actionData for particular index as self.myLocalArray[0]. In whole:
let index = 0
if self.myLocalArray.count > index {
if let photoArrayIndex0 = self.myLocalArray[index]["photos"] as? [[String: Any]] {
print(photoArrayIndex0)
}
}

Related

Swift: How to decode a JSON with a dictionary of dictionaries with each key being an incrementing number value?

I am new to Swift and I was assigned a task to decode this JSON:
{
"data": {
"id": 1,
"elements": {
"E01": {
"title": "cars",
"items": ["honda", "toyota", "mercedes"],
"details": {
"id": 2,
"location": "toronto"
}
},
"E02": {
"title": "bagel types",
"items": ["plain", "grain", "toasted"],
}
}
}
}
I played around with this and I figured out how to decode this JSON when elements is an array of dictionaries with no incrementing key value. This is when elements looks like this:
"elements": [
{
"title": "cars",
"items": ["honda", "toyota", "mercedes"],
"details": {
"id": 2,
"location": "toronto"
}
},
{
"title": "bagel types",
"items": ["plain", "grain", "toasted"],
}
]
Here is my code on how to decode the JSON when elements look like the JSON directly above:
The classes I am decoding to:
public class StructureContainer: Codable {
let data: DataStructure
init(data: DataStructure) {
self.data = data
}
}
public class DataStructure: Codable {
let id: Int64?
let elements: [ElementStructure]?
init(id: Int64?, elements: [ElementStructure]?) {
self.id = id
self.elements = elements
}
}
public class ElementStructure: Codable {
let title: String?
let items: [String]?
let details: DetailStructure?
init(title: String?, items: [String]?, details: DetailStructure?) {
self.title = title
self.items = items
self.details = details
}
}
public class DetailStructure: Codable {
var id: Int64?
var location: String?
init(id: Int64?, location: String?) {
self.id = id
self.location = location
}
}
How I am decoding this:
func parseJSONLocally() {
do {
// JSONText is the variable that contains the JSON string
let jsonData = JSONText.data(using: .utf8)!
// dataInstance is the variable of type Structure that stores the parsed object.
dataInstance = try JSONDecoder().decode(StructureContainer.self, from: jsonData)
}
catch {
print("error", error)
}
}
I am able to successfully decode this when elements is an array of dictionaries with no incrementing key. However, I am lost on how to decode this when elements is a dictionary of dictionaries with each key incrementing.
I have found similar questions to this on StackOverflow but I don't think I have seen my exact problem. I am also new to Swift so I might be missing some knowledge somewhere. Any help is appreciated.
First of all Int64 makes no sense. On all modern computers Int64 is equal to Int. And declare only the properties as optional which can be nil
Assuming the dictionary keys don't matter implement init(from decoder to decode the dictionary version, decode [String:ElementStructure] and assign the dictionary values sorted by the keys to elements
public class DataStructure: Decodable {
let id: Int
let elements: [ElementStructure] // why not just Element?
private enum CodingKeys : String, CodingKey { case id, elements }
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
let elementData = try container.decode([String:ElementStructure].self, forKey: .elements)
let sortedkeys = elementData.keys.sorted{ $0.localizedStandardCompare($1) == .orderedAscending }
elements = sortedkeys.map{elementData[$0]!}
}
}

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!

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

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)

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

Am I converting JSON data to a Swift variable the long way?

I am reading json data from a file and trying to move it into a NSDictionary and I am wondering if there is a more concise way to do it. The code shown below works fine. In my program the Astruct-type variable has more than 25 Bstruct-type variables. I have several files similar to the one I show. So it leads to a lot of cumbersome programming.
import UIKit
struct Bstruct {
var label = String()
var inputTypeStr = String()
var list = [String]()
}
struct Astruct {
// in real situation this has a over 25 variables
// limited to 3 for this question
var number = Bstruct()
var customer = Bstruct()
var location = Bstruct()
}
func loadJson(forFilename fileName: String) -> NSDictionary? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
if let data = NSData(contentsOf: url) {
do {
let dictionary = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as? NSDictionary
return dictionary
} catch {
print("Error!! Unable to parse \(fileName).json")
}
}
print("Error!! Unable to load \(fileName).json")
}
return nil
}
func fillAstruct (value: NSDictionary) -> Bstruct {
return Bstruct(label: value["label"] as! String, inputTypeStr: value["inputTypeStr"] as! String, list: value["list"] as! [String])
}
var aVar = Astruct()
let json = loadJson(forFilename: "document")! as NSDictionary
let d = json["aTemplate"]! as! NSDictionary
print("d = \(d)")
// load d into aVar, appears to be complex particularly if the variable has a large number of variables in aVar and the json file
for d1 in d {
let key = String(describing: d1.0)
let value = d1.1 as! NSDictionary
switch key {
case "number":
aVar.number = fillAstruct(value: value)
case "customer":
aVar.customer = fillAstruct(value: value)
case "location":
aVar.location = fillAstruct(value: value)
default: break
}
}
print("\(aVar)")
The JSON file is shown below:
{
"aTemplate": {
"number": {
"label": "Number",
"inputTypeStr": "selection",
"list": [
"A",
"B",
"C"
]
},
"customer": {
"label": "Customer",
"inputTypeStr": "label",
"list": [
""
]
},
"location": {
"label": "location",
"inputTypeStr": "label",
"list": [
""
]
}
}
}