struct AltWX: Decodable {
var raw: String
}
typealias AltMetarDecode = [String: AltWX]
do
{
let aero = try decoder.decode(AltMetarDecode.self, from: data)
DispatchQueue.main.async {
if self.DownloadOrderCode == 3
{
if let Ab = aero[ICAO1]
{
self.Alternate1Metar.text = Ab.raw
UserDefaults.standard.set(Ab.raw, forKey: "MetarCodeAlt1")
}
}
...
The above code accesses a Json like this:
"xxxx": { "raw": "Test"
so Ab.raw would print Test
How can I add an error check to see if ICAO1 exists and also if Ab.raw exists, and if any values are nil return something? I read the usual catch responses to Json but not sure how to format it for this case specifically especially that i have a lot of order codes and each has to be checked separately.
This checks if ICAO1 exists and raw is empty
if let raw = aero["ICAO1"]?.raw, raw.isEmpty {
}
Please name variables lowercased according to the naming convention.
I think you want a do try catch block for this.
enum ContentError: Error {
case missingICAO1
case missingRaw
case orderCodeError
}
// not sure of the return types from the decode block ???
func setValues(values: AltMetarDecode) throws {
guard self.DownloadOrderCode == 3 else { throw ContentError.orderCodeError }
guard let Ab = values[ICA01] else { throw ContentError.missingICA01 }
guard Ab.raw != nil else { throw ContentError.missingRaw }
self.Alternate1Metar.test = Ab.raw
UserDefaults.standard.set(Ab.raw, forKey: "MetarCodeAlt1")
}
do {
let aero = try decoder.decode(AltMetaDecode.self, from data)
try DispatchQueue.main.async {
setValues(values: aero)
}
} catch {
print(error)
}
Setting up a function to throw errors may not be the most code efficient way, but to me it makes the code clear and readable.
Related
Problem
The data is successfully decoded from Reddit’s api and put into the variable theUser inside the .onAppear {…}, but when I try to use it I keep getting nil values.
Code
struct ContentView: View {
#State var didAppear = false
#State var theUser = getNilUser()
var body: some View {
Text("User Profile")
Text(theUser.data.subreddit.display_name_prefixed ?? "No name found")
.onAppear(perform: {
theUser = getUser(withName: "markregg")
})
}
func getUser(withName username: String) -> user {
if !didAppear {
if let url = URL(string: "https://www.reddit.com/user/\(username)/about.json") {
do {
let data = try Data(contentsOf: url)
do {
let decodedUser = try JSONDecoder().decode(user.self, from: data)
return decodedUser
} catch {
print(error)
}
} catch {
print(error)
}
}
didAppear = true
}
return getNilUser()
}
}
Edit: I’m very new to this so if I made a stupid mistake I’m sorry
You can't get JSON data like that. You have to execute a HTTP request using the URLSession class in order to get the data and parse it.
let username = "markregg"
let url = URL(string: "https://www.reddit.com/user/\(username)/about.json")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
} else if let data = data {
do {
let decodedUser = try JSONDecoder().decode(User.self, from: data)
} catch {
print(error)
}
} else {
print("Request failed")
}
}.resume()
Another thing to note is that URLSession methods are asynchronous. In simple terms it means they take some time to execute and return the result. So you can't use a simple return statement like return decodedUser to get the result right away. Look into how to use URLSession more.
I get a nil in let restResponse = try? JSONDecoder().decode(SelectCourseResponse.self, from: data) any reasons ?
func getMotherLangRequest() {
showActivityIndicatory(uiView: view, show: true)
AF.request(NetworkUrl.motherLang_getRequest,
method: .get)
.responseJSON { response in
self.showActivityIndicatory(uiView: self.view, show: false)
debugPrint(response)
switch response.result {
case .success:
guard let data = response.data else { return }
let restResponse = try? JSONDecoder().decode(SelectCourseResponse.self, from: data)
if restResponse?.status == 0 {
} else { self.showErrorResponse(data: data) }
case let .failure(error): print(error)
}
}
}
here get request
My SelectCourseResponse struct
Your price, icon and update_date fields are null declare them as optional and your code will work.
I would recommend using a do/catch block and printing out the error. That said, you have a number of null fields in your JSON object but you didn't define those fields as optional in your data structures. update_date, price, and icon should all be optionals.
do {
let restResponse = try JSONDecoder().decode(
SelectCourseResponse.self,
from: data
)
process(restReponse)
} catch {
print("DECODE ERROR: \(error)")
}
I have reduced my dictionary keys successfully in this question as pseudo-code without a real json model. The goal which I accomplished in the previous question is to return only the keys that have matching values. So the output is a dictionary that looks something like this ["WoW": ["#jade", "#kalel"]. Exactly what I needed. Of course there could be other matches and I'd like to return those as well.
Now that I have a proper json model, the reduce function is printing out an empty dictionary [:]. Is it the type in .reduce(into: [String:[String]]() that is causing the issue?
All the data is printing so the json and model structure must be correct.
json
[
{
"id": "tokenID-tqkif48",
"name": "#jade",
"game": "WoW",
"age": "18"
},
{
"id": "tokenID-fvkif21",
"name": "#kalel",
"game": "WoW",
"age": "20"
}
]
UserModel
public typealias Users = [UserModel]
public struct UserModel: Codable {
public let name: String
public let game: String
// etc...
enum CodingKeys: String, CodingKey {
case name
case game
// etc...
Playground
guard let url = Bundle.main.url(forResource: "Users", withExtension: "json") else {
fatalError()
}
guard let data = try? Data(contentsOf: url) else {
fatalError()
}
let decoder = JSONDecoder()
do {
let response = try decoder.decode([UserModel].self, from: data)
for userModel in response {
let userDict: [String:String] = [ userModel.name:userModel.game ]
let reduction = Dictionary(grouping: userDict.keys) { userDict[$0] ?? "" }.reduce(into: [String:[String]](), { (result, element) in
if element.value.count > 1 {
result[element.key] = element.value
}
})
// error catch etc
}
Your code is too complicated. You can group the array by game simply with
let response = try decoder.decode([UserModel].self, from: data)
let reduction = Dictionary(grouping: response, by: {$0.game}).mapValues{ usermodel in usermodel.map{ $0.name}}
UPDATE I may be mistaking what you want to get. There's another code below and please check the results and choose one you want.
If you want to use reduce(into:updateAccumulatingResult:), you can write something like this.
do {
let response = try decoder.decode([UserModel].self, from: data)
let userArray: [(name: String, game: String)] = response.map {($0.name, $0.game)}
let reduction = userArray.reduce(into: [String:[String]]()) {result, element in
if !element.game.isEmpty {
result[element.name, default: []].append(element.game)
}
}
print(reduction)
} catch {
print(error)
}
If you prefer an initializer of Dictionary, this may work:
do {
let response = try decoder.decode([UserModel].self, from: data)
let userArray: [(name: String, games: [String])] = response.map {
($0.name, $0.game.isEmpty ? [] : [$0.game])
}
let reduction = Dictionary(userArray) {old, new in old + new}
print(reduction)
} catch {
print(error)
}
Both output:
["#jade": ["WoW"], "#kalel": ["WoW"]]
Anyway, your way of combining loop, Dictionary(grouping:) and reduce(into:) in addition of userDict.keys is making things too complex than they should be.
ADDITION When you want to get a Dictionary with keys as games:
do {
let response = try decoder.decode([UserModel].self, from: data)
let userArray: [(game: String, name: String)] = response.compactMap {
$0.game.isEmpty ? nil : ($0.game, $0.name)
}
let reduction = userArray.reduce(into: [String:[String]]()) {result, element in
result[element.game, default: []].append(element.name)
}
print(reduction)
} catch {
print(error)
}
Or:
do {
let response = try decoder.decode([UserModel].self, from: data)
let userArray: [(game: String, names: [String])] = response.compactMap {
$0.game.isEmpty ? nil : ($0.game, [$0.name])
}
let reduction = Dictionary(userArray) {old, new in old + new}
print(reduction)
} catch {
print(error)
}
Output:
["WoW": ["#jade", "#kalel"]]
I'm trying to add JSON deserializing functionality inside my protocols, but I can't get the decode function to compile. How do I write the initializer without forcibly unwrapping the value?
protocol Mappable: Codable {
init?(jsonString: String)
}
extension Mappable {
init?(jsonString: String) {
guard let data = jsonString.data(using: .utf8) else {
return nil
}
// TODO:- Value of optional type 'Self?' not unwrapped error
self = try? JSONDecoder().decode(Self.self, from: data)
}
}
try? returns an optional. But in a failable initializer, you don't assign nil to self, you return nil.
The following works:
extension Mappable {
init?(jsonString: String) {
guard let data = jsonString.data(using: .utf8) else {
return nil
}
do {
self = try JSONDecoder().decode(Self.self, from: data)
} catch {
return nil
}
}
}
As others have stated, a few other things to consider:
String data(using:) with an encoding of .utf8 can't fail so the guard can be replaced with a forced-unwrapped.
Consider changing this from a failable initializer to one that throws.
A throwable initializer would work better here:
protocol Mappable: Codable {
init(jsonString: String) throws
}
extension Mappable {
init(jsonString: String) throws {
let data = jsonString.data(using: .utf8)! // this will never be nil
self = try JSONDecoder().decode(Self.self, from: data)
}
}
This way your protocol doesn't swallow errors and callers can simply use try? on their side, if they don't care about the error.
I am trying to parse JSON with the following code:
func ltchandler(response: NSURLResponse!, data : NSData!, error : NSError!) { //Is passed the results of a NSURLRequest
if ((error) != nil) {
//Error Handling Stuff
} else {
if (NSString(data:data, encoding:NSUTF8StringEncoding) == "") {
//Error Handling Stuff
} else {
var data = NSData(data: data);
// Define JSON string
var JSONString = "\(data)"
// Get NSData using string
if let JSONData = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
// Parse JSONData into JSON object
var parsingError: NSError?
if let JSONObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parsingError) as? [String: AnyObject] {
// If the parsing was successful grab the rate object
var rateObject: Double! = JSONObject["price"]?.doubleValue
// Make sure the rate object is the expected type
if let rate = rateObject as? Double! { // THIS IS NOT WORKING!!!
//Do stuff with data
} else {
println("Parsing Issue")
}
}
}
}
}
}
The line marked THIS IS NOT WORKING!!! is not being called.
From what I can tell, it cannot cast rateObject as a double - why not? It is not showing any errors.
To clarify, the expected behavior is that a double is created from the JSON object.
To strictly answer your question have you tried printing the rateObject. Also why are you casting to Double! rather than just Double in the problematic line?
Personally I don't use ! in almost all cases. You are better off using either non-optionals or proper optionals.
In the relevent section I would write:
// Make sure the rate object is the expected type
if let rate = JSONObject["price"]?.doubleValue {
//Do stuff with rate
} else {
print("Parsing Issue")
}
Of course if the JSONObject["price"] is not something with a doubleValue method or the method returns nil you will end up with nil and the else case being taken.
the code worked for me, try this code:
// if the value equals nil or any String, the instruction abort the if
// SWIFT 2.0 in xcode beta 5
if let rate = Double((JSONObject["price"] as? String)!){
// insert you code here
} else {
print("error message")
}