Swift: convert [Dictionary<String, [String : Double]>.Element] to [String : [String : Double]] - swift

I have this JSON string:
[
{
"name": "first_category",
"model": {
"OK": 0.49404761904761907,
"NOPE": 0.48214285714285715
}
},
{
"name": "second_category",
"model": {
"YOP": 0.389338593,
"GO": 0.20420894
}
}
]
I have created this struct to decode it:
struct JSONModel: Codable {
let name: String
let model: [String: Double]
}
The decoding:
let decodedModel = try? decoder.decode([JSONModel].self, from: Data(jsonString.utf8))
It correctly fills the array as expected, but now I would like to use this array to create a dictionary whose keys would be the JSONModel names, and values the models. This is what I expect to get:
[
"first_category": [
"OK": 0.49404761904761907,
"NOPE": 0.4821428571428571
],
"second_category": [
"YOP": 0.389338593,
"GO": 0.20420894
]
]
So I tried this:
let simplifiedModel: [String: [String: Double]] = decodedModel.flatMap { [$0.name: $0.model] }
But I'm getting this error:
Cannot convert value of type '[Dictionary<String, [String : Double]>.Element]' to closure result type '[String : [String : Double]]'
What should I do instead?
Thank you for your help

I would use reduce(into:) for this
let dictionary = decodedModel.reduce(into: [:]) {
$0[$1.name] = $1.model
}

Related

swift can not make struct for parsing geoJSON

I have geoJSON file:
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
40.303141,
55.9765684
],
[
40.3033449,
55.9765114
],
[
40.3034017,
55.976575
],
[
40.3031979,
55.9766321
],
[
40.303141,
55.9765684
]
]
]
]
},
"properties": {
"#id": 4305947573,
"building": "yes"
}
}
I'm interested in properties:
"properties":{"#id":4305947573,"building":"yes"}
I want parse "properties", and make structure:
struct Feature: Decodable {
let type: String
let properties: Dictionary<String, String> }
It's work good, but then i add parameter in geoJSON: "#id":4305947573
4305947573 - this is Int variable, and parser don't parse geoJSON.
I think i need modify my struct Feature. I want to parser understand and String, and Int in properties.
Help me please. Thank you
There are a number of GeoJSON swift libraries (search github) that you could use
instead of re-inventing the wheel.
If you really want to code it yourself, try this approach,
where the dynamic keys and values of properties are decoded
into a dictionary of var data: [String: Any], as shown.
Use the struct models like this:
let result = try JSONDecoder().decode(Feature.self, from: data)
Models
struct Feature: Decodable {
let type: String
var properties: Properties
// ...
}
struct Properties: Decodable {
var data: [String: Any] = [:]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicKey.self)
container.allKeys.forEach { key in
if let theString = try? container.decode(String.self, forKey: key) {
self.data[key.stringValue] = theString
}
if let theInt = try? container.decode(Int.self, forKey: key) {
self.data[key.stringValue] = theInt
}
}
}
}
struct DynamicKey: CodingKey {
var intValue: Int?
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = ""
}
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
}

Can I request a two-dimensional associative array in swift Moya?

The following two forms of data were successfully requested.
{
"ride_fare": 1000,
"km": 7
]
}
{
"ride_fare": 1000,
"km": 7,
"options": [ 0, 1, 2]
}
However, I don't know how to request a two-dimensional associative array like the one below.
How can I request it?
{
"ride_fare": 1000,
"km": 7,
"option_fares": [
{
"price": 200,
"name": "立ち寄り",
"id": 1
}
]
}
The code that I wrote:
var options = [Any]()
for option in optionFares {
let params = [
"id" : option.id ?? 0,
"name" : option.name ?? "",
"price" : option.price ?? 0
] as [String : Any]
options.append(params)
}
let faresData = [
"id" : driverOrder.id ?? 0,
"km" : driverOrder.distance ?? 0,
"option_fares" : options,
"ride_fare" : driverOrder.ride_fare ?? 0
] as [String : Any]
First, create a struct that matches the json format you want to request.
struct Params: Codable {
let rideFare, km: Int
let optionFares: [OptionFare]
enum CodingKeys: String, CodingKey {
case rideFare = "ride_fare"
case km
case optionFares = "option_fares"
}
}
struct OptionFare: Codable {
let price: Int
let name: String
let id: Int
}
And you must create a request parameter in Moya's task.
import Moya
extension APITarget: TargetType {
var task: Task {
case .yourCaseName(let price, let name, let id, let rideFare, let km):
let encoder: JSONEncoder = JSONEncoder()
let optionFareData: [OptionFare] = []
optionFareData.append(OptionFare(price, name, id))
let paramsData = Params(rideFare, km, optionFareData)
let jsonData: Data = try! encoder.encode(paramsData)
return .requestData(jsonData)
}
}
}

How to make a struct with `variable: [String: Codable]` codable?

I got a struct like the following
struct Wrapper {
var value: [String: Any]
// type "Any" could be String, Int or [String].
// i.g. ["a": 1, "b": ["ccc"]]
// and the keys of this dictionary are not determined
}
I been struggled for quite a while😭.
Anyone has any idea how to resolve it?
You can use some library like AnyCodable
Then you can make your struct Codable by using AnyCodable class instead of Any.
struct Wrapper: Codable {
var value: [String: AnyCodable]
}
Example
let arrayWrapper: [String: Any] =
["value" :
[
"test" : ["1", "2", "3"],
"parse" : ["4", "5", "6"]]
]
let jsonData = try! JSONSerialization.data(withJSONObject: arrayWrapper, options: .prettyPrinted)
do {
let decoder = JSONDecoder()
let result = try decoder.decode(Wrapper.self, from: jsonData)
print("result:", result)
} catch let error {
print("error:", error)
}
Output
result: Wrapper(value: ["parse": AnyCodable(["4", "5", "6"]), "test": AnyCodable(["1", "2", "3"])])

Send JSON array as a parameter Alamofire

I'm having difficulty with a parameter which contains a Json array using Alamofire
Here is a string of my Json:
"[{\"id\":546836102,\"count\":1},{\"id\":216479424,\"count\":1}]"
Here is my code where I make the request:
let data = (params["cart"] as! String).data(using: .utf8)!
do {
if let jsonArray = try JSONSerialization.jsonObject(with: data, options : .fragmentsAllowed) as? [Dictionary<String, Int>] {
let parameters: Parameters = [
"address_id": params["address_id"] as! Int,
"delivery_day": params["delivery_day"] as! String,
"delivery_hour": params["delivery_hour"] as! Int,
"cart": jsonArray,
"via": params["via"] as! String
]
Alamofire.request(Global.baseURL + "orders/finish", method: .post, parameters: parameters, encoding: URLEncoding.default, headers: header)
.responseSwiftyJSON {
Printing out my parameters
["cart": [["id": 546836102, "count": 1], ["count": 1, "id": 216479424]], "address_id": 641589205, "delivery_day": "1399-01-20", "delivery_hour": 21, "via": "ios"]
Backend must receive the cart as below:
[
{
"id": 123456,
"count": 2
},
{
"id": 654321,
"count": 3
}
]
But instead it gets the data like this:
{
"delivery_hour" : "21",
"delivery_day" : "1399-01-20",
"cart" : [
{
"count" : "1"
},
{
"id" : "546836102"
},
{
"count" : "1"
},
{
"id" : "216479424"
}
],
"via" : "ios",
"address_id" : "641589205"
}
I have tried JSONEncoding and URLEncoding options by Alamofire but nothing seems to work and this is the closest I've gotten to the API template so far.
What am I doing so wrong here?
// MARK: - Update
So I updated to the latest version of Alamofire and still no good results :(
Here is the code:
let payload = MyParameters(address_id: 641589205, delivery_day: "1399-01-21", delivery_hour: 21, cart: items, via: "ios")
AF.request(url, method: .post, parameters: payload, encoder: JSONParameterEncoder.default, headers: .init(header), interceptor: nil).response { dataResponse in
switch dataResponse.result {
case .success(let value):
if dataResponse.response?.statusCode == 200 {
let json = JSON(value!)
completionHandler(json, nil)
} else {
print(dataResponse.response?.statusCode ?? -1)
}
case .failure(let error):
print(error)
completionHandler(nil, error.localizedDescription)
}
}
My payload looks exactly what I want it to be
{
"cart" : [
{
"count" : 1,
"id" : 546836102
},
{
"count" : 1,
"id" : 216479424
}
],
"via" : "ios",
"address_id" : 641589205,
"delivery_day" : "1399-01-21",
"delivery_hour" : 21
}
But then again the endpoint receives this:
{
"{\"delivery_day\":\"1399-01-21\",\"address_id\":641589205,\"delivery_hour\":21,\"cart\":" : {
"{\"id\":546836102,\"count\":1},{\"id\":216479424,\"count\":1}" : null
}
}
Using Alamofire 4, you can't send arrays using the method you've shown, as the Parameters type is [String: Any]. You're also not using the correct ParameterEncoding. If you want to send a JSON body, you need to use the JSONEncoding. What you can do is manually serialize the array into a URLRequest and make that request using Alamofire.
Additionally, if you update to Alamofire 5, Encodable parameters are now supported, so you if you can define your parameters as an Encodable type, sending them would be as easy as passing an array.
AF.request(..., parameters: [some, encodable, values], encoder: JSONEncoder.default)...

Alamofire Encoding issue with struct

I have a rather complex structure to encode in order to pass it with the body of my post.
case .sendData(let parameters):
urlRequest = try Alamofire.JSONEncoding.default.encode(urlRequest, withJSONObject: parameters.encode())
parameters is not of the [String:Any] type, but of the type 'QuestionnaireAnswers' which is a struct. So I need to create the encode functions. I tried the code below but it fails
struct ItemResponse {
let question: String
let answerArray: [String]
}
// MARK: Encodable
extension ItemResponse {
func encode() -> [String: Any] {
return [
"question": question,
"answerArray": answerArray
]
}
}
struct QuestionnaireAnswers{
let version: Int
var answers: [ItemResponse]
}
// MARK: Encodable
extension QuestionnaireAnswers {
func encode() -> [String: Any] {
return [
"version": version,
"answers": answers
]
}
}
In the end, the JSON should look like this :
{
version: 1,
answers: [
{ question: 'dog', answerArray: ['yay'] },
{ question: 'cat', answerArray: ['< 500'] },
{ question: 'tree', answerArray: ['sorry', 'OK then','NotWorking'] },
],
}