0

I'm using RxSwift to help me achieve asynchronous HTTP requests in my osx application. My HTTP requests are modeled as a stream that returns an Observable<Any?>. The Any? value that is returned is a jsonObject created from the data received in the HTTP response:

let data = try? JSONSerialization.jsonObject(with: data)    
return Observable.just(data)

After making an HTTP request, I then subscribe to the stream, waiting for the next value to arrive. After it arrives, I save the data as a [String: Any] before proceeding:

let onNextSubscription = httpResponse?.subscribe(onNext: { data in
    if let data = data as? [String: Any] {
        print(data)
        ...
    }
}

In this scenario, I am evaluating a CheckVersion method in my application to ensure that it is of a version that is currently supported. To accomplish this, I call out to a service that sends back JSON data regarding which versions of the application are currently supported and where to find them on the web if the evaluation fails. I then use data from the response in conjunction with data from within the app to evaluate whether or not the application is supported and proceed accordingly. That is, I will compare the "minVersion" value from the JSON data with the app's version to determine the result of the CheckVersion method.

The data I receive from the HTTP response is:

["status": 0, "result": {
    "minVersion":"1.7.0",
    "currVersion":"2.0.0",
    "urls":{
        "2.0.0":"/downloads/app.2.0.0.exe",
        "1.9.0":"/downloads/app.1.9.0.exe",
        "1.8.0":"/downloads/app.1.8.0.exe",
        "1.7.0":"/downloads/app.1.7.0.exe",
    }
}]

My next step is to retrieve the "minVersion" value from the data, so I continue within the same if {...} block shown above:

let result = data["result"] as? [String: Any]
print(result)

let minVersion = result["minVersion"] as? String
print(minVersion)

...

What I expect to be able to do is access the value as result["minVersion] before executing my version comparison logic; however, I am unable to, because data["result"] == nil after it is downcast to [String: Any].

When data["result"] is forcibly downcast to [String: Any], I receive the following error:

Could not cast value of type '__NSCFString' to 'NSDictionary'.

I'm not sure why this is going on as other posts I've seen around the web have shown that this is a normal means of accessing nested JSON data, although maybe I'm missing something.

My best guess is that it has to do with the fact that result["urls"] values contains a jsonObject of its own, while result's other values are simply strings.

Here are some of documents that I referenced while modeling my code:

  1. https://developer.apple.com/swift/blog/?id=37
  2. How to access nested JSON in brackets in Swift?

I'm a C# .NET developer by trade, and this is my first foray into OS X and Swift world, so thanks in advance for the help and for bearing with me on this!

Edit: Here is the result of print(data["result"]):

Optional({
    "minVersion":"1.7.0",
    "currVersion":"2.0.0",
    "urls":{
        "2.0.0":"/downloads/app.2.0.0.exe",
        "1.9.0":"/downloads/app.1.9.0.exe",
        "1.8.0":"/downloads/app.1.8.0.exe",
        "1.7.0":"/downloads/app.1.7.0.exe",
    }
})

Edit: As per Dan's request, I tried the method outlined in https://stackoverflow.com/questions/30480672/....

let subNext = httpResponse?.subscribe(onNext: { data in
    if let data = data as? [String: Any] {
        print(data)

        let result = self.convertToDictionary(text: data["result"] as! String)
        print(result)
    }
})

The result is:

Optional(["currVersion": 2.0.0, "urls": {
    "1.7.0" = "/downloads/app.1.7.0.exe",
    "1.8.0" = "/downloads/app.1.8.0.exe",
    "1.9.0" = "/downloads/app.1.9.0.exe",
    "2.0.0" = "/downloads/app.2.0.0.exe",
}, "minVersion": 1.7.0])

Final Edit

Thanks to Rob and Dan in the comments, I was able to solve my problem. It was essentially the same as shown in https://stackoverflow.com/questions/30480672/...

I'll go ahead and flag this one as a duplicate.

ncats
  • 93
  • 11
  • The error message is telling you the `data["result"]` is a string, not a dictionary. – dan Jul 11 '17 at 17:19
  • Thanks, I understand that much. I know that I need to be able to take that string and format it into a `[String: Any]` so that I can take advantage of the key-value pairs stored within. I'm just not sure what the right approach is in trying to accomplish that. – ncats Jul 11 '17 at 17:23
  • Well if the string is a json string then see: https://stackoverflow.com/questions/30480672/how-to-convert-a-json-string-to-a-dictionary – dan Jul 11 '17 at 17:25
  • Interestingly enough, `print(JSONSerialization.isValidJSONObject(data))` returned `true` while `print(JSONSerialization.isValidJSONObject(data["result"]))` returned `false`. Then `let result = data["result"] as! [String: AnyObject]` returned the same error as before, I assume because it has been shown that data["result"] is not a valid JSON string. – ncats Jul 11 '17 at 17:29
  • So it looks like I need to determine why `data["result"]` is not valid JSON and find out how to deal with it. – ncats Jul 11 '17 at 17:35
  • That's not what `JSONSerialization.isValidJSONObject` is for. Did you try the code from the answer in the question I linked? – dan Jul 11 '17 at 17:35
  • @Rob: Yes, that is what is being shown in the console. – ncats Jul 11 '17 at 17:37
  • @Dan: I tried the code solution you recommended. I got an error: "The data couldn't be read because it isn't in the correct format." – ncats Jul 11 '17 at 17:43
  • @Rob: Our platform uses services that are written in C#. Could this be the reason? – ncats Jul 11 '17 at 17:44
  • Can you do a `print(data["result"])` and edit the output into your question? – dan Jul 11 '17 at 17:44
  • @Rob: Thanks for the response. I'll definitely look into this. – ncats Jul 11 '17 at 17:59
  • @Dan: I've updated the question as you asked. – ncats Jul 11 '17 at 18:01
  • @ncats I don't see where you've shown the result of `print(data["result"])`. Your attempt to try the method I linked is also wrong, you need to properly unwrap `data["result"]` as a string. `String(describing:)` won't produce a valid json string. – dan Jul 11 '17 at 18:05
  • @Dan: Sorry about that. How does it look now? – ncats Jul 11 '17 at 18:16
  • @Rob: Thank you for spotting that mistake! This seems to have produced a value. I'll edit my question to show the true response. – ncats Jul 11 '17 at 18:33
  • Thanks, anyway for the help, @Rob. I greatly appreciate the time you spent helping me out. – ncats Jul 11 '17 at 18:38
  • In your final edit, you have an optional dictionary. You can now unwrap that, e.g. `if let result = self.convertToDictionary(text: data["result"] as! String), let minVersion = result["minVersion"] as? String { print(minVersion) }`. – Rob Jul 11 '17 at 18:45
  • @Dan: I've edited my question to show the true response after Rob pointed out a mistake I made. – ncats Jul 11 '17 at 18:48
  • This is wonderful. I've got it working. Thank you both for all your help! – ncats Jul 11 '17 at 18:51

0 Answers0