skip to Main Content

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"
          }
        }
      ]
    }
  }
}

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search