Home > Net >  Swiftui Decoding Json from Bing search api
Swiftui Decoding Json from Bing search api

Time:01-12

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
  • Related