I need to store each part of the json into a data structure, maybe Array?
This is what I have when I call Firebase realtime:
ref.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
let value = snap.value
print("######################################################\n\n")
print("key = \(key) value = \(value!)")
}
})
when printed it looks like this:
What would be the best way to accomplish this?
I need to store the title, description and postUrl
EDIT:
If I do this:
let userID = Auth.auth().currentUser?.uid
ref.child("post").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
print(snapshot)
}) { (error) in
print(error.localizedDescription)
}
that would give me the snapshot id but not the values because the user is not in that DB, how can I print the snapshots without saying current user?
If you can't for some reason access the snapshot's value parameter as above, you will probably need to manually parse the value string (as it's not proper json). The code below will allow you to initialise the data structure with the value text. This assumes value is a nicely formed string as in the example below. If not you may need to play with it a bit more:
let valueString = """
{
description = dawdled
postUrl = http://darrengillman.com
timestamp = 1572735278831
title = testy
}
"""
struct DataStruct {
let decription: String
let postUrl: URL
let timestamp: Int
let test: String
init?(from value: String) {
let strings = Array(value.components(separatedBy: .newlines).dropLast().dropFirst()).map{$0.components(separatedBy: "=")}.map{$0.dropFirst().first!.trimmingCharacters(in: .whitespaces)}
guard strings.count == 4 else {return nil}
self.decription = strings[0]
self.postUrl = URL(string: strings[1])!
self.timestamp = Int(strings[2]) ?? 0
self.test = strings[3]
}
}
let myData = DataStruct(from: valueString)
Related
After receiving a JSON payload from a web request, I need to parse out the data into variables in an NSObject subclass.
My variables are declared in the object:
var name:String?
var email:String?
var aboutMe:String?
Then I start parsing through all the possible data the JSON may return:
if let name = json["Name"] as? String
{
self.name = name
}
if let email = json["Email"] as? String
{
self.email = email
}
if let aboutMe = json["AboutMe"] as? String
{
self.aboutMe = aboutMe
}
This is becoming rather long as we have a lot of data.
I was wanting to shorted it by using a Dictionary containing the JSON key and the variable like this:
let varMap:[String:Any?] = [
"Name": self.name,
"Email": self.email,
"AboutMe": self.aboutMe
]
Then iterating over them like this:
for (key, var variable) in varMap
{
if let string = json[key]
{
variable = string
}
}
The problem here is that the Dictionary copies the value from the variable and not a pointer to it, so setting it to a new value has no effect on the variables of the overall object.
Is there a way to achieve what I am trying? If not, what other pattern would be a better way to do this instead of having tons of if statements?
For JSON parsing, you can simply use Codable types.
Let's assume your JSON looks like,
{
"name": "Alex",
"email": "alex#gmail.com",
"about_Me": "My name is Alex"
}
Models:
class Root: Decodable {
let name: String?
let email: String?
let aboutMe: String?
enum CodingKeys: String, CodingKey {
case name, email
case aboutMe = "about_Me"
}
}
Parse the JSON data like so,
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response) //use response here...
} catch {
print(error)
}
You can use updateValue function for that, it finds the property and changes it.
if let oldValue = varMap.updateValue(self.name, forKey: "Name") {
print("The old value of \(oldValue) was replaced with a new one.")
}
So you for iteration is;
for (key, var variable) in varMap
{
varMap.updateValue(string, forKey: key )
//if let string = json[key]
//{
// variable = string
//}
}
After you update the dictionary you can call that part;
if let name = json["Name"] as? String
{
self.name = name
}
if let email = json["Email"] as? String
{
self.email = email
}
if let aboutMe = json["AboutMe"] as? String
{
self.aboutMe = aboutMe
}
I believe that part in a function, if its not, you can refactor it.
https://developer.apple.com/documentation/swift/dictionary/3127179-updatevalue
I am properly loading up my own profile information in an iOS Swift application, but the JSON has an object associated with it which is confusing me on how to properly parse it in order to access the data within it. Here is my method.
func attemptToLoadProfile(hash: String) {
let url = "https://www.gravatar.com/\(hash).json"
let fileURL = URL(string: url)
do {
let contents = try String(contentsOf: fileURL!)
let data = contents.data(using: String.Encoding.utf8)
let json = try? JSONSerialization.jsonObject(with: data!, options: [])
print(json!)
} catch {
print("error parsing json")
}
}
This works fine, but when I print it out, the JSON is formatted like this.
{
entry = (
{
displayName = edolecki;
hash = <myhash here>;
id = 1333520;
name = {
familyName = Dolecki;
formatted = "Eric Dolecki";
givenName = Eric;
};
photos = (
{
type = thumbnail;
value = "https://secure.gravatar.com/avatar/<myhash here>";
}
);
preferredUsername = edolecki;
profileUrl = "http://gravatar.com/edolecki";
requestHash = <myhash here>;
thumbnailUrl = "https://secure.gravatar.com/avatar/<myhash here>";
urls = (
);
}
);
}
How do I parse the JSON seeing there is that entry object at the root? I am after displayName, id, etc. I normally parse JSON without a more simplified root. I haven't seen this before.
The value associated with the entry key is just an array with one element. In this case, you can access json["entry"], cast it to a [[String: Any]] and access the first element [0]. Then you can access the things you want, like displayName and id.
A better way to do this is use Codable. Using QuickType, I generated this code:
struct Root: Codable {
let entry: [Entry]
}
struct Entry: Codable {
let id, hash, requestHash: String
let profileURL: URL
let preferredUsername: String
let thumbnailURL: URL
let photos: [Photo]
let displayName: String
enum CodingKeys: String, CodingKey {
case id, hash, requestHash
case profileURL = "profileUrl"
case preferredUsername
case thumbnailURL = "thumbnailUrl"
case photos, displayName
}
}
struct Photo: Codable {
let value: URL
let type: String
}
Then you can do this to parse the json:
let decoder = JSONDecoder()
let root = try decoder.decode(Root.self, from: data)
// e.g.
let displayName = root.entry[0].displayName
If you don't need any of the json KVPs, just remove it from the struct.
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.
I am trying to retrieve information from Firebase. I can get the snapshot in JSON but I am having trouble accessing it and saving the values in my app.
This is how the code looks like:
self.ref.child("users").child(userFound.userRef!).child("currentGame").observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
if let snapDict = snapshot.value as? [String:AnyObject] {
for each in snapDict {
self.theApp.currentGameIDKey = String(each.key)
self.currentGame.playerAddressCoordinates?.latitude = each.value["playerLatitude"] as! Double
print(self.theApp.playerAddressCoordinates?.latitude)
print(self.currentGame.currentGameIDKey)
}
}
})
And this is how it prints in the console:
Snap (currentGame) {
"-KUZBVvbtVhJk9EeQAiL" = {
date = "2016-10-20 18:24:08 -0400";
playerAdress = "47 Calle Tacuba Mexico City DF 06010";
playerLatitude = "19.4354257";
playerLongitude = "-99.1365724";
};
}
The currentGameIDKey gets saved but the self.currentGame.playerAddressCoordinates do not.
Assuming that you have multiple objects in your node "currentGame" and you're looking to extract the player address coordinates and current game id key from all of them, here's how you can do it :
self.ref.child("users").child(userFound.userRef!).child("currentGame").observeSingleEvent(of: .value, with: { (snapshot) in
if(snapshot.exists()) {
let enumerator = snapshot.children
while let listObject = enumerator.nextObject() as? FIRDataSnapshot {
self.theApp.currentGameIDKey = listObject.key
let object = listObject.value as! [String: AnyObject]
self.currentGame.playerAddressCoordinates?.latitude = object["playerLatitude"] as! Double
print(self.theApp.playerAddressCoordinates?.latitude)
print(self.currentGame.currentGameIDKey)
}
}
According to your database design, you were not accessing the "playerLatitude" in the correct manner. "playerLatitude" is a child of the child of your snapshot.
I'm guessing you are inserting into "currentGame" using childByAutoId(). Therefore, you need to unwrap it one level further to access it.
Also, if you have to access only one child, you can also use :
self.ref.child("users").child(userFound.userRef!).child("currentGame").observeSingleEvent(of: .value, with: { (snapshot) in
if(snapshot.exists()) {
let currentGameSnapshot = snapshot.children.allObjects[0] as! FIRDataSnapshot
self.theApp.currentGameIDKey = currentGameSnapshot.key
self.currentGame.playerAddressCoordinates?.latitude = currentGameSnapshot.childSnapshot(forPath: "playerLatitude").value as! Double
print(self.theApp.playerAddressCoordinates?.latitude)
print(self.currentGame.currentGameIDKey)
}
Hope this helps!
So I've run into a tiny obstacle and I'm trying to access a variable created inside an Alamofire request function. A bit of background into:
Used SwiftyJSON/Alamofire to access JSON file and parse it, have a variable for a accessing date, but date was in RFC 3339 format and now I created a function to parse the date from RFC 339 to a readable format but i don't now how to access the date variable created in the JSON parse function to use with the Date parse function.
//Get the JSON from server
func getJSON() {
Alamofire.request(.GET, "link goes here").responseJSON { (Response) in
if let value = Response.result.value {
let json = JSON(value)
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let date: String? = anItem["Date"].stringValue //trying to access this variable outside the function
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableDate.append(date!)
self.tableBody.append(body!)
print(anItem["Title"].stringValue)
print(anItem["Date"].stringValue)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
}
// this date stuff isn't being used yet, because I have no idea how...
public func dateForRFC3339DateTimeString(rfc3339DateTimeString: String) -> NSDate? {
let enUSPOSIXLocale = NSLocale(localeIdentifier: "en_US_POSIX")
let rfc3339DateFormatter = NSDateFormatter()
rfc3339DateFormatter.locale = enUSPOSIXLocale
rfc3339DateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
rfc3339DateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
return rfc3339DateFormatter.dateFromString(rfc3339DateTimeString)
}
public func userVisibleDateTimeStringForRFC3339DateTimeString(rfc3339DateTimeString: String) -> String? {
let maybeDate = dateForRFC3339DateTimeString(rfc3339DateTimeString)
if let date = maybeDate {
let userVisibleDateFromatter = NSDateFormatter()
userVisibleDateFromatter.dateStyle = NSDateFormatterStyle.MediumStyle
userVisibleDateFromatter.timeStyle = NSDateFormatterStyle.ShortStyle
return userVisibleDateFromatter.stringFromDate(date)
} else {
return nil
}
}
let finalDateStr = userVisibleDateTimeStringForRFC3339DateTimeString(MasterViewController) //now this is where it gets weird, instead of letting me enter the string in the brackets, it defaults to MasterViewController, now I tried to move the date functions to another .swift file (an empty one) and it doesn't do that anymore
So yeah, that's about it, if anyone could help, it would be greatly appreciated.
Try below code, let me know if it works:
func dateForRFC3339Date(rfc3339Date: NSDate) -> NSDate? {
let enUSPOSIXLocale = NSLocale(localeIdentifier: "en_US_POSIX")
let rfc3339DateFormatter = NSDateFormatter()
rfc3339DateFormatter.locale = enUSPOSIXLocale
rfc3339DateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
rfc3339DateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
let dateString = rfc3339DateFormatter.stringFromDate(rfc3339Date)
return rfc3339DateFormatter.dateFromString(dateString)
}
And call it in your getJSON() method like:
let convertedDate = self.dateForRFC3339Date(rfc3339Date: anItem["Date"])