Sorry if this question is too repetitive here but I cant figured out how to made it :(
I need to send data to an API (https://www.opticutter.com/public/doc/api#introduction) my data comes from somes TextFields and this is what the API expect.
curl -H "Content-type: application/json" \
-H "Authorization: Bearer <TOKEN>" \
-X POST -d '{
"stocks" : [{
"length" : "60",
"width" : "40",
"count": "10",
"grainDirection": null
},{
"length" : "40",
"width" : "35",
"grainDirection": null
}],
"requirements" : [{
"length" : "30",
"width" : "20",
"count": "8",
"grainDirection": null
},
{
"width" : "20",
"length" : "20",
"count": "3",
"grainDirection": null
}],
"settings" : {
"kerf": "0"
}
}'
So I already have the code to make the request but I cant realize how convert the data into that.
Hope do you understand what I need.
Thanks in advance!
The best approach in my opinion is to map your data into structs/classes and use the Decodable protocol.
struct Stock: Encodable {
let length: Int
let width: Int
let count: Int
let grainDirection: Int?
}
struct Requirement: Encodable {
let length: Int
let width: Int
let count: Int
let grainDirection: Int?
}
struct Setting: Encodable {
let kerf: Int
}
struct MyObject: Encodable {
let stocks: [Stock]
let requirements: [Requirement]
let settings: [Setting]
}
Then you build the MyObject with the data from the textfields:
let myObjectData = MyObject(stocks: [
Stock(length: 60, width: 40, count: 10, grainDirection: nil),
Stock(length: 40, width: 35, count: 0, grainDirection: nil)
],
requirements: [
Requirement(length: 30, width: 20, count: 8, grainDirection: nil),
Requirement(length: 20, width: 20, count: 3, grainDirection: nil),
],
settings: [Setting(kerf: 0)])
Finally on the request you would have to encode the data, converting it into a JSON String for example:
do {
let jsonData = try JSONEncoder().encode(myObjectData)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString)
} catch {
debugPrint(error)
}
This will produce a json string with the following result:
{
"stocks":[
{
"width":40,
"count":10,
"length":60
},
{
"width":35,
"count":0,
"length":40
}
],
"requirements":[
{
"width":20,
"count":8,
"length":30
},
{
"width":20,
"count":3,
"length":20
}
],
"settings":[
{
"kerf":0
}
]
}
I'm new to programming so apologies if the fix is a simple one. I'm trying to get the JSON data from the Alamofire request to show up not as an optional in the console.
I've already tried response.data which does give me the data as an optional but I don't know how to unwrap that optional in this call. I've searched and have seen that result.value might be closer to what I need. Below is what I have so far. This results in a "Cannot convert value of type '[String : Any]' to expected argument type 'Data'" error.
JSON File-->
"forecasts": [
{
"dateLabel": "今日",
"telop": "晴時々曇",
"date": "2019-08-16",
"temperature": {
"min": null,
"max": null
},
"image": {
"width": 50,
"url": "http://weather.livedoor.com/img/icon/2.gif",
"title": "晴時々曇",
"height": 31
}
},
{
"dateLabel": "明日",
"telop": "晴時々曇",
"date": "2019-08-17",
"temperature": {
"min": {
"celsius": "27",
"fahrenheit": "80.6"
},
"max": {
"celsius": "37",
"fahrenheit": "98.6"
}
},
"image": {
"width": 50,
"url": "http://weather.livedoor.com/img/icon/2.gif",
"title": "晴時々曇",
"height": 31
}
},
{
"dateLabel": "明後日",
"telop": "晴時々曇",
"date": "2019-08-18",
"temperature": {
"min": null,
"max": null
},
"image": {
"width": 50,
"url": "http://weather.livedoor.com/img/icon/2.gif",
"title": "晴時々曇",
"height": 31
}
}
],
"location": {
"city": "東京",
"area": "関東",
"prefecture": "東京都"
},
"publicTime": "2019-08-16T17:00:00+0900",
"copyright": {
"provider": [
{
"link": "http://tenki.jp/",
"name": "日本気象協会"
}
],
"link": "http://weather.livedoor.com/",
"title": "(C) LINE Corporation",
"image": {
"width": 118,
"link": "http://weather.livedoor.com/",
"url": "http://weather.livedoor.com/img/cmn/livedoor.gif",
"title": "livedoor 天気情報",
"height": 26
}
}
Data model-->
import Foundation
import Alamofire
// MARK: - WeatherData
struct WeatherData: Codable {
let forecasts: [Forecast]
}
struct Forecast: Codable {
let dateLabel, telop, date: String
let temperature: Temperature
let image: Image
enum CodingKeys: String, CodingKey {
case dateLabel = "dateLabel"
case telop = "telop"
case date = "date"
case temperature
case image
}
}
struct Image: Codable {
let width: Int
let url: String
let title: String
let height: Int
enum CodingKeys: String, CodingKey {
case width = "width"
case url = "url"
case title = "title"
case height = "height"
}
}
struct Temperature: Codable {
let min, max: Max?
enum CodingKeys: String, CodingKey {
case min = "min"
case max = "max"
}
}
struct Max: Codable {
let celsius, fahrenheit: String
enum CodingKeys: String, CodingKey {
case celsius = "celsius"
case fahrenheit = "fahrenheit"
}
}
viewcontroller-->
import UIKit
import Alamofire
class ForecastTableViewController: UITableViewController {
let WEATHER_URL = "http://weather.livedoor.com/forecast/webservice/json/v1?city=130010"
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(WEATHER_URL).responseJSON { (response) in
if let data = response.result.value as? [String: Any]{
let decoder = JSONDecoder()
let forecast = try? decoder.decode(WeatherData.self, from: data)
print(forecast?.forecasts)
}
}
}
My ultimate goal is to print out the JSON data into a tableview, including the images and dates. I think being able to unwrap this optional is the first step before I figure out the next part.
I've already tried response.data which does give me the data as an optional but I don't know how to unwrap that optional in this call.
You definitely should learn how to unwrap optionals properly. It basically comes down to what do you want to do when the value is nil. response.data could be nil when somehow the data could not be fetched, there's no internet, the server doesn't respond, or whatever reason it might be.
Think about what you want to happen in such a situation. Do you want to show an error as an alert to the user? Do you want to try again, or just do nothing?
And then use this code:
Alamofire.request(WEATHER_URL).responseData { (response) in
guard let data = response.data else {
// do the stuff you want to do when the data failed to be fetched
return
}
let decoder = JSONDecoder()
guard let forecast = try? decoder.decode(WeatherData.self, from: data) else {
// do the stuff you want to do when the data is not in the right format
return
}
print(forecast?.forecasts)
}
If you want to access the raw Data returned from a request, you need to use responseData. responseJSON parses the response using JSONSerialization and gives you an Any value. responseData gives you the raw Data returned, so you can unwrap it as you're currently doing and use the JSONDecoder.
You can also update to Alamofire 5 (currently in beta) and use responseDecodable to be able to parse Decodable types automatically.
I have a struct type which lists different types which must be decoded from JSON and I cannot find any information on how to specify more than one type in my enum called CodingKeys. In the innermost dictionary sometimes one key:value pair will be [String:String] and another key:value pair will be [String:Int]
I have tried specifying just String and CodingKey as in the code snippet below but Xcode reports 2 error messages.
struct JSONSiteData : Codable {
let destinationAddresses : [String]
let originAddresses : [String]
let rows : [ [String : [[String:[[String: [String:Any]]]]]] ]
let status : String
}
enum CodingKeys : String, CodingKey {
case destinationAddresses = "destination_addresses"
case originAddresses = "origin_addresses"
case rows
case status
}
I get the following error messages from Xcode;
Type 'JSONSiteData' does not conform to protocol 'Decodable'
Type 'JSONSiteData' does not conform to protocol 'Encodable'
Here is my JSON;
{
"destination_addresses": [
"1 Dunwell Ln, Bolam, Darlington DL2 2UW, UK",
"Unnamed Road, Newton Aycliffe DL5 6QZ, UK",
"Preston Manor Farm, Preston le Skerne, Newton Aycliffe DL5 6JH, United Kingdom",
"6 Middridge Farms, Middridge, Newton Aycliffe DL5 7JQ, UK",
"1 The Gardens, Hunwick, Crook DL15 0XW, UK"
],
"origin_addresses": [
"42 Drovers Way, Dunstable LU6 1AW, UK"
],
"rows": [
{
"elements": [
{
"distance": {
"text": "220 mi",
"value": 353731
},
"duration": {
"text": "3 hours 45 mins",
"value": 13475
},
"status": "OK"
},
{
"distance": {
"text": "222 mi",
"value": 356696
},
"duration": {
"text": "3 hours 45 mins",
"value": 13471
},
"status": "OK"
},
{
"distance": {
"text": "222 mi",
"value": 358053
},
"duration": {
"text": "3 hours 46 mins",
"value": 13545
},
"status": "OK"
},
{
"distance": {
"text": "225 mi",
"value": 361421
},
"duration": {
"text": "3 hours 49 mins",
"value": 13768
},
"status": "OK"
},
{
"distance": {
"text": "229 mi",
"value": 369280
},
"duration": {
"text": "3 hours 57 mins",
"value": 14238
},
"status": "OK"
}
]
}
],
"status": "OK"
}
You can try this,
// To parse the JSON, add this file to your project and do:
//
// let jsonSiteData = try? newJSONDecoder().decode(JSONSiteData.self, from: jsonData)
import Foundation
struct JSONSiteData: Codable {
let destinationAddresses, originAddresses: [String]
let rows: [Row]
let status: String
enum CodingKeys: String, CodingKey {
case destinationAddresses = "destination_addresses"
case originAddresses = "origin_addresses"
case rows, status
}
}
struct Row: Codable {
let elements: [Element]
}
struct Element: Codable {
let distance, duration: Distance
let status: String
}
struct Distance: Codable {
let text: String
let value: Int
}
Refer this link to generate models from JSON Strings.
https://app.quicktype.io/
In terms of Codable there are no different types.
Decode these structs
struct JSONSiteData : Decodable {
let destinationAddresses : [String]
let originAddresses : [String]
let rows : [Row]
let status : String
}
struct Row : Decodable {
let elements : [Element]
}
struct Element : Decodable {
let distance : Item
let duration : Item
let status : String
}
struct Item : Decodable {
let text : String
let value : Int
}
I'm currently downloading data from a server and I'm not sure how to get the response that I'm getting back into the format that's expected by the method that I'm working with. Could someone guide me on what I would need to do to get all of the items in the JSON response added into a [[String : AnyObject]] format?
Thank you in advance!!
JSON that I'm getting back
{
"Green Shirt": [
{
"id": "740",
"name": “Nice Green Shirt",
"quantity": "0",
"make": "",
"model": "",
"price": “15.00",
"size": "XXS",
"sku": null,
"image": "https:\/\/google.com\/green_shirt.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
},
{
"id": "743",
"name": "Green Shirt",
"quantity": “68",
"make": "",
"model": "",
"price": “20.00",
"size": "XS",
"sku": null,
"image": "https:\/\/google.com\/green_shirt.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
}
],
"Dark Blue Jeans": [
{
"id": "1588",
"name": "Dark Blue Jeans",
"quantity": "0",
"make": "",
"model": "",
"price": "0.00",
"size": “S",
"sku": null,
"image": "https:\/\/google.com\/dark_blue_jeans.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
"category": null
},
{
"id": "1559",
"name": "Dark Blue Jeans",
"quantity": "4",
"make": "",
"model": "",
"price": "0.00",
"size": “XL",
"sku": null,
"image": "https:\/\/google.com\/dark_blue_jeans.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
"category": null
}
],
"White Belt": [
{
"id": "1536",
"name": "White Belt",
"quantity": "37",
"make": "",
"model": "",
"price": "0.00",
"size": "One Size",
"sku": null,
"image": "https:\/\/google.com\/white_belt.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
"category": null
}
]
}
What i'm trying to do is take all the items in "Green Shirt", "Dark Blue Jeans" and "White Belt" and put them in this format [[String : AnyObject]]
// 1 - Make the HTTP Request
var endpoint = NSURL(string: "https://www.mycustomsite.com/get-inventory")
var data = NSData(contentsOfURL: endpoint!)
// 2 - Validate and Deserialize the response
if let json: NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: nil) as? NSDictionary {
}
The trick is to declare the right type for the cast.
For your data we are using [String: [[String: AnyObject]]]: a dictionary with a String as key and an array of dictionaries as value, these dictionaries have their value as AnyObject because there's several possible types.
After a successful decoding, we print the resulting dictionary (Result 1).
Then, as an example, we loop over the dictionaries contained in the array behind the key "Green Shirt", and display their ID and size.
And as a last example: get an array with all the clothes objects.
if let json = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.allZeros, error: nil) as? [String: [[String: AnyObject]]] {
// Result 1
println(json)
// Example of how to parse the data
if let allGreenShirts = json["Green Shirt"] {
for greenShirt in allGreenShirts {
if let id = greenShirt["id"] as? String, let size = greenShirt["size"] as? String {
println("ID \(id) is of size \(size)")
}
}
}
// If you want an array of all the clothes, populate an array of dictionaries:
var allClothes = [[String:AnyObject]]()
for (_, clothes) in json {
allClothes += clothes
}
println(allClothes)
}
Result 1:
[White Belt: [[size: One Size, price: 0.00, category: , make: , model: , image: https://google.com/white_belt.jpg, category_name: , new_record: 0, name: White Belt, sku: , id: 1536, quantity: 37, bar_code: ]], Green Shirt: [[size: XXS, price: 15.00, sku: , name: Nice Green Shirt, id: 740, make: , model: , image: https://google.com/green_shirt.jpg, category_name: , quantity: 0, bar_code: , new_record: 0], [size: XS, price: 20.00, sku: , name: Green Shirt, id: 743, make: , model: , image: https://google.com/green_shirt.jpg, category_name: , quantity: 68, bar_code: , new_record: 0]], Dark Blue Jeans: [[size: S, price: 0.00, category: , make: , model: , image: https://google.com/dark_blue_jeans.jpg, category_name: , new_record: 0, name: Dark Blue Jeans, sku: , id: 1588, quantity: 0, bar_code: ], [size: XL, price: 0.00, category: , make: , model: , image: https://google.com/dark_blue_jeans.jpg, category_name: , new_record: 0, name: Dark Blue Jeans, sku: , id: 1559, quantity: 4, bar_code: ]]]
Example:
ID 740 is of size XXS
ID 743 is of size XS
Array of all clothes:
[[size: One Size, price: 0.00, category: , make: , model: , image: https://google.com/white_belt.jpg, category_name: , new_record: 0, name: White Belt, sku: , id: 1536, quantity: 37, bar_code: ], [size: XXS, price: 15.00, sku: , name: Nice Green Shirt, id: 740, make: , model: , image: https://google.com/green_shirt.jpg, category_name: , quantity: 0, bar_code: , new_record: 0], [size: XS, price: 20.00, sku: , name: Green Shirt, id: 743, make: , model: , image: https://google.com/green_shirt.jpg, category_name: , quantity: 68, bar_code: , new_record: 0], [size: S, price: 0.00, category: , make: , model: , image: https://google.com/dark_blue_jeans.jpg, category_name: , new_record: 0, name: Dark Blue Jeans, sku: , id: 1588, quantity: 0, bar_code: ], [size: XL, price: 0.00, category: , make: , model: , image: https://google.com/dark_blue_jeans.jpg, category_name: , new_record: 0, name: Dark Blue Jeans, sku: , id: 1559, quantity: 4, bar_code: ]]
Note that with our mapping, we can also easily create allClothes with flatMap instead of making a loop:
let allClothes = flatMap(json, { $1 })
So, to conclude, here's our functions included in a class as an example of how you could use it.
class DataManager {
typealias myJSONDic = [String: [[String: AnyObject]]]
func getClothesDictionaryFromJSON(data: NSData) -> myJSONDic? {
if let json = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: nil) as? myJSONDic {
return json
}
return nil
}
func shirtsSizes(json: myJSONDic, category: String) -> [String] {
var shirts = [String]()
if let allShirtsInCategory = json[category] {
for shirt in allShirtsInCategory {
if let id = shirt["id"] as? String, let size = shirt["size"] as? String {
shirts.append("ID \(id) is of size \(size)")
}
}
}
return shirts
}
func getAllClothes(json: myJSONDic) -> [[String: AnyObject]] {
return flatMap(json, { $1 })
}
}
let manager = DataManager()
let clothesDictionary = manager.getClothesDictionaryFromJSON(data!)
let greenShirtsSizes = manager.shirtsSizes(clothesDictionary!, category: "Green Shirt")
let allClothes = manager.getAllClothes(clothesDictionary!)
Note that in this class example we've created a typealias for the type of our dictionary, it's more convenient to write and read.
Swift 2 update
class DataManager {
typealias myJSONDic = [String: [[String: AnyObject]]]
func getClothesDictionaryFromJSON(data: NSData) -> myJSONDic? {
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? myJSONDic {
return json
}
} catch let error as NSError {
print(error)
}
return nil
}
func shirtsSizes(json: myJSONDic, category: String) -> [String] {
var shirts = [String]()
if let allShirtsInCategory = json[category] {
for shirt in allShirtsInCategory {
if let id = shirt["id"] as? String, let size = shirt["size"] as? String {
shirts.append("ID \(id) is of size \(size)")
}
}
}
return shirts
}
func getAllClothes(json: myJSONDic) -> [[String: AnyObject]] {
return json.flatMap { $1 }
}
}
let manager = DataManager()
if let data = data, let clothesDictionary = manager.getClothesDictionaryFromJSON(data) {
let greenShirtsSizes = manager.shirtsSizes(clothesDictionary, category: "Green Shirt")
let allClothes = manager.getAllClothes(clothesDictionary)
print(greenShirtsSizes)
print(allClothes)
}
Something along these lines would parse the individual items into an array of dictionaries for you:
var items: [[String: AnyObject]] = [] //Create new array
for key in json.keys {
let group = json[key] as! [[String: AnyObject]] //Get array such as "Green Shirt"
for item in group {
items.append(item) //Loop through items and add to new array
}
}