Cannot properly parse JSON Data because of its format? - json

I am trying to parse data which look:
It looks like each record is sequential.. 0, 1, 2 and then within each record there are lots of key value pairs such as the name or showID.
I want to go into each record and only get certain pairs, for example the name, showID and Date.
Here is my code, I am unsure what should be my modal in for item in loop
in other words, how do I get the specific fields into my empty dictionary array?
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data
{
do
{
var jsonResult:NSDictionary = try NSJSONSerialization.JSONObjectWithData(urlContent, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
if let items = jsonResult["items"] as! NSArray?
{
var emptyArrayOfDictionary = [[String : AnyObject]]()
for item in 0...jsonResult.count
{
}
}

The idea would be to create a struct (or a class) which contains the properties you need, created with an initializer from the values in your dictionaries.
Let's say you want to make "Show" objects containing the show name and the show ID.
You could create a struct like this:
struct Show {
let name:String
let showID:Int
init?(dictionary: [String:AnyObject]) {
guard let name = dictionary["name"] as? String,
let showID = dictionary["showID"] as? Int else {
return nil
}
self.name = name
self.showID = showID
}
}
Then iterate over your dictionaries and pass each one to the struct initializer, something like this:
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data {
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(urlContent, options: []) as? [String : AnyObject] {
if let items = jsonResult["items"] as? [[String : AnyObject]] {
let shows = items.flatMap { Show(dictionary: $0) }
}
}
} catch {
print(error)
}
}
}
The struct initializer is an Optional one, meaning that if the dictionary does not contain the keys "name" or "showID" it will not create the object and will return nil instead; that's why we're using flatMap to iterate instead of map (because flatMap unwraps the Optionals).
Now you have an array of objects, shows, and you can filter or sort its contents easily with Swift methods like sort, filter, etc.
Each object in the shows array is a Show object and has name and showID properties with the data of your dictionaries.
What flatMap does is create an array of Show objects by iterating (like a loop) over the initial array. On this line:
let shows = items.flatMap { Show(dictionary: $0) }
the $0 represents the current array element. What it means is that for each element in the items array, we take it and create a new Show instance with it, and put the resulting array of objects in the constant shows.
There's also map which is often used, but here the init of our Show struct is an optional init, so it returns an Optional Show, and flatMap knows how to deal with this (it will safely unwrap the optional and ignore the nil ones) where map does not.

If you would like to simplify your son parsing try this Open source https://github.com/SwiftyJSON/SwiftyJSON
With this you access name field of item 0
let userName = json[0]["name"].string

Related

JSON Parsing using Decodable protocol

I have json below for which I want to parse/assign values from
{
"Rooms":[
{
"id":"100",
"title":"CS Classroom",
"description":"Classroom for Computer science students",
"capacity":"50"
},
{
"id":"101",
"title":"Mechanical Lab",
"description":"Mechanical Lab work",
"capacity":"50"
},
{
"id":"108",
"title":"Computer Lab",
"description":"Computer Lab work",
"capacity":"50"
}
]
}
This json is of type [Dictionary: Dictonary] which has only key "Rooms"
While creating struct should I create
struct RoomsInfo: Decodable {
let rooms: Rooms
}
struct Rooms {
let id: String
let title: String
let description: String
let capacity: String
}
My 1st Question is: Since I have only Rooms key , Is there a possiblity to create just one struct instead of two ?
My 2nd Question is: What if my json has keys as "Rooms1", "Rooms2", "Rooms3", "Rooms4"... in this case can i create structure which confirms to decodable or do i need to parse it manually?
Please advice
For the first question, you have a key called Room so it has to decode that key,
is it possible to not have it sure, instead of parsing that JSON data first call out the value of that key JSON["Rooms"], and parse what inside as a [Room].self ,
For the second question if the count is unlimited, as if you don't know how much Room key count are going to be, the Decoder abilities are limited then, however you can always map out the values as Dictionary and then decode the values as Room without caring about the key, this trick will do but you will abandon the original Key.
Update for the second case:
Check out this code below.
typealias jsonDictionary = [String: Any]
let jsonData = json.data(using: .utf8)! // converting test json string to data
var arrayOfRooms: [Room] = []
do {
let serialized = try JSONSerialization.jsonObject(with: jsonData, options: []) // serializing jsonData to json object
if let objects = serialized as? [String: Any] { //casting to dictionary
for key in objects.keys { //looping into the keys rooms (n) number
let rooms = objects[key] // getting those rooms by key
let data = try JSONSerialization.data(withJSONObject: rooms!, options: []) //converting those objects to data again to parse
var myRoom = try! JSONDecoder().decode([Room].self, from: data) // decoding each array of rooms
arrayOfRooms.append(contentsOf: myRoom) // appending rooms to main rooms array declared on top
print("Data", data) // just to check
}
print("MY Array Of Rooms Count \(arrayOfRooms.count)")
} else {
print("nil")
}
} catch {
}
Answer #1: Yes, it's possible with nestedContainers but the effort is greater than the benefit.
Answer #2: Decode the dictionary as [String:Room] or use custom coding keys described in this answer

How to parse JSON data and eliminate duplicates in Swift?

I have a very large JSON file that I have downloaded from the web, and I need to parse this in Swift. The JSON construction is an array of dictionaries. Each dictionary object contains a key of "phone" (referring to the phone number), and whose value is the actual phone number in the form of a string.
What I would like to do, is iterate through the entire list of dictionary objects in the array, and ensure that there are no dictionary objects that have the same value for the key, "phone". If a duplicate is found, I would like to eliminate it from the list, and print it out to the console.
Here is the relevant code that I have:
guard let json = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
print("error")
return
}
for dict in json! {
//This is where I would do the check
}
How would I accomplish this?
You can do as
var ph = [String]()
var newjson = [[String:String]]()
for dict in json {
if ph.contains(dict["Phone"]!) {
print("duplicate phone \(dict["Phone"]!)")
} else {
ph.append(dict["Phone"]!)
newjson.append(dict)
}
}
print(newjson)
Hare newjson is the new array of dictionary that do not have duplicate phone
Use the array extension method to remove the duplicates from the json object
extension Array where Element: Equatable {
mutating func removeDuplicates() {
var result = [Element]()
for value in self {
if !result.contains(value) {
result.append(value)
}
}
self = result
}
}
Alamofire.request(apiURL, method: .get, parameters:parameters, headers:headers)
.responseJSON { response in
if let result = response.result.value {
let json = JSON(result)
var listArray = json["somekey"].arrayValue
listArray.removeDuplicates()
print(listArray)
}
}

Dynamically Generate UITableView Cells and Headrs

Project files:
https://jumpshare.com/v/Otai3BBXYwfvyz8jb53k
(Would be wise to view these to see structure of project)
Problem:
Ok, so i'm following a tutorial that creates a UITableView with headers and then cell content.
The code worked and runs fine, Now I want to extend beyond that tutorial and dynamically load that content using alamofire and SwiftyJSON.
In the tutorial, the code used is like so:
func getSectionsFromData() -> [Sections] {
var sectionsArray = [Sections]()
let animals = Sections(title: "Animals", objects: ["Cats", "Dogs", "Birds", "Lions"])
sectionsArray.append(animals)
return sectionsArray
}
What I tried to do was:
Alamofire.request(.GET, url).validate().responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
for (_, subJson) in json {
for (year, content) in subJson {
let title = year
let objects = content
sectionsArray.append(Sections(title: title, objects: objects))
}
}
}
case .Failure(let error):
print(error)
}
}
If I print out the results they show in the console - so I know the getting and looping of the JSON works. I then added in
let title = year
let objects = content
sectionsArray.append(Sections(title: title, objects: objects))
But on this line:
sectionsArray.append(Sections(title: title, objects: objects))
I get this error:
cannot convert value of type 'JSON' to expected argument type '[String]'
Here is the JSON I am using:
{"posts": [
{
"Category1": [
"Post1cat1"
],
"Category2": [
"Post1cat2",
"Post2cat2"
]
}
]}
Can someone help me? I might be going in the wrong direction here I want to loop through the JSON and display the categories as headers and the posts in a cell of a table.
edit: 1/29/2016
so, I changed the loop to:
for (_, subJson) in json {
for (index, data) in subJson {
for (title, objects) in data {
sectionsArray.append(Sections(title: title, objects: objects.self.arrayValue.map { $0.string!}))
}
}
}
Still no luck. When I add in some prints (under: sectionsArray.append) to test if there is data:
print("--")
print(title)
print(objects.self.arrayValue.map { $0.string!})
print(Sections(title: title, objects: objects.self.arrayValue.map { $0.string!}))
I get this result in the console:
--
Category1
["Post1cat1"]
Sections(headings: "Category1", items: ["Post1cat1"])
--
Category2
["Post1cat2", "Post2cat2"]
Sections(headings: "Category2", items: ["Post1cat2", "Post2cat2"])
Which shows that the information is there, however when I run the app there are still no results form he JSON just the originally defined section and cells above.
In second parsing method (after edit), you're iterating on array in last loop, so either you could create array there and add each element separately, as in example:
for (title, data) in subJson {
var elements: [String] = []
for (_, object) in data {
if let stringELement = object.rawString() {
elements.append(stringELement)
}
}
sectionsArray.append(Sections(title: title, objects: elements))
}
or if you prefer you can use casted raw array from JSON object as in this example:
for (_, subJson) in json["posts"] {
for (title, data) in subJson {
let optionalCastedObjects = data.arrayObject as? [String]
let unwrappedObjects = optionalCastedObjects ?? []
let section = Sections(title: title, objects: unwrappedObjects)
sectionsArray.append(section)
}
}
That should fix mentioned compilation issue.
But in the end remember that you're using async callback (in your GET request) in synchronous getSectionsFromData method. And you're always will return array before the values from that callback (clojure) will append new data. That will cause, that you're never display the data that you fetched that way.
UPDATE
To do that you should refactor your getSectionsFromData method as below.
func getSectionsFromData(completion: ([Sections]) -> ()) {
var sectionsArray = [Sections]()
Alamofire.request(.GET, url).validate().responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
for (_, subJson) in json["posts"] {
for (title, data) in subJson {
let optionalCastedObjects = data.arrayObject as? [String]
let unwrappedObjects = optionalCastedObjects ?? []
let section = Sections(title: title, objects: unwrappedObjects)
sectionsArray.append(section)
}
}
completion(sectionsArray)
}
case .Failure(let error):
print(error)
}
}
}
And relevant parts in your UITableViewController class.
var sections: [Sections] = []
override func viewDidLoad() {
super.viewDidLoad()
SectionsData().getSectionsFromData { [weak self](sections: [Sections]) -> () in
self?.sections = sections
self?.tableView.reloadData()
}
}
Your second loop is on the array object hence in that loop year is index value and content is the object at that index.
You need to implement an additional loop to fix the problem i.e:
for (_, subJson) in json {
for (index, data) in subJson {
for (title, objects) in data {
sectionsArray.append(Sections(title: title, objects: objects.arrayValue.map { $0.string!}))
}
}
}
From the SwiftyJSON documentation:
for (key,subJson):(String, JSON) in json {
//Do something you want
}
This indicates that subJson is of type JSON. However, your Sections constructor in the first example is:
Sections(title: "Animals", objects: ["Cats", "Dogs", "Birds", "Lions"])
In your second example, you're calling it as:
Sections(title: title, objects: objects)
Unless you have changed the constructor, it's expecting objects to be an array of strings, not JSON. This is why you're getting an error saying Swift can't convert JSON to String. In your case, the objects JSON is actually an array of strings, so you need to use something like:
Sections(title: title, objects: objects.arrayValue.map { $0.string!})

JSON with Swift 2, Array Structure

I'm Having trouble with JSON and Swift 2.
I'm getting this Array from the server
[{"KidName":"Jacob","KidId":1,"GardenID":0},
{"KidName":"Sarah","KidId":2,"GardenID":0},
{"KidName":"Odel","KidId":3,"GardenID":0}]
I'm familiar with JSON and I know it's not the recommended way to get a JSON, since it's supposed to be something like
{"someArray":[{"KidName":"Jacob","KidId":1,"gardenID":0}, .....
So my first question is it possible to run over the first JSON I've post and get the KidName number without editing the JSON and Add to it a JSON OBJECT to hold the array ?
my second question is really with Swift 2, how can I get the KidName (after I've edited the JSON to have an holder for the array)?
this is my code... (please read the Notes I've added)
BTW, I'm familiar with SwiftyJSON as well...
// Method I've build to get the JSON from Server, the Data is the JSON
sendGetRequest { (response, data ) -> Void in
// need to convert data to String So I can add it an holder
if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String {
/**
after editing the str, i'm Having a valid JSON, let's call it fixedJSON
*/
let fixedJSON = "{\"kidsArray\":\(dropLast)}"
// Now I'm converting it to data back again
let jsonTodata = fixedJSON.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// After Having the data, I need to convert it to JSON Format
do{
let dataToJson = try NSJSONSerialization.JSONObjectWithData(jsonTodata, options: []) as! [String:AnyObject]
//Here I'm getting the KidID
if let kidID = jsonSe["kidsArray"]![0]["KidId"]!!.integerValue {
print("kidID in first index is: \(kidID)\n")
}
//NOW trying to get the KidName which not working
if let kidname = jsonSe["kidsArray"]![0]["KidName"]!!.stringValue {
print("KidName is \(kidname)\n")
}
}
So as you can see, I'm not able to get the KidName.
Any Help Would be Appreciate.
You can use the following function to get the 'someArray' array and then use this getStringFromJSON function to get the 'KidName' value.
func getArrayFromJSON(data: NSDictionary, key: String) -> NSArray {
if let info = data[key] as? NSArray {
return info
}
else {
return []
}
}
let someArray = self.getArrayFromJSON(YourJSONArray as! NSDictionary, key: "someArray")
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String {
return info
}
return ""
}
let KidName = self.getStringFromJSON(someArray as! NSDictionary, key: "KidName")
Hope this might be useful to you.

Swift - Reading JSON File

I'm new to Swift - trying to read a JSON file from a URL. My attempt below.
The JSON looks valid - I tested it with JSONLint but it keeps crashing.
Thoughts?
func getRemoteJsonFile() -> NSDictionary {
//Create a new url
let remoteUrl:NSURL? = NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")
//check if its nil
if let actualRemoteUrl = remoteUrl {
//try to get the data
let filedata:NSData? = NSData(contentsOfURL: actualRemoteUrl)
//check if its nil
if let actualFileData = filedata {
//parse out the dictionaries
let jsonDict = NSJSONSerialization.JSONObjectWithData(actualFileData, options: NSJSONReadingOptions.AllowFragments, error: nil) as NSDictionary
return jsonDict
}
}
return NSDictionary()
}
This took me a second to figure out, so I don't blame you for missing it.
The JSON you linked to is minified, so it's difficult to see the structure. Let's take a look at (a fragment of) it after piping it through a prettifier:
[
{
"PlayerId":2501863,
"PlayerName":"Peyton Manning",
"PlayerTeam":"DEN",
"PlayerPosition":"QB",
"PlayerPassingYards":4727,
"PlayerPassingTDs":39,
"PlayerInterceptions":15,
"PlayerRushingYards":-24,
"PlayerRushingTDs":0,
"PlayerReceivingYards":0,
"PlayerReceivingTDs":0,
"PlayerReturnYards":0,
"PlayerReturnTDs":0,
"PlayerFumbleTDs":0,
"PlayerTwoPointConversions":2,
"PlayerFumblesLost":2,
"PlayerTeamLogo":"http://i.nflcdn.com/static/site/7.0/img/logos/teams-gloss-81x54/den.png"
}
]
Huh. It's encased in brackets, which means that it's an array.
It's an array, so you can't cast it as an NSDictionary. Instead, you could cast it as an NSArray, but why not use native Swift types?
Well, if you don't like types, you're about to find out, but I still think that this is a better way, because it forces you to think about the data you're parsing.
So we have the first part of our type definition for this function; it's an array ([]). What components is our array made up of? We could go with a simple NSDictionary, but we're doing full native types here, so let's use a native Swift dictionary.
To do that, we have to know the types of the dictionary (the syntax for a native dictionary type is [KeyType: ValueType]). Examining the JSON shows that all of the keys are Strings, but the values are of varying types, so we can use AnyObject.
That gives us a dictionary type of [String: AnyObject], and our entire JSON is an array of that, so the final type is [[String: AnyObject]] (wow).
Now that we have the proper type, we can modify the function you're using to parse the JSON a bit.
First of all, let's use our new type for the return and cast values. Then, let's make the return type optional in case something goes wrong and add an error variable to document that.
A cleaned up function would look something like this:
func getData() -> [[String: AnyObject]]? {
let data: NSData? = NSData(contentsOfURL: NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")!)
if let req: NSData = data {
var error: NSError?
if let JSON: [[String: AnyObject]] = NSJSONSerialization.JSONObjectWithData(req, options: NSJSONReadingOptions.AllowFragments, error: &error) as? [[String: AnyObject]] {
return JSON
}
}
return nil
}
That's it!
We can now call the function and extract values from our [[String: AnyObject]] (again, wow) like this:
if let data: [[String: AnyObject]] = getData() {
println(data[0]["PlayerName"]!) // Peyton Manning
}
Update your code with this:
func getRemoteJsonFile() -> [NSDictionary] {
// Create a new URL
let remoteUrl:NSURL? = NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")
let urlString:String = "\(remoteUrl)"
// Check if it's nil
if let actualRemoteUrl = remoteUrl {
// Try to get the data
let fileData:NSData? = NSData(contentsOfURL: actualRemoteUrl)
// Check if it's nil
if let actualFileData = fileData {
// Parse out the dictionaries
let arrayOfDictionaries:[NSDictionary]? = NSJSONSerialization.JSONObjectWithData(actualFileData, options: NSJSONReadingOptions.MutableContainers, error: nil) as [NSDictionary]?
if let actualArrayOfDictionaries = arrayOfDictionaries {
// Successfully parsed out array of dictionaries
return actualArrayOfDictionaries
}
}
}
return [NSDictionary]()
}
This is working fine for me.