Multidimensional array makes Xcode6 crash - swift

I have an application which retrieves a JSON file. Here you are a piece of my code:
Array definition
var photos: NSArray = []
How I populate the array:
ezJson().createRequest("http://myapiurl/load", type: "GET", params: nil, completion: {(returnedObject : AnyObject?, error : NSError?)in
if returnedObject{
self.photos = returnedObject as NSArray
self.tableView.reloadData()
}
})
println(self.photos)
{
created = {
date = "2014-06-13 18:35:46";
timezone = "Europe/Madrid";
"timezone_type" = 3;
};
description = description1;
id = 3;
name = 539b286277617;
},
{
created = {
date = "2014-06-13 18:38:38";
timezone = "Europe/Madrid";
"timezone_type" = 3;
};
description = description2;
id = 4;
name = 539b290ed8577;
}
println(self.photos[0])
{
created = {
date = "2014-06-13 18:35:46";
timezone = "Europe/Madrid";
"timezone_type" = 3;
};
description = description1;
id = 3;
name = 539b286277617;
}
The problem is, I don't know how to get a particular item. I've tried:
println(self.photos[0]) // it works
println(self.photos[0]["name"] // Xcode crash "Command /Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift failed with exit code 254"
println(self.photos[0].name) // returns nil
How can I access to the name parameter ?

It seems that you are casting a String to NSArray. This won't give you the effect that you want.
First of all, if you want to acces your elements by name, you want an NSDictionary.
Then it propably still won't work, as there is no implicit conversion between the types so you will have to either parse it yourself or use some JSON library.
Last : your JSON is incorrect.

Related

Complex Dictionary Get Values as Strings With Conditions

I have built a survey using laanlabs/SwiftSurvey and I am able to get the results from the surveyComplete method as a dictionary.
The results return in a complex structure and I need to get the values for each response by the key tag of the questions array -> question object. This object contains an array of choices and within each choice object there is a key of selected. If the selected key's value is true (1) I need to get the text key's value that is in the same object. Some of these choices will have multiple selected keys with a value of true (1), if this is the case I'd like to concatenate the text key values with a comma in between the values.
The intention is then to insert the keys in to a SQLite database.
I am new to decoding dictionaries and traversing them in the correct way, I can access the dictionary print(dictionary) and also get into the the correct NSArray - print(dictionary["questions"] but from there I am stumped, could someone show me how please.
The results are below unfortunately its a large block apologies.
[
"version": 001,
"metadata":
{
"app_version" = "1.1";
build = 22;
debug = true;
},
"questions": <__NSArrayI 0x600000614d20>(
{
question = {
allowsMultipleSelection = 0;
choices = (
{
allowsCustomTextEntry = 0;
selected = 1;
text = Physician;
uuid = "224E1B76-D220-4068-AA22-6861E5F836CB";
},
{
allowsCustomTextEntry = 0;
selected = 0;
text = Dietitian;
uuid = "2DB2B6FB-E344-4BBF-A551-2FABE0DFF6AA";
},
{
allowsCustomTextEntry = 0;
selected = 0;
text = "Genetic Counsellor";
uuid = "A9BE7093-B95C-4BF4-B629-12FDA3154ABE";
},
{
allowsCustomTextEntry = 0;
selected = 0;
text = "Nurse/Nurse Practitioner/Physician Assistant";
uuid = "8E75A41B-0D8C-4ADA-A31C-2BC408F8269D";
},
{
allowsCustomTextEntry = 0;
selected = 0;
text = "Pharmacist / Pharmaceutical Industry";
uuid = "C943430D-EA48-4BCB-8ADF-011A223BDF36";
},
{
allowsCustomTextEntry = 0;
selected = 0;
text = "Academic/Researcher";
uuid = "E28377A4-37FC-4351-A857-88383A3D5A3B";
},
{
allowsCustomTextEntry = 0;
selected = 0;
text = "Patient/Patient Advocacy Group";
uuid = "E5836187-6C08-4272-A88E-40578F4FCF44";
},
{
allowsCustomTextEntry = 1;
selected = 0;
text = "Other (please specify)";
uuid = "EFF22342-48A9-4B8E-81A0-BB44D0E86EBC";
}
);
required = 1;
tag = "hcp-specialty";
title = "Please select the option that best describes your specialty:";
uuid = "7F77E248-8429-463E-9291-241B94BEE4F8";
};
type = 0;
},
{
question = {
autoAdvanceOnChoice = 1;
choices = (
{
allowsCustomTextEntry = 0;
selected = 1;
text = Yes;
uuid = "3C7A330D-F16B-4F3E-8ABC-6767A1A6332A";
},
{
allowsCustomTextEntry = 0;
selected = 0;
text = No;
uuid = "0E4F5360-FCCD-4860-9971-86E23BB8F6C1";
}
);
required = 1;
tag = "newborn-screening";
title = "Is newborn screening for classical homocystinuria available in your region/country?";
uuid = "F7C1A9D5-43AB-420D-80CF-F6644B95C73E";
};
type = 1;
},
{
question = {
feedback = "This is a free text response";
required = 1;
tag = "biggest-unmet-need";
title = "What do you believe is the biggest unmet need for patients living with classical HCU?";
uuid = "133E2EDC-8FF4-48D1-8BFA-3A20E5DA0052";
};
type = 4;
}
)
]
Based on their result example, you are getting JSON. Converting JSON to Dictionary is the easiest, of course, but also is the dirtiest. You are basically getting "whatever", so when it's time to use the data, you have to do a lot of - as you said - "decoding", validation, etc.
Instead of that, create a few Decodable structures that match your response. In this case you just need 3 structures:
struct Survey: Codable {
let questions: [Question]
}
struct Question: Codable {
let allowsMultipleSelection: Int?
let choices: [Choice]?
let required: Int
let tag: String
let title: String
let uuid: String
let feedback: String?
}
struct Choice: Codable {
let allowsCustomTextEntry: Int
let selected: Int
let text: String
let uuid: String
}
(I didn't verify every fields, you can adjust as needed. And you can omit any properties you don't need / don't care about.)
And then you decode it like this:
// Assume jsonData is your original JSON, which you currently decode as dictionary. So instead you do this:
let decoder = JSONDecoder()
let product = try decoder.decode(Survey.self, from: jsonData)
This approach allows you to
Most importantly, having a well-defined data makes working with database easier. You can even implement database encoder, based on your codable structures, which means you don't need to manually walk through columns of the database. Also when. you read from database, you get the same structures, no need to have 2 sets of rules / validations for database and dictionary you decoded.
This also allows you to be confident about data you decoded: it has proper names and types. You don't need to validate it (it was validated for you on decoding). You are in control which properties are required, which defaults to set, and so on. In more complicated cases, you may need to add manual decoding to your structures, but even then this manual decoding is inside the structure itself, easy to change / work with / test. All this instead of giant messy dictionary with "some stuff" in it.

How to identify the type of an object?

Here is my JSON response for a particular API.
Case 1
ChallengeConfiguration = {
AnswerAttemptsAllowed = 0;
ApplicantChallengeId = 872934636;
ApplicantId = 30320480;
CorrectAnswersNeeded = 0;
MultiChoiceQuestion = (
{
FullQuestionText = "From the following list, select one of your current or previous employers.";
QuestionId = 35666244;
SequenceNumber = 1;
},
{
FullQuestionText = "What color is/was your 2010 Pontiac Grand Prix?";
QuestionId = 35666246;
SequenceNumber = 2;
}
)
}
The key "MultiChoiceQuestion" returns an array with two questions. So here is my code.
let QuestionArray:NSArray = dict1.objectForKey("ChallengeConfiguration")?.objectForKey("MultiChoiceQuestion") as! NSArray
Case 2
ChallengeConfiguration =
{
AnswerAttemptsAllowed = 0;
ApplicantChallengeId = 872934636;
ApplicantId = 30320480;
CorrectAnswersNeeded = 0;
MultiChoiceQuestion = {
FullQuestionText = "From the following list, select one of your
current or previous employers.";
QuestionId = 35666244;
SequenceNumber = 1;
}
}
For Case 2 my code does not work and app crashes because it returns a dictionary for that specific Key. So how could I write a generic code that would work for all objects?
It looks like the key can contain either an array of dictionary values or a dictionary, so you just need to try casting to see which one you have.
so I would likely do it like this:
if let arr = dict1.objectForKey("ChallengeConfiguration")?.objectForKey("MultiChoiceQuestion") as? Array {
// parse multiple items as an array
} else if let arr = dict1.objectForKey("ChallengeConfiguration")?.objectForKey("MultiChoiceQuestion") as? [String:AnyObject] {
// parse single item from dictionary
}
You should never really use ! to force unwrap something unless you are completely certain that the value exists and is of the type you are expecting.
Use conditional logic here to test the response and parse it safely so that your app doesn't crash, even in failure.

Swift 3: Iterate through a _NSSingleObjectArrayI

I'm fetching data from a weather API. I'm not sure how to access the description?
"weather": <__NSSingleObjectArrayI 0x608000012910>(
{
description = "overcast clouds";
icon = 04n;
id = 804;
main = Clouds;
}
)
I tried:
print(weatherDict["weather"]!.description!)
It just gave me this:
(
{
description = "overcast clouds";
icon = 04n;
id = 804;
main = Clouds;
}
)
How do I properly access the description?
weather contains an array of dictionaries.
description is a key in the first item of the array.
The code unwraps weather safely and checks if the array is not empty:
if let weatherArray = weatherDict["weather"] as? [[String:Any]],
let weather = weatherArray.first {
print(weather["description"]) // the value is an optional.
}

Swift, dictionary parse error

I'm using an API to get weather condition and the retrieved dict is
dict = {
base = stations;
clouds = {
all = 92;
};
cod = 200;
coord = {
lat = "31.23";
lon = "121.47";
};
dt = 1476853699;
id = 1796231;
main = {
"grnd_level" = "1028.63";
humidity = 93;
pressure = "1028.63";
"sea_level" = "1029.5";
temp = "73.38";
"temp_max" = "73.38";
"temp_min" = "73.38";
};
name = "Shanghai Shi";
rain = {
3h = "0.665";
};
sys = {
country = CN;
message = "0.0125";
sunrise = 1476827992;
sunset = 1476868662;
};
weather = (
{
description = "light rain";
icon = 10d;
id = 500;
main = Rain;
}
);
wind = {
deg = "84.50239999999999";
speed = "5.97";
};
}
If I want the value of humidity, I just use
let humidityValue = dict["main"]["humidity"] and it works.
But the problem is I also want to get the value of description in weather
when I used let dscptValue = dict["weather"]["description"]
it retrieved nil.
How's that? and I notice there are two brackets around weather .I'm not sure whether it is the same with the statement without brackets.
weather = (
{
description = "light rain";
icon = 10d;
id = 500;
main = Rain;
}
);
How to get the value of description?
weather keys contains Array of Dictionary not directly Dictionary, so you need to access the first object of it.
if let weather = dict["weather"] as? [[String: AnyObject]], let weatherDict = weather.first {
let dscptValue = weatherDict["description"]
}
Note: I have used optional wrapping with if let for preventing crash with forced wrapping.
Weather is an array of dictionaries.
dict["weather"][0]["description"]
may give you the expected result.

parsing twitter search json data using NSJSONSerialization

I am parsing twiter search api json data with NSJSONSerialization.Requirement is to search tweets by hashtag.In Twitter api console tool I am correctly getting data about 15 tweets.
written code is
if let results: NSDictionary = NSJSONSerialization .JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments , error: errorPointer) as? NSDictionary {
}
I am getting results value as
{
"search_metadata" = {
"completed_in" = "0.05";
count = 15;
"max_id" = 680240431771156480;
"max_id_str" = 680240431771156480;
"next_results" = "?max_id=680240407322689535&q=%23ChristmasEve&include_entities=1";
query = "%23ChristmasEve";
"refresh_url" = "?since_id=680240431771156480&q=%23ChristmasEve&include_entities=1";
"since_id" = 0;
"since_id_str" = 0;
};
statuses = (
{
contributors = "<null>";
coordinates = "<null>";
"created_at" = "Fri Dec 25 04:15:31 +0000 2015";
entities = {
hashtags = (
{
indices = (
0,
13
);
text = ChristmasEve;
},
{
which is incomplete.
I even tried using SwiftyJSon library but I am getting similar results.
Is there any way to get statuses/Tweet info value without using any external library?
Given the fact that you mentioned you are getting multiple tweets (15), the JSON data you're getting back from the API possibly is an array, not a dictionary. It's a good practice to handle both cases when you make network calls:
do {
let object = try NSJSONSerialization.JSONObjectWithData(data, options: [])
if let dictionary = object as? [NSObject: AnyObject] {
// Handle dictionary
} else if let array = object as? [[NSObject: AnyObject]] {
// Handle array
}
} catch {
}