Get array key value with Alamofire - json

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.

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

How to make json keys as a special order in Swift JSONEncoder?

I need make a request to a server of an open API. The rule of the open API asked json parameter must make keys as a special order(not the A-Z order nor Z-A order).
struct Req : Encodable {
let SourceText: String
let Target: String
let Source: String
let ProjectId: Int = 0
}
What I want is:
{"SourceText":"你好","Source":"zh","Target":"en","ProjectId":0}
But the encoded json is:
{"ProjectId":0,"Source":"zh","SourceText":"你好","Target":"en"}
With JSONEncoder i think that isn't possible.
One possible workaround is to use key placeholders, e.g. keyA, keyB, keyC, let them sort them alphabetically and then just replace them using a dictionary in the resulting JSON from this question
I hope is helpful.
However, JSON is:
"An object is an unordered collection of zero or more name/value
pairs, where a name is a string and a value is a string, number,
boolean, null, object, or array."
RFC 7159
One workaround here that is ok if you don't have to many properties or custom types is to build a string containing the json and then convert it to Data
extension Req {
var jsonString: String {
String(format: #"{""SourceText": "%#, "Source": %#, "Target": %#, "ProjectId": %d}"#,
SourceText, Source, Target, ProjectId)
}
}
or directly returning Data?
extension Req {
var json: Data? {
String(format: #"{""SourceText": "%#, "Source": %#, "Target": %#, "ProjectId": %d}"#,
SourceText, Source, Target, ProjectId).data(using: .utf8)
}
}

Decoding JSON dictionaries in Swift - "Expected to decode String but found a dictionary instead"

I'm new to Swift and programming. I am having some trouble decoding a JSON file.
I have JSON that looks like:
{
"actors": {
"2048": "Gary Busey",
"3": "Harrison Ford",
"5251": "Jack Warden",
"14343": "Rene Russo",
"51214": "Brad Renfro",
"9560": "Ellen Burstyn"
}
}
My model is:
struct Actors: Codable {
let actors: [String: String]
}
I'm using this extension from hackingwithswift.com to decode the JSON.
When I call:
let actors = Bundle.main.decode([String: String].self, from: "actors.json")
I get this error:
"Failed to decode actors.json from bundle due to type mismatch – Expected to decode Array but found a dictionary instead."
I feel like I am missing something simple. Any ideas on what I am missing here?
Decoding with let actors = Bundle.main.decode([String: String].self, from: "actors.json") would work if you had a JSON like this:
{
"2048": "Gary Busey",
"3": "Harrison Ford",
"5251": "Jack Warden",
"14343": "Rene Russo",
"51214": "Brad Renfro",
"9560": "Ellen Burstyn"
}
But you have additionally a dictionary with a string key and a dictionary value. Try this:
let actors = Bundle.main.decode([[String: [String: String]].self, from: "actors.json")
Or this:
let actors = Bundle.main.decode(Actors.self, from: "actors.json")

Swift - Using PATCH Method to update receive errors

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

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.