Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I am trying to generate QRCode and then read it in. My problem is that I do not know how to get values from the QRCode string.
String inside my QRCode is following: ["firstName": "John", "lastName": "Bush"]
I achieved it like this:
let dict = ["firstName":firstName, "lastName": lastName]
dict.description
Now I want to get the values out of it, how should I do this? I tried to convert the string back to dictionary but couldn't do it.
If you're really interested in parsing this result, there is of course ways to do it. It's never going to be consistently successful unless there is a consistent format coming from your source. In the event that it is consistent, you could of course do something like this:
//get the string representation of our dictionary
let code = "[\"firstName\": \"firstName\", \"lastName\": \"lastName\"]"
//remove the brackets
let bracketless = String(code.characters.dropFirst().dropLast())
//get rid of our quotes
let quoteless = bracketless.replacingOccurrences(of: "\"", with: "")
//split our dictionary by key/value pairs
let pairs = quoteless.components(separatedBy: ", ")
//now split our key/value pairs into each key and value component respectively
let values: [[String]] = pairs.map { $0.components(separatedBy: ": ") }
//create ourself a dictionary to populate with our parsed data
var dict: [String:Any] = [:]
values.forEach { dict[$0[0]] = $0[1] } //populate the dictionary
print(dict) // ["lastName": "lastName", "firstName": "firstName"]
It's always better to use a standardized format (e.g. JSON). Perhaps in your situation this just isn't an option. I would still wonder why then you're in said situation..
This is rather tricky, the result from description is not really meant to be used as an exchange format. The best thing to do would be to use a different format to generate your QR Codes. But let's assume that it's too late for this.
To get the data back you need to write a parser, since the system doesn't provide one for the description format. Sounds hard, but it really is not. You might consider using the String method components(separatedBy:), but this will turn out very inefficient. The Foundation Scanner class is a much better tool for this.
The simplest parser to write is a "recursive descent" parser. This means for each part you want to recognise you write a function that calls such functions for sub-parts. So lets see what we have here:
The outer layer is the dictionary. This starts with the string "[", then we have key/value pairs separated by "," and finally another "]". (There also might be the case of an empty dictionary which is just "[]"
Then we have a pair. This is a string inside quotes, followed by a ":" and another quoted string.
The last part is the quoted string. There we have the quote ", and any other characters up to the next quote.
So in pseudo-code this will look something like this:
func dictionary() {
expect("[")
if !read("]") {
repeat {
pair()
} while read(",")
}
expect("]")
}
func pair() {
quotedString()
expect(":")
quotedString()
}
func quotedString() {
expect("\"")
readUpTo("\"")
expect("\"")
}
Here expect, read and readUpTo are placeholders for the methods provided by the Scanner class. If we provide those this basically is the complete parser. But like this it is not very useful as it just ignores the data. So we need to extend our parser so it actually returns the found data.
The final result then could look something like this:
let scanner = Scanner(string: string)
func dictionary() -> [String: String]? {
guard scanner.scanString("[", into: nil) else { return nil }
var result: [String: String] = [:]
if !scanner.scanString("]", into: nil) {
repeat {
guard let (key, value) = pair() else { return nil }
result[key] = value
} while scanner.scanString(",", into: nil)
}
guard scanner.scanString("]", into: nil) else { return nil }
return result
}
func pair() -> (String, String)? {
guard let key = quotedString(),
scanner.scanString(":", into: nil),
let value = quotedString()
else {
return nil
}
return (key, value)
}
func quotedString() -> String? {
guard scanner.scanString("\"", into: nil) else { return nil }
var result: NSString? = nil
guard scanner.scanUpTo("\"", into: &result), let string = result as? String else { return nil }
guard scanner.scanString("\"", into: nil) else { return nil }
return string
}
A little bit more code that the hacky solution using string splitting, but not really complicated either and much more flexible. Here we won't have any problems if the strings themselves contain "," or ":". The performance will be better too, since here we look at every character only once. The other solution will have to look at each character three times.
It still has one problem though: If any of your strings contain the double quote character this parser will fail. The description property of the dictionary will output this in a backslash-quoted form as \" - this will have to be handled in quotedString.
Related
I am trying to mock Apollo Queries using its init. It pretty much is taking in a dictionary to build the object up.
public init(unsafeResultMap: [String: Any]) {
self.resultMap = unsafeResultMap
}
So, I have decided to create Mock objects that have the same properties of the query objects while being Encodable (So we get the free JSON conversion, which can be represented as a string version dictionary).
For example:
class MockAnimalObject: Encodable {
let teeth: MockTeethObject
init(teeth: MockTeethObject) {
self.teeth = teeth
}
}
class MockTeethObject: Encodable {
let numberOfTeeth: Int
let dateOfTeethCreation: Date
init (numberOfTeeth: Int, dateOfTeethCreation: Date) {
self.numberOfTeeth = numberOfTeeth
self.dateOfTeethCreation = dateOfTeethCreation
}
}
The problem is, the Apollo conversion checks the types during the result map, which in our case is a string of [String: Encodable].
And this is where the Date encodable becomes a problem.
/// This property is auto-generated and not feasible to be edited
/// Teeth date for these teeth
public var teethCreationDate: Date {
get {
// This is the problem. resultMap["teethCreationDate"] is never going to be a Date object since it is encoded.
return resultMap["teethCreationDate"]! as! Date
}
set {
resultMap.updateValue(newValue, forKey: "teethCreationDate")
}
}
So, I am wondering if it is possible to override the encoder to manually set the date value as a custom type.
var container = encoder.singleValueContainer()
try container.encode(date) as Date // Something where I force it to be a non-encodable object
JSON has nothing to do with this. JSON is not any kind of dictionary. It's a serialization format. But you don't want a serialization format. You want to convert types to an Apollo ResultMap, which is [String: Any?]. What you want is a "ResultMapEncoder," not a JSONEncoder.
That's definitely possible. It's just an obnoxious amount of code because Encoder is such a pain to conform to. My first pass is a bit over 600 lines. I could probably strip it down more and it's barely tested, so I don't know if this code works in all (or even most) cases, but it's a start and shows how you would attack this problem.
The starting point is the source code for JSONEncoder. Like sculpture, you start with a giant block of material, and keep removing everything that doesn't look like what you want. Again, this is very, very lightly tested. It basically does what you describe in your question, and not much else is tested.
let animal = MockAnimalObject(teeth: MockTeethObject(numberOfTeeth: 10,
dateOfTeethCreation: .now))
let result = try AnyEncoder().encode(animal)
print(result)
//["teeth": Optional(["dateOfTeethCreation": Optional(2022-08-12 18:35:27 +0000),
// "numberOfTeeth": Optional(10)])]
The key changes, and where you'd want to explore further to make this work the way you want, are:
Gets rid of all configuration and auto-conversions (like snake case)
Handles the "special cases" (Date, Decimal, [String: Encodable]) by just returning them. See wrapEncodable and wrapUntyped
If you want [String: Any] rather than [String: Any?] (which is what ResultMap is), then you can tweak the types a bit. The only tricky piece is you would need to store something like nil as Any? as Any in order to encode nil (or you could encode NSNull, or you could just not encode it at all if you wanted).
Note that this actually returns Any, since it can't know that the top level encodes an object. So you'll need to as? cast it to [String: Any?].
To your question about using Mirror, the good thing about Mirror is that the code is short. The bad thing is that mirror is very slow. So it depends on how important that is. Not everything has the mirror you expect, however. For your purposes, Date has a "struct-like" Mirror, so you have to special-case it. But it's not that hard to write the code. Something like this:
func resultMap(from object: Any) -> Any {
// First handle special cases that aren't what they seem
if object is Date || object is Decimal {
return object
}
let mirror = Mirror(reflecting: object)
switch mirror.displayStyle {
case .some(.struct), .some(.class), .some(.dictionary):
var keyValues: [String: Any] = [:]
for child in mirror.children {
if let label = child.label {
keyValues[label] = resultMap(from: child.value)
}
}
return keyValues
case .some(.collection):
var values: [Any] = []
for child in mirror.children {
values.append(resultMap(from: child.value))
}
return values
default:
return object
}
}
let animal = MockAnimalObject(teeth: MockTeethObject(numberOfTeeth: 10, dateOfTeethCreation: .now))
let result = resultMap(from: animal)
print(result)
// ["teeth": ["dateOfTeethCreation": 2022-08-12 21:08:11 +0000, "numberOfTeeth": 10]]
This time I didn't bother with Any?, but you could probably expand it that way if you needed. You'd need to decide what you'd want to do about enums, tuples, and anything else you'd want to handle specially, but it's pretty flexible. Just slow.
As pointed out in comments, JSON (Javascript object notation) is universal format and is not anyhow related to Date object in Swift after it is encoded. Therefore somewhere in the flow you need to make it String Double or some other object type that can be encoded to JSON. Anyway, if you want to make encoding Date to be easier you can take hold of some native JSONEncoder functions, such as folowing:
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
I am inserting an Array into my database as a String and after fetching it I want it to convert it again to Array. So that I can fetch my values again and I can do next operation.
Here below is my array inserting into database(TestQuestion) as a String:
let testQuestionModel : TestQuestion = NSEntityDescription.insertNewObject(forEntityName: "TestQuestion", into: AppDelegate.getContext()) as! TestQuestion
testQuestionModel.optionsArray = "\(question["options"] as! NSArray)"
Example: String Array I am getting from Database
(\n \"Rahul Abhyankar\",\n \"Pinkesh Shah\",\n \"Ramanan
Ganesan\",\n \"Dr. Marya Wani\",\n \"\",\n \"\"\n)".
Here is 4 options you can see this is my string after fetching from Database.
1) Rahul Abhyankar.
2) Pinkesh Shah.
3) Ramanan Ganesan.
4) Dr. Marya Wani.
Now how can I convert it into array?
I tried some methods.
let arr = NSArray(object: quetion.optionsArray!).
But I am getting only one object. How can I get my array values same as previous from this string array?
I don't know about the actual type of the "option" in your code, so I set up a fake Elem struct to represent it. The remaining logic is independent of the type as long as you provide a conversion logic to and from String.
struct Elem {
// let's say this is your element type in your array
let foo: Int;
}
extension Elem: CustomStringConvertible {
var description: String {
// provide a logic to convert your element to string
return "\(foo)";
}
}
let arrayToSave = [
Elem(foo: 1),
Elem(foo: 2),
Elem(foo: 3)
]
extension Elem {
init(string: String) {
// provide a function to construct your element type from a string
self.init(foo: Int(string)!)
}
}
let stringToSave = arrayToSave.map { $0.description }.joined(separator: "|")
// save this string
// at some point retrieve it from database, which hopefully same as the saved one
let retrivedString = stringToSave;
let retrivedArray = retrivedString.split(separator: "|").map { Elem(string: String($0)) }
print(retrivedArray) // [1, 2, 3]
Here below is my array inserting into database (TestQuestion) as a
String :
let testQuestionModel : TestQuestion = NSEntityDescription.insertNewObject(forEntityName: "TestQuestion", into: AppDelegate.getContext()) as! TestQuestion
testQuestionModel.optionsArray = "\(question["options"] as! NSArray)"
No, and No.
You are using -description method of an array to save it. Clearly no.
What's wrong? Apple can't affirm that in next OS release, it won't add an extra character. In some more complex cases, it's added <NSArray <0x address> or stuff similar like that.
Suggestion 1:
Modify your entity to have an ARRAY (or usually a Set) of String.
Learn about Core-Data relationship (but that's clearly a DataBase basic knownledge). A relationship one to many should be the thing to do.You could even keep in memory what were the choices, by adding for creating the entity Options, with a String property name (name of the option), another one boolean isChecked, etc.
Suggestion 2:
If you have a limited number of options (like says one to 5), add 5 options string to your entity, and iterate to set them
testQuestionModel.option1 = question["option"][0]
testQuestionModel.option2 = question["option"][1] (if it's of course not out of range for the array)
...
Suggestion 3:
Not really recommended (in my opinion it's missing the whole advantage of the database, especially fetch and predicates, on previous sample you could fetched easily which options were checked), but if you still want to save them as a String, save them as JSON (ie. stringified).
In pseudo code (I'm not sure about the exact syntax, there are no fail safe like try/catch, optional/wrapping):
let options = questions["options"] as [String]
let jsonData = JSONSerialization.data(withJSONObject: (question["options"], options:[])
let jsonString = String.init(data:jsonData encoding:.utf8)
To retrieve them:
let options = JSONSerialization.jsonObject(with data: myJSONString.data(encoding:.utf8), options:[]) as [String]
done using Library SwiftyJSON.
if let dataFromString = yourString?.data(using: String.Encoding.utf8, allowLossyConversion: false) {
do{
let json = try JSON(data: dataFromString)
print(json)
let arrayValue = json.rawValue as! NSArray
print(arrayValue)
} catch{
print(error.localizedDescription)
}
}
Source: https://github.com/SwiftyJSON/SwiftyJSON
I have asked a question before about this program, but it seems that not all problems are resolved. I am currently experiencing an error that states: "Cannot convert value of type 'String' to expected argument type '_Element' (aka 'Character') on the "guard let indexInWord" line:
guard let letterIndex = letters.indexOf(sender)
else { return }
let letter = letterArray[letterIndex]
guard let indexInWord = word.characters.indexOf(letter)
else {
print("no such letter in this word")
return
}
// since we have spaces between dashes, we need to calc index this way
let indexInDashedString = indexInWord * 2
var dashString = wordLabel.text
dashString[indexInDashedString] = letter
wordLabel.text = dashString
I tried converting the String 'letter' to Character but it only resulted in more errors. I was wondering how I can possibly convert String to argument type "_Element." Please help.
It is hard to treat a string like a list in swift, mostly because the String.characters is not a typical array. Running a for loop on that works, but if you are looking for a specific character given an index, it is a bit more difficult. What I like doing is adding this function to the string class.
extenstion String {
func getChars() -> [String] {
var chars:[String] = []
for char in characters {
chars.append(String(char))
}
return chars
}
}
I would use this to define a variable when you receive input, then check this instead of String.characters
I'm creating an app that should retrieve some JSON from a database.
This is how my JSON looks:
[{"id":"1","longitude":"10","latitude":"10","visibility":"5","timestampAdded":"2015-10-01 15:01:39"},{"id":"2","longitude":"15","latitude":"15","visibility":"5","timestampAdded":"2015-10-01 15:06:25"}]
And this is the code i use:
if let jsonResult = JSON as? Array<Dictionary<String,String>> {
let longitudeValue = jsonResult[0]["longitude"]
let latitudeValue = jsonResult[0]["latitude"]
let visibilityValue = jsonResult[0]["visibility"]
print(longitudeValue!)
print(latitudeValue!)
print(visibilityValue!)
}
As you can see it only gets the first chunk from the JSON and if there are no JSON at all it will crash, but if i want it to count the amount and make an array out of it like this:
var longitudeArray = [10, 15]
var latitudeArray = [10, 15]
And so on...
I also need this to be apple watch compatible so i can't use SwiftyJSON.
What do i do? I really hope you can help me!
Thanks.
SOLVED!
Problems was solved by "Eric D."
This is the code:
do {
if let url = NSURL(string: "YOU URL HERE"),
let data = NSData(contentsOfURL: url),
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [[String:AnyObject]] {
print(jsonResult)
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
print(longitudeArray)
print(latitudeArray)
}
} catch let error as NSError {
print(error.description)
}
Thank you soo much Eric!! :-)
You could use flatMap to get an array of your elements:
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
etc.
flatMap is like map but unwraps optionals, which is adequate because we need to safely cast the type of the object we get from each dictionary in the json array.
$0 represents the object in the current iteration of flatMap of the array it's applied to.
If you're currently using SwiftyJSON, then that would be:
let longitudeArray = jsonResult.flatMap { $1["longitude"].string }
let latitudeArray = jsonResult.flatMap { $1["latitude"].string }
because .string is SwiftyJSON's optional String value getter.
But as you said, you don't want to use it (anymore), so you need to use NSJSONSerialization to decode your JSON data, there's plenty of examples on the Web and on SO. Then you will be able to use my original answer.
You're already getting an array with all of the elements (not just the first one. you're simply only accessing the first one). jsonResult is an array of dictionaries. Each dictionary (in this case, based on the json you provided) contains these elements: id, longitude, latitude, visibility and timestampAdded. In order to access each of them, you can simply loop over jsonResult and access the i'th element (and not always the 0 element). This will also prevent the crash you're experiencing with the json is blank or invalid (since you'll only be going over the valid elements in jsonResult.
This will give you the flexibility to create the custom arrays you wish to create (in order to create an array of all of the longitudes, for example, you will simply add that element to the new array while looping over jsonResult). However, if you'd like to save yourself the trouble of manually building these arrays and assuming you have control over the json structure, I would recommend changing the received json to the relevant structure (a dictionary or arrays instead of an array of dictionaries), so it would better fit your needs and provide you the results in the relevant format right "out of the box".
I'm new to Swift and taking a class to learn iOS programming. I find myself stumped on how to search in an array of dictionaries for a string value and dump the string value into an array. This is taken from my Xcode playground.
I'm trying to figure out how to:
1) search through an array of dictionaries
2) dump the results of the search into an array (which I've created)
These are the character dictionaries.
let worf = [
"name": "Worf",
"rank": "lieutenant",
"information": "son of Mogh, slayer of Gowron",
"favorite drink": "prune juice",
"quote" : "Today is a good day to die."]
let picard = [
"name": "Jean-Luc Picard",
"rank": "captain",
"information": "Captain of the USS Enterprise",
"favorite drink": "tea, Earl Grey, hot"]
This is an array of the character dictionaries listed above.
let characters = [worf, picard]
This is the function I'm trying to write.
func favoriteDrinksArrayForCharacters(characters:Array<Dictionary<String, String>>) -> Array<String> {
// create an array of Strings to dump in favorite drink strings
var favoriteDrinkArray = [String]()
for character in characters {
// look up favorite drink
// add favorite drink to favoriteDrinkArray
}
return favoriteDrinkArray
}
let favoriteDrinks = favoriteDrinksArrayForCharacters(characters)
favoriteDrinks
I would be grateful for any assistance on how to move forward on this. I've dug around for examples, but I'm coming up short finding one that's applicable to what I'm trying to do here.
Inside the loop, you need to fetch the "favorite drink" entry from the dictionary, and append it to the array:
for character in characters {
if let drink = character["favorite drink"] {
favoriteDrinkArray.append(drink)
}
}
Note, the if let drink = guards against the possibility there is no such entry in the array – if there isn't, you get a nil back, and the if is checking for that, only adding the entry if it's not nil.
You might sometimes see people skip the if let part and instead just write let drink = character["favorite drink"]!, with an exclamation mark on the end. Do not do this. This is known as "force unwrapping" an optional, and if there is ever not a valid value returned from the dictionary, your program will crash.
The behavior with the first example is, if there is no drink you don't add it to the array. But this might not be what you want since you may be expecting a 1-to-1 correspondence between entries in the character array and entries in the drinks array.
If that's the case, and you perhaps want an empty string, you could do this instead:
func favoriteDrinksArrayForCharacters(characters: [[String:String]]) -> [String] {
return characters.map { character in
character["favorite drink"] ?? ""
}
}
The .map means: run through every entry in characters, and put the result of running this expression in a new array (which you then return).
The ?? means: if you get back a nil from the left-hand side, replace it with the value on the right-hand side.
Airspeed Velocity's answer is very comprehensive and provides a solution that works. A more compact way of achieving the same result is using the filter and map methods of swift arrays:
func favoriteDrinksArrayForCharacters(characters:Array<Dictionary<String, String>>) -> Array<String> {
// create an array of Strings to dump in favorite drink strings
return characters.filter { $0["favorite drink"] != nil }.map { $0["favorite drink"]! }
}
The filter takes a closure returning a boolean, which states whether an element must be included or not - in our case, it checks for the existence of an element for key "favorite drink". This method returns the array of dictionaries satisfying that condition.
The second step uses the map method to transform each dictionary into the value corresponding to the "favorite drink" key - taking into account that a dictionary lookup always returns an optional (to account for missing key), and that the filter has already excluded all dictionaries not having a value for that key, it's safe to apply the forced unwrapping operator ! to return a non optional string.
The combined result is an array of strings - copied from my playground:
["prune juice", "tea, Earl Grey, hot"]
let drinks = characters.map({$0["favorite drink"]}) // [Optional("prune juice"), Optional("tea, Earl Grey, hot")]
or
let drinks = characters.filter({$0["favorite drink"] != nil}).map({$0["favorite drink"]!}) // [prune juice, tea, Earl Grey, hot]
It may help you
var customerNameDict = ["firstName":"karthi","LastName":"alagu","MiddleName":"prabhu"];
var clientNameDict = ["firstName":"Selva","LastName":"kumar","MiddleName":"m"];
var employeeNameDict = ["firstName":"karthi","LastName":"prabhu","MiddleName":"kp"];
var attributeValue = "karthi";
var arrNames:Array = [customerNameDict,clientNameDict,employeeNameDict];
var namePredicate = NSPredicate(format: "firstName like %#",attributeValue);
let filteredArray = arrNames.filter { namePredicate.evaluateWithObject($0) };
println("names = ,\(filteredArray)");
Use the following code to search from NSArray of dictionaries whose keys are ID and Name.
var txtVal:NSString
let path = NSBundle.mainBundle().pathForResource(plistName, ofType: "plist")
var list = NSArray(contentsOfFile: path!) as [[String:String]]
var namePredicate = NSPredicate(format: "ID like %#", String(forId));
let filteredArray = list.filter { namePredicate!.evaluateWithObject($0) };
if filteredArray.count != 0
{
let value = filteredArray[0] as NSDictionary
txtVal = value.objectForKey("Name") as String
}
i have array of customer ,each customer having name,phone number and other stubs .so i used the below code to search by phone number in the array of dictionary in search bar
for index in self.customerArray
{
var string = index.valueForKey("phone")
if let phoneNumber = index.valueForKey("phone") as? String {
string = phoneNumber
}
else
{
string = ""
}
if string!.localizedCaseInsensitiveContainsString(searchText) {
filtered.addObject(index)
searchActive = true;
}
}