Swift - Using PATCH Method to update receive errors - json

I am fairly new using API's and been trying to use the API with Airtable. I got the Update API to work with the Rested app. Now I am trying to build my app and utilize the data in the database. The code I am using is:
func requestUpdate(completion: #escaping (Result<Codable, Error>) -> Void) {
let url = URL(string: baseURL)
var request = URLRequest(url: url!)
request.addValue(authorizationToken, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "PATCH"
do {
let jsonEncoder = try JSONEncoder().encode(test)
request.httpBody = jsonEncoder
} catch {
print("Error")
}
let task = URLSession.shared.dataTask(with: request) {data, response, error in
print("Error: \(String(describing: error))")
print(response as Any)
}
task.resume()
}
This is the Data structure I am using to send to the API:
let test: Dictionary<String, Any> = [
"records": [
"id": "recG5MIwZ6Tp3OczR",
"fields": [
"Goods": "Other",
"Paid": 1,
"Sold": 69,
"Weight": 2,
"ETA": 2
]
]
]
The basic structure I need is
'{
"records": [
{
"id": "a id number",
"fields": {
"Goods": "Test 1",
"Paid": 200,
"Sold": 500,
"Weight": 120,
"ETA": 40
}
},
{
"id": "a id number",
"fields": {
"Goods": "Test 2",
"Paid": 150,
"Sold": 0,
"Weight": 10,
"ETA": 7
}
}
]
}'
I even tried creating some simple structs to duplicate this but kept getting an error of Invalid Top-level error in json. Which I am still investigating the error.
I get the error: Status Code: 422, Headers {...} I have only ever used GET method. I am pretty positive I am missing some code with the conversion and all. I think my error could also be with the JSON conversion. Either way I am lost an not sure where to go. Any guidance would be helpful.

When you want to encode a Dictionary<String, Any> into JSON you better use JSONSerialisation.
Alternatively, create an appropriate encodable struct for your "patch spec" (the Dictionary<String, Any>):
struct Spec: Encodable {
...
}
and then use JSONEncoder to encode the value to JSON.
Either way, in your PATCH request you assign the encoded patch spec value (a Data) the httpBody parameter from the patch request - as you have already done.
When you receive a result, you perform the mandatory error checks, and if you got a valid response data whose content type is "application/json", you again have two ways to decode it into a value representation:
Having an appropriate struct which represents the JSON from the response which also conforms to Decodable, then you can use JSONDecoder to decode the data into the value.
Otherwise, you can use JSONSerialisation and try to decode the JSON into either an Array<Any> or Dictionary<String, Any> object.
So, basically you have two approaches to decode/encode JSON. I would prefer to use JSONDecoder/JSONEncoder since it provides type safe values, and you have it much easier to deal with them, instead when having a Dictionary<String, Any>.
How to make your custom structs conform to Decodable and Encodable protocol is a different topic and there are numerous questions and answers to this. If you come to a point where you got stuck there, please ask a specific question.
How to make a proper PATCH request is also a different topic. However, the backend should already have defined a proper API for this.
While not always an easy read, the definitive bible for all HTTP related questions are the various "RFC" from the "Internet Engineering Task Force (IETF)". For a deep dive into a PATCH request you find the specification here:
PATCH Method for HTTP

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

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

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.

HTTPTask response into Swifty for JSON serialization

I'm using HTTPTask to load data from openweathermap.org. Which is working fine. I'm having trouble converting the data to JSON. I'd like to use SwiftyJSON but, I can't quite figure out how to bridge the two.
HTTPTask has a JSON Serializer, which I got working, but I rather use Swifty, it's seems easier to work with.
Here's what I have so far. This loads the weather from openweathermap.org. I'm not sure how to pass the response into Swifty.
var request = HTTPTask()
request.requestSerializer = JSONRequestSerializer()
request.responseSerializer = JSONResponseSerializer()
request.GET(openWeatherURL, parameters: ["q":"San Francisco", "APPID":openWeatherAPIKey], success: {(response: HTTPResponse) in
if let dict = response.responseObject as? Dictionary<String, AnyObject> {
println("Response: \(response)")
println("Dictionary: \(dict)")
let description = dict["weather"]["description"]
println(description)
}
}, failure: {(error: NSError, repsonse: HTTPResponse?) in
println("error \(error)")
})
SwiftyJSON is quite happy to take a variety of objects, including Dictionary?, so just go for it!
let dict = JSON(response.responseObject)