Get JSON Element in Swift 3 - json

Please excuse me if this is a simple question, but I am stuck. I have tried to read everything I can to work it out myself.
I am trying to extract a URL from JSON data, I get the JSON data fine and I can print it to the console, however I can't work out how to access the URL for the audio file.
This is the code I use to get the JSON:
let session = URLSession.shared
_ = session.dataTask(with: request, completionHandler: { data, response, error in
if let response = response,
let data = data,
let jsonData = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) {
if let dictionary = jsonData as? [String: Any] {
if let prounce = dictionary["pronunciations"] as? [String: Any]{
if let audioPath = prounce["audioFile"] as? String {
print(audioPath)
}
}
}
print(response)
print(jsonData)
} else {
print(error)
print(NSString.init(data: data!, encoding: String.Encoding.utf8.rawValue))
}
}).resume()
The output I get is:
metadata = {
provider = "Oxford University Press";
};
results = (
{
id = maladroit;
language = en;
lexicalEntries = (
{
entries = (
{
etymologies = (
"late 17th century: French"
);
grammaticalFeatures = (
{
text = Positive;
type = Degree;
}
);
senses = (
{
definitions = (
"inefficient or inept; clumsy:"
);
examples = (
{
text = "both men are unhappy about the maladroit way the matter has been handled";
}
);
id = "m_en_gb0494140.001";
}
);
}
);
language = en;
lexicalCategory = Adjective;
pronunciations = (
{
audioFile = "http://audio.oxforddictionaries.com/en/mp3/maladroit_gb_1.mp3";
dialects = (
"British English"
);
phoneticNotation = IPA;
phoneticSpelling = "\U02ccmal\U0259\U02c8dr\U0254\U026at";
}
);
text = maladroit;
}
);
type = headword;
word = maladroit;
}
);
}
I want to get the URL called audioFile in the pronunciations. Any help is much appreciated.

If my guess is right, your output shown above lacks opening brace { at the top of the output.
(I'm also assuming the output is taken from your print(jsonData).)
Your jsonData is a Dictionary containing two values:
A dictionary value for "metadata"
An array value for "results"
So, you cannot retrieve a value for "pronunciations" directly from jsonData (or dictionary).
You may need to:
Retrieve the value for "results" from jsonData, it's an Array
Choose one element from the "results", it's a Dictionary
Retrieve the value for "lexicalEntries" from the result, it's an Array
Choose one element from the "lexicalEntries", it's a Dictionary
Retrieve the value for "pronunciations" from the lexicalEntry, it's an Array
Choose one element from the "pronunciations", it's a Dictionary
Here, you can access the values in each pronunciation Dictionary. In code, you need to do something like this:
if
let dictionary = jsonData as? [String: Any],
let results = dictionary["results"] as? [[String: Any]],
//You need to choose one from "results"
!results.isEmpty, case let result = results[0],
let lexicalEntries = result["lexicalEntries"] as? [[String: Any]],
//You need to choose one from "lexicalEntries"
!lexicalEntries.isEmpty, case let lexicalEntry = lexicalEntries[0],
let pronunciations = lexicalEntry["pronunciations"] as? [[String: Any]],
//You need to choose one from "lexicalEntries"
!pronunciations.isEmpty, case let pronunciation = pronunciations[0]
{
//Here you can use `pronunciation` as a Dictionary containing "audioFile" and some others...
if let audioPath = pronunciation["audioFile"] as? String {
print(audioPath)
}
}
(You can use let result = results.first instead of !results.isEmpty, case let result = results[0], if you always use the first element for arrays. Other two lines starting from !...isEmpty, case let... as well.)
You need to dig into the target element from the outermost element step by step.

Related

Parse JSON Data in Swift from API Call "Value of type 'Any?' has no subscripts"

I am trying to parse some JSON data returned from an API call. The path I want to navigate is media > faces > tags. When I navigate to media it works, but when I add faces i receive the "Value of type 'Any?' has no subscripts" error.
I know this has been covered before on Stackoverflow but I can't seem to find out what is wrong. Any help is appreciated!
let dictionary = try JSONSerialization.jsonObject(with: data!, options:.mutableContainers) as? [String:Any];
// print(dictionary!["media"]) //this line works
print(dictionary!["media"]["faces"]) // this line does not work
The API returned data looks something like this
Optional({
checksum = 44efb3256385bfe62425c5fde195a3352e814ff6d900058e47a07b2cd7654252;
duration = "00:00:00";
faces = (
{
angle = "1.2222";
"appearance_id" = 0;
"detection_score" = 1;
duration = "00:00:00";
"face_uuid" = "78338d20-9ced-11ea-b153-0cc47a6c4dbd";
height = "74.31999999999999";
"media_uuid" = "fec83ac3-de00-44f0-ad5b-e1e990a29a8c";
"person_id" = "";
points = (
{
name = "basic eye left";
type = 512;
x = "85.16";
y = "86.62";
},
You need to cast the value returned from dictionary!["media"] as another Dictionary to be able to do this. Try this:
if let data = data,
let dictionary = try JSONSerialization.jsonObject(with: data, options:.mutableContainers) as? [String: Any],
let media = dictionary["media"] as? [String: Any] {
print(media["faces"])
}

Accessing JSON data with Swift

I have an array of JSON data from the following call:
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [Any] else {
print("Not containing JSON")
return
}
when I run print(json) I get the following in the output:
[{
"CREATED_BY" = "DOMAIN\\USER";
"CREATED_DATE" = "2016-11-28T08:43:59";
STATUS = U;
"WIDGET_NUMBER" = K11;
"UPDATED_BY" = "<null>";
"UPDATED_DATE" = "<null>";
}, {
"CREATED_BY" = "DOMAIN\\USER";
"CREATED_DATE" = "2016-05-09T08:46:23";
STATUS = U;
"WIDGET_NUMBER" = 89704;
"UPDATED_BY" = "<null>";
"UPDATED_DATE" = "<null>";
}]
I am trying to get all of the WIDGETNUMBER values in the array of JSON data. The json variable is a Any type and I have not been able to convert to a struct so far. Is there an easy way to get the elements from the JSON objects?
It looks like you have an array of dictionaries
for item in json {
if let item = item as? [String: Any], let widgetNo = item["WIDGET_NUMBER"] {
print(widgetNo)
}
}
Your content is array of Dictionary, so that you must convert each element Dictionary to Json
for dic in content {
do {
let jsonData = try JSONSerialization.data(withJSONObject: dic, options: .prettyPrinted)
print(jsonData)
} catch {
print(error.localizedDescription)
}
}
Or you can read value of WIDGET_NUMBER direct from Dictionary
for dic in content {
print(dic["WIDGET_NUMBER"] ?? "Not found")
}
Joakim's answer is spot on for getting the widget number. For your struct, be sure to add something like this as an initializer to map your object.
let widgetNumber: Int
let user: String
init?(json:[String:Any]) {
guard let widgetNumber = json["WIDGET_NUMBER"] as? Int,
let user = json["CREATED_BY"] as? String else { return nil }
self.widgetNumber = widgetNumber
self.user = user
}
If you just want an array of widget numbers you could use the reduce function which iterates the dictionaries in the array and extracts the widget numbers:
Using your data I put this in a storyboard:
let json = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as! [[String: Any]]
let widgetNumbers = json?.reduce(into: [String]()){ (accum, dict) in
guard let widget = dict["WIDGET_NUMBER"] as? String else { return }
accum.append(widget)
}
widgetNumbers // -> ["K11", "89704"]

Why does my JSON request fail more than 50% of the time? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I am making a spelling bee game that gets words from Oxford Dictionary's API. The app delivers words expected, but its failure rate is higher than I want. I'm getting about a 25 to 36 percent success rate; most words deliver a nil result but that's obviously my fault, not the API's. I want a higher success rate because the API is not free. Or is this kind of hit-rate typical for major APIs?
I know that I need to change this over to Swift 4's Decodable for JSON but I haven't gotten around to figuring that one out yet.
Can you find anything in my code that would cause perfectly simple words to be skipped, such as one-syllable words that would be in any dictionary? At the end of the code, I've tacked on a sample console log of the words that succeed and fail.
func speakRandomWord() {
//Now that a valid random word is selected, this where I make my API call.
appId = "private"
appKey = "private"
language = "en"
word = randomWord //I get the random word from Lexicontext, Oxford API doesn't support random words yet. I filter out entriest that aren't one word.
regions = "gb"
word_id = word.lowercased()
url = URL(string:"https://odapi.oxforddictionaries.com:443/api/v1/entries/\(language)/\(word_id)/regions=\(regions)")!
var request = URLRequest(url: url)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue(appId, forHTTPHeaderField: "app_id")
request.addValue(appKey, forHTTPHeaderField: "app_key")
let session = URLSession.shared
print ("URLSession began...")
dataAttempted += 1
_ = session.dataTask(with: request, completionHandler: { data, response, error in
if let httpResponse = response as? HTTPURLResponse {
print("statusCode: \(httpResponse.statusCode)")
if httpResponse.statusCode != 200 {
print ("URLSession failed, trying again...")
self.urlSessionFailed += 1
self.selectRandomWord()
self.speakRandomWord()
}
}
DispatchQueue.main.async { [unowned self] in
self.activitySpinner.stopAnimating()
self.activitySpinner.isHidden = true
self.theUserInput.becomeFirstResponder()
self.repeatButton.isEnabled = true
self.defineButton.isEnabled = true
}
if let response = response,
let data = data,
let jsonData = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject]
{
if
let dictionary = jsonData as? [String: Any],
let results = dictionary["results"] as? [[String: Any]],
!results.isEmpty, case var result = results[0],
let lexicalEntries = result["lexicalEntries"] as? [[String: Any]],
!lexicalEntries.isEmpty, case var lexicalEntry = lexicalEntries[0],
let derivatives = lexicalEntry["derivatives"] as? [[String:Any]],
!derivatives.isEmpty, case var derivative = derivatives[0],
let pronunciations = lexicalEntry["pronunciations"] as? [[String: Any]],
!pronunciations.isEmpty, case var pronunciation = pronunciations[0],
let entries = lexicalEntry["entries"] as? [[String:Any]],
!entries.isEmpty, case var entry = entries[0],
let senses = entry["senses"] as? [[String:Any]],
!senses.isEmpty, case var sense = senses[0],
let examples = sense["examples"] as? [[String:Any]],
!examples.isEmpty, case var example = examples[0]
//Now I pick out details from the JSON like audio, sample sentence, etc.
{
if let theText = example.removeValue(forKey:"text") {
self.theText = String(describing:theText)
print ("The value is \(self.theText)")
}
if let meaning = sense.removeValue(forKey:"short_definitions") {
self.newMeaning = String(describing:meaning)
print ("The short meaning is \(self.meaning)")
}
if let origin = entry.removeValue(forKey:"etymologies") {
self.newEtymology = String(describing:origin)
self.decoded2 = String(self.newEtymology.characters.filter({!"U0123456789".contains(String($0))}))
// print ("The etymology is \(self.decoded2)")
}
if let audioPath = pronunciation.removeValue(forKey:"audioFile") {
print ("MP3 audio file for \(self.word) exists")
print (audioPath)
self.audioPaths.append(audioPath as! String)
self.dataIsValid += 1
self.successfulWords.append(self.word)
self.path = String(describing:audioPath)
self.repeatPath = self.path
print (self.path)
if let url = URL.init(string: self.path) {
self.player = AVPlayer.init(url: url)
self.player.play()
}
}
else {
print ("No audio data for \(self.word)!")
self.dataFailed += 1
self.audioFailed.append(self.word)
self.selectRandomWord()
}
}
else {
print ("\(self.randomWord) failed, reason unknown!")
self.dataFailed += 1
self.failedOtherReason.append(self.word)
self.selectRandomWord()
}
}
}).resume()
}
// Console results:
//URL Session failures: 5
//API attempts: 41.0
//API successes: 15.0
//API failures: 21.0
//Successful Oxford API hit percentage: 36.5853658536585
//These 15 words succeeded: ["denunciation", "strategic", "strategic", "detract", "opaline", "opaline", "jurist", "exit", "throaty", "mother", "mother", "regroup", "early", "managerial", "recalcitrant"]
//These 0 words had no audio: []
//These words 21 words failed for an unknown reason: ["overlook", "lignin", "mandate", "evident", "anagrammatize", "avoirdupois", "extraterrestrial", "opaline", "cauterization", "malted", "clear", "snuggle", "fresno", "jurist", "streamline", "nonconforming", "trochee", "adventitia", "rudiment", "sculpturesque", "bicycle"]
There is not enough information to debug this specific problem but it is likely that the JSON response you are receiving has optional fields. Fields that are expected to be empty for certain responses. This part of your code,
if
let dictionary = jsonData as? [String: Any],
let results = dictionary["results"] as? [[String: Any]],
!results.isEmpty,
case var result = results[0],
let lexicalEntries = result["lexicalEntries"] as? [[String: Any]],
!lexicalEntries.isEmpty,
case var lexicalEntry = lexicalEntries[0],
let derivatives = lexicalEntry["derivatives"] as? [[String:Any]],
!derivatives.isEmpty,
case var derivative = derivatives[0],
let pronunciations = lexicalEntry["pronunciations"] as? [[String: Any]],
!pronunciations.isEmpty, case var pronunciation = pronunciations[0],
let entries = lexicalEntry["entries"] as? [[String:Any]],
!entries.isEmpty,
case var entry = entries[0],
let senses = entry["senses"] as? [[String:Any]],
!senses.isEmpty, case var sense = senses[0],
let examples = sense["examples"] as? [[String:Any]],
!examples.isEmpty,
case var example = examples[0]
//Now I pick out details from the JSON like audio, sample sentence, etc.
{
needs to be split to handle (and report) missing fields. That is 20 boolean tests that all must pass before your 'good' path executes. What is likely happening is that your dictionary accesses are failing. If for instance dictionary does not contain "lexicalEntries", then that will take the failure path and not return an empty array as you are expecting.
Confirm that the same words fail every time. Look at the actual JSON for failed words and make sure that every field is being reported.
UPDATE:
if let response = response,
let data = data,
let jsonData = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject]
{
guard let dictionary = jsonData as? [String: Any] else {
print("Error: dictionary was not returned by endpoint")
return
}
guard let results = dictionary["results"] as? [[String: Any]] else {
print("Error: no results array returned by endpoint")
return
}
result = results.first
...

Accessing Object Attributes from JSON, Swift3

let url = URL(string: "http://192.168.30.243:5000/trippy/destination/info?id=4864cc0a-8")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print ("ERROR")
}
else {
if let content = data {
do {
//Array
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(myJson)
if let information = myJson as? NSDictionary {
print (information.value(forKey: "EmergencyNumbers")!)
if let number = information.value(forKey: "EmergencyNumbers") as? NSArray {
//This is the part I am unsure about
if let description = number[0] as? AnyObject {
//I know do not know how to access the object's attribute values
}
}
}
}
catch {
}
}
}
}
task.resume()
}
I have used JSON to parse data from the web. I have utilized a dictionary to access the information and then an array to get the data from the certain key. Within this array are lie some objects. How do I access each of these objects' properties' values?
JSON Example:
{
Currency = testCurrency;
DestinationId = "4864cc0a-8";
DialCode = testDialCode;
DoesntUseMetricSystem = 0;
DrinkingAge = 16;
DriverLicense = 1;
EmergencyNumbers = (
{
Description = "Emergency Pizza Delivery";
Id = 1;
Number = 6969;
}
);
Id = 1;
IsNorthHemisphere = 1;
OfficialLanguage = {
Id = 1;
Name = testLanguage;
};
PowerGridVoltage = 226;
PowerSocket = dk;
Telecoms = nonern;
Tipping = 2;
WidelySpokenLanguages = (
{
Id = 2;
Name = testtLanguage;
}
);
WrongSideOfRoad = 0;
}
I see you are coming from Objective-C world, so first I'd recommend you give up using NSArray, NSDictionary etc. in favor of their Swift counterparts Array and Dictionary:
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
...
let JSON = try? JSONSerialization.jsonObject(with: data!, options: [])
if let dictionary = JSON as? [String: Any],
let emergencyNumbers = dictionary["EmergencyNumbers"] as? [[String: Any]]
{
emergencyNumbers.forEach { numbers in
print(numbers["Description"] as? String)
print(numbers["Id"] as? Int)
print(numbers["Number"] as? Int)
}
}
}
By the way [String: Any] is just a syntactic sugar for Dictionary<String, Any>. Same applies to arrays as well: [[String: Any]] is for Array<Dictionary<String, Any>>.
As always, don't use NSArray / NSDictionary in Swift. You throw away the type information.
The root object is a dictionary ([String:Any]), the value for key EmergencyNumbers is an array ([[String:Any]]). Use a loop to iterate thru the array.
if let root = try JSONSerialization.jsonObject(with: content) as? [String:Any] {
print(myJson)
if let emergencyNumbers = root["EmergencyNumbers"] as? [[String:Any]] {
for emergencyNumber in emergencyNumbers {
let description = emergencyNumber["Description"] as? String
let id = emergencyNumber["Id"] as? Int
let number = emergencyNumber["Number"] as? Int
print("description", description ?? "n/a")
print("id", id ?? "n/a")
print("number", number ?? "n/a")
}
}
Some other bad habits:
.mutableContainers is completely meaningless in Swift. The hilarious thing is, everybody who passes the option .mutableContainers assigns the result to an immutable constant.
The unspecified JSON type in Swift 3+ is Any not AnyObject
valueForKey, a KVC method, is inappropriate for this purpose, use objectForKey or key subscription. With Swift native types don't use it at all.

Turning json variables into swift variables

My PHP server-side returns a JSON like this:
[{"scan_status":"ok","visitorData":[{"visitorCompany":"xyl","visitorStreet":"street","visitorBranche":"health","visitorEmail":"wesweatyoushop#gmail.com","lastmodified":"2014-12-15 14:18:55"}]}]
Now in Swift I would like to store this data, and for this I am trying to parse the data into Swift variables, however I got stuck.
do {
//check wat we get back
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves )
let vData = jsonData[0]["visitorData"]
//let vCompany = vData["visitorCompany"]
print("Test vData: \(vData)")
}
This prints
Test vData: Optional(( { visitorStreet = street; visitorPhone = 01606478; visitorCompany = xyl; visitorBranche = Sports; visitorEmail = "health#gmail.com"; lastmodified = "2014-12-15 14:18:55"; } ))
but when I try to get visitorCompany with
let vCompany = vData["visitorCompany"]
I get a compile error:
Cannot subscript a value of type 'AnyObject?!' with an index of type 'String'
BTW, why do we see the equals sign in swift i.e. visitorStreet = street?
This is because the compiler doesn't know the type of your decoded objects.
Help the compiler using casting with if let:
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves )
if let vData = jsonData[0]["visitorData"] as? [[String:AnyObject]] {
if let vCompany = vData[0]["visitorCompany"] {
print(vCompany)
}
}
}
let vData = jsonData[0]["visitorData"] populates vData with a generic AnyObject?, because Swift can't know what kind of objects PHP returns in the JSON.
You need to do a an optional cast to another dictionary before you can use vData like you want: jsonData[0]["visitorData"] as? [String:AnyObject].
And because a conditional cast returns an optional, it's best you do an optional binding to unwrap that optional, resulting in a code similar to this:
if let vData = jsonData[0]["visitorData"] as? [String:AnyObject] {
//let vCompany = vData["visitorCompany"]
print("Test vData: \(vData)")
}
Or even better, as jsonData can not be an array, or it could be an empty array (the server malfunctions and sends an invalid json for example), you can go even further with the validation:
if let items = jsonData as? [[String:AnyObject]], vData = items.first?["visitorData"] {
//let vCompany = vData["visitorCompany"]
print("Test vData: \(vData)")
}
items = jsonData as? [[String:AnyObject]] fails if jsonData is not an array, while vData = items.first?["visitorData"] fails if items.first is nil (optional chaining here), or if items.first doesn't have a visitorData key.
Try with this:
let vData = jsonData[0]["visitorData"]![0] as! [String:AnyObject]