Decoding JSON with swift's Codable - json

I'm trying to download some JSON data from Wikipedia using their APIs and trying to parse it through swift's Codable. The url I'm using returns the following:
{
"batchcomplete": "",
"query": {
"pageids": [
"143540"
],
"pages": {
"143540": {
"pageid": 143540,
"ns": 0,
"title": "Poinsettia",
"extract": "The poinsettia ( or ) (Euphorbia pulcherrima) is a commercially important plant species of the diverse spurge family (Euphorbiaceae). The species is indigenous to Mexico. It is particularly well known for its red and green foliage and is widely used in Christmas floral displays. It derives its common English name from Joel Roberts Poinsett, the first United States Minister to Mexico, who introduced the plant to the US in 1825."
}
}
}
}
in swift I have declared the following:
struct WikipediaData: Codable {
let batchComplete: String
let query: Query
enum CodingKeys : String, CodingKey {
case batchComplete = "batchcomplete"
case query
}
}
struct Query: Codable {
let pageIds: [String]
let pages: Page
enum CodingKeys : String, CodingKey {
case pageIds = "pageids"
case pages
}
}
struct Page: Codable {
let pageId: Int
let ns: Int
let title: String
let extract: String
enum CodingKeys : String, CodingKey {
case pageId = "pageid"
case ns
case title
case extract
}
}
I'm not sure if the above correctly models the data. Specifically,
let pages: Page
as there might be more than one page returned.
Also, shouldn't that be a dictionary? if so, what would the key be since it is data ("143540" in the above case) rather than a label?
I hope you could point me in the right direction.
thanks

Since your pageIds are Strings your pageId should be too. Its mesmerizingly simple to use a Dictionary at this point, in fact you can simply use
let pages: [String:Page]
and you will be done (and given a Dictionary with minimal effort). Codable alone almost seems a reason to pick up Swift. I am intrigued to see to what other interesting and elegant solutions it will lead eventually. It has already changed how I think about JSON parsing permanently. Besides: your pages really want to be a Dictionary.

Related

Decoding JSON from Bing search API

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

Decode nested JSON child with Decodable [duplicate]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
The new Swift "Decoder" class sounds like a great way to parse JSON data, but all of the examples I've found use a well-known, well-defined 'struct' to do so.
In my case I'm querying an arbitrary website that returns a HUGE JSON string and I only care about a few of the (deeply nested) fields, so I don't want to take all that time to define a 'struct' to get at them.
Is it even possible to do this with "Decoder"? And if so, how does one go about it?
The question seems to be based on a misapprehension about how Decodable works. As a convenience, Decodable is willing to do some automatic code generation behind the scenes so that you can define a struct or nest of structs and just decode the entirety of the JSON. But you are not required to take advantage of that in order to decode JSON.
There is no need to define struct properties for "fields" you don't care about. If a JSON dictionary contains 100 keys and your corresponding struct contains just one property, no problem; that key will be fetched, and no others.
With regard to the "deeply nested" part, it should not take you much time to write simple nested structs that perform the dive to reach the dictionary you really care about. But if you don't want to do even that, you could write an implementation of init(from:) that dives down and fetches out the desired values.
In other words, if you think of Decodable as consisting primarily of your implementation of init(from:), and learn to write the code that it needs, you will see that this JSON can be parsed in a few quick simple lines of code.
As an example, here's a JSON sketch of a deeply nested piece of information with a bunch of extra information at every level that we want to ignore:
{
"ignore": true,
"outer1": {
"ignore": true,
"outer2": {
"ignore": true,
"outer3": {
"name": "matt",
"ignore": true
}
}
}
}
What I'd like to do is define a very simple struct Person that consists solely of the deeply nested name:
struct Person : Decodable {
let name : String
}
I can do that! To do so, I implement Decodable myself, supplying a "hoover" CodingKey adopter struct and an implementation of init(from:), like this (this may look like a lot of work, but it isn't, because the AnyCodingKey implementation is boilerplate, copied and pasted from here, and the init(coder:) implementation is just a few lines of code that were easy to write):
struct Person : Decodable {
let name : String
struct AnyCodingKey : CodingKey {
var stringValue: String
var intValue: Int?
init(_ codingKey: CodingKey) {
self.stringValue = codingKey.stringValue
self.intValue = codingKey.intValue
}
init(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
var con = try! decoder.container(keyedBy: AnyCodingKey.self)
con = try! con.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue:"outer1"))
con = try! con.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue:"outer2"))
con = try! con.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue:"outer3"))
let name = try! con.decode(String.self, forKey: AnyCodingKey(stringValue:"name"))
self.name = name
}
}
When I want to dive into the JSON and grab the name information, it's trivial:
let person = try! JSONDecoder().decode(Person.self, from: json)
The result is a Person object with name value "matt". Note that I didn't have to add any of the ignore keys and I didn't need to make a nest of structs.
Sure you can achieve this but with both JSonSerialization & Decodable , you have to serialize the json until reach the desired content then decode it ,but instead I recommend to create structs for root keys only then decode , think of it as it's a path from top to bottom don't decode a key that isn't in the path of your desired content

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.

Swift JSON Serialization typeMismatch

Currently struggling how to use Decodable. I've done some googling to the errors I'm getting but I still believe that the way i'm structuring the structs isn't correct but it seems to make sense to me.
I've also tried using optionals
In the error that I've posted at the end, I'm confused about the reference to the Double type. As I don't have any type or anything int he response that uses a double.
(I'm also able to serialize the json reponse using the old swift method of casting the data as dictionaries - [String : Any]. But I'd like to use the modern/updated approach.)
JSON Response
{"NEWS":
[
{
"DATE":"2018-10-13T03:56:06+1000",
"SOURCE":"smh.com.au",
"BLURB":"Assistant Treasurer Stuart Robert says he has repaid $37,975 of \"excess usage charges\" in home internet bills footed by taxpayers.",
"ID":102347,
"TITLE":"Stuart Robert pays back $38,000 in excessive home internet charges"
},
{
"DATE":"2018-10-12T18:00:38+1000",
"SOURCE":"itwire.com",
"BLURB":"The CVC costs set by the NBN Co make it very difficult for ISPs to offer gigabit connections to more than a select band of customers who are willing to sign up in numbers and pay slightly more than other speed tiers, according to one ISP who caters to this type of consumer.",
"ID":102343,
"TITLE":"NBN gigabit connections will remain mostly a pipe dream"},
{
"DATE":"2018-10-12T09:48:43+1000",
"SOURCE":"computerworld.com.au",
"BLURB":"The Department of Home Affairs has rejects calls to include independent judicial oversight of the decision to issue Technical Assistance Notices and Technical Capability Notices as part of proposed legislation intended to tackle police agencies’ inability to access encrypted communications services.",
"ID":102342,
"TITLE":"Home Affairs rejects calls for additional safeguards in ‘spyware’ law"
},
{
"DATE":"2018-10-11T12:16:05+1000",
"SOURCE":"itnews.com.au",
"BLURB":"NBN Co is hoping to “speed up” building works on the fibre-to-the-curb (FTTC) portion of its network as it tries to make up lost ground.",
"ID":102334,
"TITLE":"NBN Co says fibre-to-the-curb build is more complex that it hoped"
},
]
}
CODE
struct Root: Decodable {
let news: [News]?
enum CodingKeys: String, CodingKey {
case news = "NEWS"
}
}
struct News: Decodable {
let date: Date
let source, blurb: String
let id: Int
let title: String
enum CodingKeys: String, CodingKey {
case date = "DATE"
case source = "SOURCE"
case blurb = "BLURB"
case id = "ID"
case title = "TITLE"
}
}
Serialization
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let dataStr = data else {
return
}
do {
let root = try JSONDecoder().decode(Root.self, from: dataStr) //error is caught here
guard let r = root else { return }
print(r.news)
} catch let err {
print("JSON Error - \(err)")
}
}.resume()
Error
error serializing json typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "NEWS", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "DATE", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
This is because the default coding strategy for Date is double (seconds since epoch). You can change the default strategy to iso8061 or anything custom. For example, you can set the date formatter in your decoder like so:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
Putting the JSON into Quicktype gives me an extra comma error. Unless there's more nodes to the json file? So make sure about that first. At first glance, I could be wrong, but I think you have to format the DATE to exactly match.

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'
}
}