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"
}
}
]
}
}
}
CodePudding user response:
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.
CodePudding user response:
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