On trying to pretty print a valid JSON object I got stuck in an error that I am unable to circumvent. The problem started when I tried to append information into a dictionary and a extension for Dictionary was necessary for this task.
The aim of the code is to create an extension template for Albert launcher in Linux.
The error is:
./org.albert.extension.external.snippy.swift:10:21: warning: no calls to throwing functions occur within 'try' expression
let jsonS = try String(data: jsonD, encoding: String.Encoding.utf8)
^
fatal error: Error raised at top level: The operation could not be completed: file /home/buildnode/jenkins/workspace/oss-swift-4.0-package-linux-ubuntu-16_10/swift/stdlib/public/core/ErrorType.swift, line 187
Current stack trace:
0 libswiftCore.so 0x00007f089c352bc0 _swift_stdlib_reportFatalErrorInFile + 221
And the code is:
#! /usr/bin/swift
import Glibc
import Foundation
let albert_op = ProcessInfo.processInfo.environment["ALBERT_OP"]
extension Dictionary where Key == String {
func toPrettyJSON() throws -> String? {
let jsonD = try JSONSerialization.data(withJSONObject: self,options: [.prettyPrinted])
let jsonS = try String(data: jsonD, encoding: String.Encoding.utf8)
return jsonS
}
}
if albert_op == "METADATA" {
let metadata : [String: Any] = [
"iid": "org.albert.extension.external/v2.0",
"name": "snippets",
"version": "0.1",
"author": "lf-araujo",
"dependencies": [],
"trigger": "snip "
]
let jsonData = try JSONSerialization.data(withJSONObject: metadata)
let JSONString = String(data: jsonData, encoding: String.Encoding.utf8)!
print(JSONString)
} else if albert_op == "QUERY" {
let filemgr = FileManager.default
let filelist = try filemgr.contentsOfDirectory(atPath: "~/.snippy")
func buildItem(name: String) -> [String:Any] {
let action : [String: Any] = [
"name": name
]
return action
}
var items : [String: Any] = [:]
items["items"] = filelist.map { buildItem(name: $0) }
if let jsonStr = try? items.toPrettyJSON() {
print(jsonStr!)
}
}
exit(0)
In order to reproduce the problem, one needs to run the code with: ALBERT_OP="QUERY" ./script.swift.
What am I doing wrong in this particular case? Is it related to the fact that I am running it as a script?
I think your issue is at try filemgr.contentsOfDirectory(atPath: "~/.snippy"), specifically the fact that Swift doesn't automatically expand tildes in paths. You can manually do it with ("~/.snippy" as NSString).expandingTildeInPath
Related
I'm trying to load a plist (keys are unique words, values are their English-language definitions) into a dictionary.
I can do a one-off like this:
let definitionsFile = URL(fileURLWithPath: Bundle.main.path(forResource: "word_definitions", ofType:"plist")!)
let contents = NSDictionary(contentsOf: definitionsFile)
guard let value = contents!.object(forKey: lastGuess) as? String else {
print("value from key fail")
return
}
...but it has to load the file every time I use it. So I tried moving the code to the program loader and storing the data in the definitions dictionary (the capitalized message is the problem area):
let definitionsFile = URL(fileURLWithPath: Bundle.main.path(forResource: "word_definitions", ofType:"plist")!)
if let contents = NSDictionary(contentsOf: definitionsFile) as? [String : String] {
print("loaded definitions dictionary")
if case state.definitions = contents {
print("added definitions to state")
} else {
print("FAILED TO ADD DEFINITIONS TO STATE")
}
} else {
print("failed to load definitions dictionary")
}
It's failing at the point where I assign it to state.definitions (which is a String:String dictionary). Is there something I'm missing? Do I need to change state.definitions to String:Any and rewrite every access?
UPDATE: Based on Tom Harrington's comment, I tried explicitly creating state.definitions as a NSDictionary (removing the as [String:String] bit) and it's still not storing the dictionary.
I put your code in a Playground that both generates a plist file and then uses NSDictionary to parse it out. Here is the full playground
import Foundation
let stringPairs = [
"One" : "For the Money",
"Two" : "For the Show",
"Three" : "To Get Ready",
"Four" : "To Go",
]
let tempDirURL = FileManager.default.url(for: .itemReplacementDirectory,
in: .userDomainMask,
appropriateFor: Bundle.main.bundleURL,
create: true)
let demoFileURL = tempDirURL.appendingPathComponent("demo_plist.plist")
do {
if let plistData = try? PropertyListSerialization.data(
fromPropertyList: stringPairs,
format: .xml,
options: 0) {
try plistData.write(to: demoFileURL)
}
} catch {
print("Serializing the data failed")
}
struct State {
var definitions: [String: String]
}
var state = State(definitions: [:])
if let fileContent = NSDictionary(contentsOf: demoFileURL),
let contents = fileContent as? [String : String] {
print("loaded definitions dictionary")
state.definitions = contents
} else {
print("failed to load definitions dictionary")
}
debugPrint(state.definitions)
Note I just made up something for the state variable and its type.
It seems to work just fine and prints:
loaded definitions dictionary
["Four": "To Go", "Two": "For the Show", "One": "For the Money", "Three": "To Get Ready"]
One thing I changed was your if case ... statement. I'm entirely sure what this construct means in this context. The Swift Language Guide says an if case should be followed by a pattern and an initializer. In my code "state.definitions" is not a pattern so the if case always returns false. But it seems to me that this should be some kind of compiler error.
At any rate, by pulling the binding of contents into its own clause of the outer if I can be sure that by the time I get into the if that contents is not null.
I'm having a problem figuring out how to handle dynamic keys with a single value one level deep. I've seen a few examples but they appear to handle other cases.
Here's an example.
[
{ "asdofjiodi": "asdofidj.com" },
{ "sadjlkj": "iejjol.com" },
{ "ijijwjljlijl": "adsijf.com" },
{ "jgncmkz": "mlkjaoijf.com" }
]
Any ideas on how I accomplish this with Codable or CodingKey? This is what I'd like for the end result.
["asdofidj.com", "iejjol.com", "adsijf.com", "mlkjaoijf.com"]
let url = Bundle.main.url(forResource: "file", withExtension: "json")!
let data = try! Data(contentsOf: url)
let decoder = JSONDecoder()
// NOTE: The below line doesn't work because I'm not sure how to do the encoding/decoding
try? decoder.decode([[String: String]].self, from: data)
First of all, is not a valid JSON. A "Valid JSON" can be a JSON Array (multiple json objects) or a single JSON object (starts and ends with { and })
After cleaning this...
You are trying to make a dictionary (json object) from a data. You don't need JSONDecoder to accomplish this. Try using JSONSerialization with jsonObject static function...
let data = Data("{\"asdofjiodi\": \"asdofidj.com\",\"sadjlkj\": \"iejjol.com\",\"ijijwjljlijl\": \"adsijf.com\",\"jgncmkz\": \"mlkjaoijf.com\"}".utf8)
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
let values = json?.map({ $1 })
print(values)
I Hope I helped!
JSONSerialization Apple documentation
Here is a minimal working example, given the JSON in your question:
let json = """
[
{ "asdofjiodi": "asdofidj.com" },
{ "sadjlkj": "iejjol.com" },
{ "ijijwjljlijl": "adsijf.com" },
{ "jgncmkz": "mlkjaoijf.com" }
]
"""
let data = Data(json.utf8)
let decoder = JSONDecoder()
do {
let decodedJson = try decoder.decode([[String: String]].self, from: data)
let values = decodedJson.compactMap(\.values.first)
print(values)
} catch {
print("Error: \(error.localizedDescription)")
}
If this doesn't appear to work for you, it may be related to how you load in the JSON.
I am rewriting a project I found on Github to learn and teach myself how to use swift and pod files. I upgraded Kanna from 2.2.1 to 4.0.2 because I was getting an arm64 error.
With 4.0.2 I am getting the error:
Initializer for conditional binding must have Optional type, not 'HTMLDocument'
Call can throw, but it is not marked with 'try' and the error is not handled
I am unsure about what this error means and how to fix it. It is associated with this if statement:
if let doc = Kanna.HTML(html: htmlText, encoding: String.Encoding.utf8) {
for itemSize in doc.css("option[value^='']") {
let itemSizeText = itemSize.text!.lowercased()
let wishListItemSize = self.websiteInstance!.websiteWishListItem.size!.lowercased()
if itemSizeText.range(of: wishListItemSize) != nil {
print("Found size")
foundItemSize = true
let itemSizeValue = itemSize["value"]
self.websiteInstance!.viewController!.websiteBrowser!.evaluateJavaScript("document.getElementById(\"size-options\").value = \(itemSizeValue!)", completionHandler: nil)
break
}
countSize += 1
}
}
The type signature for the method you are calling is public func HTML(html: String, url: String? = nil, encoding: String.Encoding, option: ParseOption = kDefaultHtmlParseOption) throws -> HTMLDocument. The function returns a non-Optional value, but can throw an error.
You can handle the error by either using the try? keyword to make the function return nil in case an error was thrown and make the optional binding you currently use work like this:
if let doc = try? Kanna.HTML(html: htmlText, encoding: String.Encoding.utf8) {...
or rather use try and put the function call in a do-catch block to see the actual error in case any was thrown.
do {
let doc = Kanna.HTML(html: htmlText, encoding: String.Encoding.utf8)
for itemSize in doc.css("option[value^='']") {
let itemSizeText = itemSize.text!.lowercased()
let wishListItemSize = self.websiteInstance!.websiteWishListItem.size!.lowercased()
if itemSizeText.range(of: wishListItemSize) != nil {
print("Found size")
foundItemSize = true
let itemSizeValue = itemSize["value"]
self.websiteInstance!.viewController!.websiteBrowser!.evaluateJavaScript("document.getElementById(\"size-options\").value = \(itemSizeValue!)", completionHandler: nil)
break
}
countSize += 1
}
} catch {
print(error)
// Handle error
}
I need to send JSON format in post api using alamofire in swift. Way data need to send is
{
"data": [{
"id": "1015683997",
"name": "Pawel"
}, {
"id": "108350039247",
"name": "Randeep"
}, {
"id": "115607797616",
"name": "Mohit"
}]
}
And way i am able to generate as of now is:
["data": {
data = (
{
id = 101583997;
name = "Pawel";
},
{
id = 108359247;
name = "Randeep";
},
{
id = 11567616;
name = "Mohit ";
}
);
}
]
Using the below mentioned way to generate json format.
for i in 0..<self.arrFriendsList.count
{
let dictFrndList:[String: AnyObject] = [
"id" : arrFriendsList[i].valueForKey("id")!,"name" : arrFriendsList[i].valueForKey("name")!
]
arrFrndList.addObject(dictFrndList)
}
Then,
let dictFBFrndList: [String: AnyObject] = [
"data":arrFrndList
]
Then,
let params: [String: AnyObject] = [
"access_token" : accessToken,
"data":dictFBFriends
]
So, please guide me. i have already spend more than a day on it. thanks in advance.
I second what Larme said, you should post "arrFrndList" at the params with key "data" but not dictFBFrndList.
Managed this way
let jsonData = try! NSJSONSerialization.dataWithJSONObject(arrFriendsList, options: NSJSONWritingOptions.PrettyPrinted)
let jsonString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)! as String
print(jsonString)
let params: [String: AnyObject] = [
"access_token" : accessToken,
"data":jsonString
]
let options = NSJSONWritingOptions()
let data = try? NSJSONSerialization.dataWithJSONObject(parameters, options: options)
let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string:"http:/myurl.com")
if let str = NSString(data: data!, encoding: NSUTF8StringEncoding) as? String {
print(str)
}
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
mutableURLRequest.setValue("your_access_token", forHTTPHeaderField: "Authorization")
mutableURLRequest.HTTPBody = data
mutableURLRequest.HTTPMethod = "POST"
Alamofire.request(mutableURLRequest).responseJSON(){
json in
if let value = json.result.value {
let _json = JSON(value)
if (!_json.isEmpty){
print(_json)
}
}
}
you can check how to seen your JSON array this code. Look at 5th cell code. This code will write your json array for you in output screen and you can coppy from there and paste in here then you will see how to send data.
Maybe this way can help you .
I have a dictionary which i convert to a string to store it in a database.
var Dictionary =
[
"Example 1" : "1",
"Example 2" : "2",
"Example 3" : "3"
]
And i use the
Dictionary.description
to get the string.
I can store this in a database perfectly but when i read it back, obviously its a string.
"[Example 2: 2, Example 3: 3, Example 1: 1]"
I want to convert it back to i can assess it like
Dictionary["Example 2"]
How do i go about doing that?
Thanks
What the description text is isn't guaranteed to be stable across SDK versions so I wouldn't rely on it.
Your best bet is to use JSON as the intermediate format with NSJSONSerialization. Convert from dictionary to JSON string and back.
I created a static function in a string helper class which you can then call.
static func convertStringToDictionary(json: String) -> [String: AnyObject]? {
if let data = json.dataUsingEncoding(NSUTF8StringEncoding) {
var error: NSError?
let json = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error) as? [String: AnyObject]
if let error = error {
println(error)
}
return json
}
return nil
}
Then you can call it like this
if let dict = StringHelper.convertStringToDictionary(string) {
//do something with dict
}
this is exactly what I am doing right now. Considering #gregheo saying "description.text is not guaranteed to be stable across SKD version" description.text could change in format-writing so its not very wise to rely on.
I believe this is the standard of doing it
let data = your dictionary
let thisJSON = try NSJSONSerialization.dataWithJSONObject(data, options: .PrettyPrinted)
let datastring:String = String(data: thisJSON, encoding: NSUTF8StringEncoding)!
you can save the datastring to coredata.