Decoding JSON from Bing search API - json

I'm trying to use the BingAPI in Swift which has no guide or directions. I'm so close but I can't figure out what type is webpages (
_type and query context are in the correct format, but I don't know how to write webPages.)
error code:
"typeMismatch(Swift.Dictionary<Swift.String, Swift.String>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "webPages", intValue: nil), _JSONKey(stringValue: "value", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, String> but found an array instead.", underlyingError: nil))"
Swift
struct codableData: Codable {
var _type: String
var queryContext: [String : String]
var webPages : [String : [String : String]] // I know it's not right, but here is the problem
}
json results
{
"_type": "SearchResponse",
"queryContext": {
"originalQuery": ""
},
"webPages": {
"totalEstimatedMatches": 20600000,
"value": [
{
"id": "https://api.bing.microsoft.com/api/v7/#WebPages.8",
"name": "tafeqld.edu.au",
"url": "https://tafeqld.edu.au/courses/18106/",
"isFamilyFriendly": true,
"displayUrl": "https://tafeqld.edu.au/courses/18106",
"snippet": "Moved Permanently. The document has moved here.",
"dateLastCrawled": "2023-01-02T12:02:00.0000000Z",
"language": "en",
"isNavigational": false
}
],
"someResultsRemoved": true
},
"rankingResponse": {
"mainline": {
"items": [
{
"answerType": "WebPages",
"resultIndex": 0,
"value": {
"id": "https://api.bing.microsoft.com/api/v7/#WebPages.0"
}
}
]
}
}
}

webPages is not [String: [String: String]] as the value types inside it include numbers as well as other objects which are not simple [String: String] dictionaries either. Like the error is telling you, value is an array and you're trying to decode it as a dictionary.
You could simply change it to [String: Any].
But you'll also benefit from Codable more if you write types matching the structure of the expected JSON.
For example:
struct CodableData: Codable {
let _type: String
let queryContext: QueryContext
let webPages: WebPage
}
struct QueryContext: Codable {
let originalQuery: String
}
struct WebPage: Codable {
let totalEstimatedMatches: Int
let value: [Foo]
let someResultsRemoved: Bool
}
// etc.
Note you only need to define objects and properties for the bits you're interested in.

Notice that the JSON Syntax indicates an Object when it s {} and an Array when it is [].
JSON has a JavaScript origin and stores types from the JavaScript world. JavaScript is not strongly typed and you can have dictionaries and arrays with mixed types.
So in JavaScript to access the WebPages name for example you would do something like BingAPIResponse.webPages.value[0].name and you can do exactly the same in Swift too however you will have to model your Codable struct to match this exact structure with sub-structures because in JavaScript you don't have a guarantee that the array in webPages.value will have all the same types and there is no guarantee that webPages.value[0].name is a string, for example.
You won't be able to use [String : Any] because Any is not decodable and you will get an error if you put values of type Any in you Codable Struct.
This is not a shortcoming of Swift but a result of trying to work with a data structure which doesn't have types in a language with strict types.
So if you want to decode it just using JSONDecoder, you will need to create a Codable struct for each sub object the way #shim described.
Personally I find it too tedious and I built myself a small library to handle JSON, which you can find here: https://github.com/mrtksn/DirectJSON
What this library does is letting you access parts of the JSON before converting them into the type you need. So the type safety is still guaranteed but you can access the data from the JSON using the JavaScript notation without modelling the whole JSON object. It is useful if you are not interested in having the complete model of the JSON object but just want some data from it.
So for example, to access the name of the webpage, you will do:
// add the library to the imports
import DirectJSON
/* Do yourAPI and retrieve the json */
let theJSONResponseString = BingAPICall()
/* Get the part of the data you are interested in */
let name : String? = theJSONResponseString.json.webPages.value[0].name
Note that you can use it with any Codable. So if you are after webPages data, you can have something like:
struct WebPages : Codable {
let id : String
let name : String
let url : String
let isFamilyFriendly : Bool
}
// then simply
let webPages : [WebPages]? = theJSONResponseString.json.webPages.value

Related

Decoding JSON dictionaries in Swift - "Expected to decode String but found a dictionary instead"

I'm new to Swift and programming. I am having some trouble decoding a JSON file.
I have JSON that looks like:
{
"actors": {
"2048": "Gary Busey",
"3": "Harrison Ford",
"5251": "Jack Warden",
"14343": "Rene Russo",
"51214": "Brad Renfro",
"9560": "Ellen Burstyn"
}
}
My model is:
struct Actors: Codable {
let actors: [String: String]
}
I'm using this extension from hackingwithswift.com to decode the JSON.
When I call:
let actors = Bundle.main.decode([String: String].self, from: "actors.json")
I get this error:
"Failed to decode actors.json from bundle due to type mismatch – Expected to decode Array but found a dictionary instead."
I feel like I am missing something simple. Any ideas on what I am missing here?
Decoding with let actors = Bundle.main.decode([String: String].self, from: "actors.json") would work if you had a JSON like this:
{
"2048": "Gary Busey",
"3": "Harrison Ford",
"5251": "Jack Warden",
"14343": "Rene Russo",
"51214": "Brad Renfro",
"9560": "Ellen Burstyn"
}
But you have additionally a dictionary with a string key and a dictionary value. Try this:
let actors = Bundle.main.decode([[String: [String: String]].self, from: "actors.json")
Or this:
let actors = Bundle.main.decode(Actors.self, from: "actors.json")

escaped json string on Swift

In an iPhone app using SWIFT, I'm having to deal with a third-party API that sends a escaped string instead of a JSON object as response.
The response looks like this:
"[
{
\"ID\":3880,
\"Name\":\"Exploration And Production Inc.\",
\"ContractNumber\":\"123-123\",
\"Location\":\"Booker #1\",
\"Volume\":1225.75,
\"OtherFees\":10.0
}
]"
Up until now I have been dealing with this by manipulating the string to remove the unwanted characters until I get a JSON-like string and then parsing that as usual.
Angular has a handy function to deal with this:
angular.fromJson(response.data);
Java has its own way to deal with it. Is an equivalent function in Swift?
If you are parsing it to a dictionary then the simplest solution will be to convert String to Data and use JSONSerialization:
if let data = string.data(using: .utf8) {
do {
let responseArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
print(responseArray)
}
catch {
print(error)
}
}
But ofcourse it would be better to process it as a Codable model in which case its again just as simple as:
try JSONDecoder().decode([Item].self, from: data)
Provided that you have a valid Decodable model like so:
struct Item: Decodable {
let id: Int
let name: String
//add the other keys you want to use (note its type sensitive)
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
}
}
Lastly, avoid stringified jsons because its an easy source of errors.
Malformed strings or small/large deviations in the structure can go easily unnoticed.
Let the backend team know that they should follow a protocol in their API that its consumers can rely on.
By setting the json format, its essentially like a contract that showcases its content and purpose with clarity.
Sending a stringified json is simply lazy and reflects poorly on its designer, imho.

How do I decode a JSON file containing arrays Swift 4?

I have a JSON file which I'm trying to decode but am getting an error message:
typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
My code is as follows:
struct Phrase: Decodable {
let sentences: [String]
}
func getFromJSON() {
do {
let jsonURL = Bundle.main.url(forResource: "script", withExtension: "json")
let jsonDecoder = JSONDecoder()
let jsonData = try Data(contentsOf: jsonURL!)
let jsonSentence = try jsonDecoder.decode([Phrase].self, from: jsonData)
debugPrint(jsonSentence)
} catch {
print(error)
}
}
I've been looking at lots of other stack overflow questions and I've noticed that the format of these JSON files are different to mine. They are formatted as dictionaries while mine is like this-
[
["bin blue", "with A 2 soon"],
["set blue", "in A 9 soon"],
["lay blue", "at A 3 please"],
["3 5 zero zero 5 8"],
["place blue", "by A 6 now"],
["lay green", "at B ZERO now"],
["8 9 1 5 4 zero"]
]
I know that the decoder is looking for a dictionary but how do I have it decode the arrays instead?
I think you need
let jsonSentence = try jsonDecoder.decode([[String]].self, from: jsonData)
Your JSON data does not match the structure you want to parse. The JSON is an array of string arrays, whereas you try to parse an array of Phrase objects.
Solution 1: to keep the JSON structure as-is, you need to parse an array of arrays, or to keep the custom model type, by using a type alias:
typealias Phrase = [String]
Solution 2: to keep the struct you defined as-is, you'll have to change the JSON format to this;
[
{ "sentences": ["bin blue", "with A 2 soon"] },
{ "sentences": ["set blue", "in A 9 soon"] },
...
]
Both solutions will work with your getFromJSON implementation.

How can I decode a generic JSON response in Swift 4?

I'm building an app with Swift 4 that consumes a JSON-RPC API. The responses all have the same general format:
{
"jsonrpc": "2.0",
"result" : { "data_type" : [ ...a bunch of instances of data_type... ] }
"id": 1
}
Where data_type would be payments, channels, peers, and so on depending on the query.
I have Decodable struct definitions for each of the data types, but I don't know how to handle the main response.
I really don't care about the jsonrpc or id fields, I'm just interested in the contents of result.
I tried:
struct LightningRPCResponse: Decodable {
let id: Int
let result: String
let json_rpc: String
}
But I got the error:
Expected to decode String but found a dictionary instead
So I tried:
struct LightningRPCResponse: Decodable {
let id: Int
let result: Dictionary
let json_rpc: String
}
But I got the error:
Reference to generic type 'Dictionary' requires arguments in <...>
Is what I'm trying to do possible or do I need to create separate response decoders to correspond to every single RPC request?
Or...should I just use string manipulation to lop off the superfluous data?
You could make two structs:
struct generalStruct:Codable {
let jsonrpc:String
let id:Int
let result:[resultsStruct]
}
struct resultsStruct{
//assuming that you have strings in here, cause you didn't specify that. And it's considered as a Dictionary like: "data_tupe":"string_value" or if you have an array also here than just make another struct or just make data_type:[String]
let data_type:String
}
With that structs you can decode now. Example:
let json = try decoder.decode(generalStruct.self, from: response.data!)
//here you can get access to each element of your 'data_type'
for obj in json.result{
for data in obj.data_type {
//you have every element from dict access here if its more objects inside every 'data_type'
}
}

Get array key value with Alamofire

I am making a simple request to get specific data from a JSON file, but I am having issues getting the exact data.
The JSON file:
{
"Something1": {
"Added": "09-10-2016",
"Expires": "09-12-2016",
"Reliability": "78%",
"Views": "2",
"Priority": "High"
},
"Something2": {
"Added": "09-11-2016",
"Expires": "09-13-2016",
"Reliability": "98%",
"Views": "5",
"Priority": "Low"
}
}
The swift code:
Alamofire.request("https://example.com/args.json").responseJSON { response in
if let JSON = response.result.value as? [String:AnyObject] {
print(JSON["Something1"])
}
}
With print(JSON["Something1"]), it prints everything for Something1 just like it's supposed to but an error is thrown when I try to do print(JSON["Something1"]["Views"]) for example. How would I go about fixing this?
Your problem isn't related to Alamofire I'm afraid, it's more of deal with JSON using Swift. In your case when you make your first optional binding you're converting to a [String: AnyObject] and it's correct and this implies that you can subscript JSON["Something1"].
But after that when you try to subscript again over the JSON["Something1"]["Views"] the compiler doesn't know what have the JSON["Something1"] so you can't using as a dictionary instead you need to cast it again to a dictionary because is nested using optional binding like this:
if let nestedDictionary1 = JSON["Something1"] as? [String: AnyObject] {
// access individual value in dictionary
if let views = nestedDictionary1["Views"] as? Int {
print(views)
}
}
You can read more about the work with JSON in this article of Apple.
I hope this help you.