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.
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 have a JSON array at url = https://api.github.com/users/greenrobot/starred .
I want to count how many objects are there in that array using swift. I don't want data present, I just want count of it.
Assuming you downloaded the contents of that URL into a variable data of type Data:
if let object = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: AnyHashable]] {
let count = object[0].keys.count
}
I assume that you are using Alamofire for making a network request. In the code below we are just extracting the value object from Alamofire result. We convert the value to Array of dictionaries and then you can just get the count.
AF.request("https://api.github.com/users/greenrobot/starred",method: .get).responseJSON { apiResponse in
switch apiResponse.result{
case .success(_):
let dictionary = apiResponse.value as? [[String:Any]]
print("dictionaryCount \(dictionary?.count ?? -1)")
case .failure(_):
print("error \(apiResponse.error?.underlyingError?.localizedDescription ?? "")")
}
}
The GitHub starred API returns a maximum of 30 items by default, in the case of greenrobot with a total number of 372 it's not meaningful.
A smart way to get the actual number of starred items is to specify one item per page and to parse the Link header of the HTTP response which contains the number of the last page
Task {
do {
let url = URL(string: "https://api.github.com/users/greenrobot/starred?per_page=1")!
let (_, response) = try await URLSession.shared.data(from: url)
guard let link = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "Link") else {
throw URLError(.badServerResponse)
}
let regex = try NSRegularExpression(pattern: "page=(\\d+)")
if let lastMatch = regex.matches(in: link).last {
let range = Range(lastMatch.range(at: 1), in: link)!
let numberOfStarredItems = String(link[range])
print(numberOfStarredItems)
} else {
print("No match found")
}
} catch {
print(error)
}
}
I'm fairly new to this. Anyway, here we go:
I have JSON data that comes from an API. For the sake of this question, I have simplified it greatly. You can run the following code in a Playground.
import UIKit
struct Book: Codable {
let image: String
}
// this comes from my API
let jsonString = "{ \"image\" = \"someURL\" }"
print(jsonString) // { "image" = "someURL" }
// convert String to Data
let jsonData = jsonString.data(using: .utf8)
// decode data (in my project, I catch the error, of course)
let decoder = JSONDecoder()
let decodingResult = try? decoder.decode(Book.self, from: jsonData!)
print(decodingResult) // nil
As you can see, I'm trying to decode my JSON-String into an Object (my Struct), but the Decoder always returns nil.
Can someone point me in the right direction?
Thank you.
Your current jsonString isn't a proper JSON. Change it to "{ \"image\": \"someURL\" }", and it should work. For more information on JSON syntax, check this manual.
I am getting the opposite value of a boolean when using .boolValue
I have this code.
let _ = channel.bind(eventName: "like-unlike-cake", callback:
{(data:Any?)->Void in
let likedCake = JSON(data!)["like"]
print("liked cake: \(likedCake)")
print("Boolean: \(likedCake["liked_by_current_user"].boolValue)")
liked cake: {
"liked_by_current_user" : true
}
}
Boolean: false
But when I use .bool it gives me nil. Does someone help me on this.
UPDATE This would be the json I want to fetch
{
"like": {
"id": "66642122-7737-4eac-94d2-09c9a35cbef8",
"liked_by_current_user": true
}
}
Try the following:
let _ = channel.bind(eventName: "like-unlike-cake", callback {
(data:Any?)->Void in
if let jsonData = data {
let json = JSON(jsonData)
let isLikedByCurrentUser = json["like"]["liked_by_current_user"].boolValue
print(isLikedByCurrentUser)
} else {
// your data is nil
debugPrint("data error")
}
}
also you can try to cast your data: Any into Data by replacing the line:
if let jsonData = data as? Data {
let json = JSON(data: jsonData)...
cause sometimes SwiftyJSON have problems with creating JSON objects from Any.
I have asked this seemingly very simple and straight forward question a few times now, never got a solution.
I have a url to obtain mySql data
// 1. get url
let url = URL(string:"http://www.mobwebplanet.com/phpWebService/sample.php")
// 2. Fetch data from url
let data = try? Data(contentsOf: url!)
//playground Output is: 102 bytes. So obviously xcode gets the response data from the URL.
Then I move on to extracting the data:
//3. Create a dictionary from data:
let urlDict = try? JSONSerialization.jsonObject(with: data!, options: [])
// playground Output is: [["Latitude": "37.331741", "Address": "1 Infinite Loop Cupertino, CA", "Name": "Apple", "Longitude": "-122"]]
print(urlDict!)
// playground Output is: "(\n {\n Address = "1 Infinite Loop Cupertino, CA";\n Latitude = "37.331741";\n Longitude = "-122";\n Name = Apple;\n }\n)\n"
My understanding is urlDict is of a Any type. Am I correct?
My biggest question is how can I (cast or convet) urlDict so that I can access the value using key=>value? Like this:
urlDict!["Address"] Outputs "1 Infinite Loop Cupertino, CA"
urlDict!["Latitude"] Outputs "37.331741"...
I am a newbie to Swift so I am doing this as an exercise, any help will be greatly appreciated.
Your JSON response returns an Array of Dictionary objects. So you just need to cast correctly.
let urlString = "http://www.mobwebplanet.com/phpWebService/sample.php"
let url = URL(string: urlString)!
let data = try? Data(contentsOf: url)
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [[String:Any]] {
for location in json! {
print(location["Longitude"])
print(location["Latitude"])
print(location["Address"])
}
}
Output:
Optional(-122)
Optional(37.331741)
Optional(1 Infinite Loop Cupertino, CA)