How to make JSON from nested struct - arrays

I have a nested struct:
struct Order: Codable {
let foodList: [FoodList]
let name: String
let phone: String
let email: String
}
struct FoodList: Codable {
let foodName: String
let foodID: String
let foodAmount: Int
}
I need to make JSON from this struct. It have to be like this:
{
"name": "Name",
"phone": "+1 999 999 999 999",
"email": "email#email.email",
"foodList": [
{
"foodName": "Big Mac",
"foodID": "115",
"foodAmount": 2
},
{
"foodName": "Naggets",
"foodID": "221",
"foodAmount": 5
}
]
}
But I always get this JSON )
[
{
"email": "Name",
"phone": "+1 999 999 999 999",
"foodList": {
"foodAmount": 2,
"foodID": "115",
"foodName": "Big Mac"
},
"name": "email#email.email"
},
{
"email": "email#email.email",
"phone": "+1 999 999 999 999",
"foodList": {
"foodAmount": 5,
"foodID": "221",
"foodName": "Naggets"
},
"name": "Name"
}
]
In JSON it have to be 1 Person and array of selected food.
I code JSON to struct with this code:
let orderList = [OrderList]()
for index in 0..<subMenuList.count {
if subMenuList[index].foodList.foodSelectedAmount != 0 {
orderList.append(OrderList(
foodList: FoodListOrder(
foodName: subMenuList[index].foodList.foodName,
foodID: subMenuList[index].foodList.foodID,
foodAmount: subMenuList[index].foodList.foodSelectedAmount),
name: mainView.nameTextField.text!,
phone: mainView.phoneTextField.text!,
email: mainView.emailTextField.text!))
}
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(orderList)
print(String(data: jsonData, encoding: .utf8)!)
} catch {
print(error)
}
Something wrong in "FOR" block and textfields... I think. I am appending person's data from textfields each time it loops.

Assuming Order is equal to OrderList and FoodList is equal to FoodListOrder you have to create first the FoodList array and then create the (single) Order object.
var foodList = [FoodList]()
for item in subMenuList { // don't use an index based loop if you actually don't need the index
if item.foodList.foodSelectedAmount != 0 {
foodList.append(FoodList(foodName: item.foodList.foodName,
foodID: item.foodList.foodID,
foodAmount: item.foodList.foodSelectedAmount)
}
}
let order = Order(foodList: foodList,
name: mainView.nameTextField.text!,
phone: mainView.phoneTextField.text!,
email: mainView.emailTextField.text!)
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(order)
print(String(data: jsonData, encoding: .utf8)!)
} catch {
print(error)
}

Related

Convert Encoded String To JSON Array in Swift

I have an Encoded String:
("[{"carMake":"Mercedes","phone":"03001234567","insurancePolicyNo":"0123456","email":"a#g.com","full_name":"Steven Fin","registrationNo":"02134","insuranceProvider":"Michael","carModel":"Benz"}, {"carMake":"Audi","phone":"03007654321","insurancePolicyNo":"654321","email":"b#g.com","full_name":"Flemming Smith","registrationNo":"4325","insuranceProvider":"Buttler","carModel":"A3"}]")
I want to convert this into JSON array like this:
[
{
"full_name": "Steven Finn",
"insuranceProvider": "Michael",
"insurancePolicyNo": "0123456",
"registrationNo": "02134",
"carMake": "Mercedes",
"carModel": "Benz",
"email": "a#g.com",
"phone": "03001234567"
},
{
"full_name": "Flemming Smith",
"insuranceProvider": "Buttler",
"insurancePolicyNo": "654321",
"registrationNo": "4325",
"carMake": "Audi",
"carModel": "A3",
"email": "b#g.com",
"phone": "03007654321"
}
]
After some searching, what I did was converting it to Dictionary, which results in:
[["registrationNo": 02134, "carModel": Benz, "phone": 03001234567, "email": a#g.com, "insuranceProvider": Michael, "insurancePolicyNo": 0123456, "carMake": Mercedes, "full_name": Steven Finn], ["carModel": A3, "insuranceProvider": Buttler, "carMake": Audi, "insurancePolicyNo": 654321, "full_name": Flemming Smith, "registrationNo": 4325, "phone": 03007654321, "email": b#g.com]]
Which is not the desired result.
Did anyone knows how to achieve my desired array?
your so called Encoded String is already json data. Try this to decode it into a Car model:
struct Car: Codable {
let carMake, phone, insurancePolicyNo, email: String
let full_name, registrationNo, insuranceProvider, carModel: String
}
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear {
let str = """
[{"carMake":"Mercedes","phone":"03001234567","insurancePolicyNo":"0123456","email":"a#g.com","full_name":"Steven Fin","registrationNo":"02134","insuranceProvider":"Michael","carModel":"Benz"}, {"carMake":"Audi","phone":"03007654321","insurancePolicyNo":"654321","email":"b#g.com","full_name":"Flemming Smith","registrationNo":"4325","insuranceProvider":"Buttler","carModel":"A3"}]
"""
do {
let data = str.data(using: .utf8)!
let response = try JSONDecoder().decode([Car].self, from: data)
print("\n---> response \(response)")
} catch {
print(" error \(error)")
}

swift change struct model into another model if have similar id

I get the response from the api as in the JSON code below
{
"timestamp": 1632838801,
"status": "ok",
"message": "ok",
"data": [
{
"id": 1,
"id_category": 6,
"products": [
{
"id": 12,
"product_name": "product one",
}
]
},
{
"id": 2,
"id_category": 6,
"products": [
{
"id": 20,
"product_name": "product two"
}
]
},
{
"id": 3,
"id_category": 13,
"products": [
{
"id": 994,
"product_name": "product three"
}
]
}
]
}
I'm success to decode the response into a struct like this:
struct ProductDataResponse {
let timestamp: Int
let status: Int
let message: String
let data: [Data]
}
struct Data {
let id: Int
let idCategory: Int
let products: [Product]
}
struct Product {
let id: Int
let productName: String
let idJelajah: Int
}
the problem is, I want my data model different from the response, if the data has the same id_cateogry then the existing product data will be grouped into the same id_category,below i attach the expected result of struct and the expected json.
the result is 2 array of diffrent id_category and product
struct ListCategory {
let idCategory
let category: [Category]
}
struct Category {
var id: Int
var product: [Product]
}
struct Product {
let id: Int
let productName: String
let idJelajah: Int
}
{
"timestamp": 1632838801,
"status": "ok",
"message": "ok",
"data": [
{
"id_category": 6,
"products": [
{
"id": 12,
"product_name": "product one",
},
{
"id": 20,
"product_name": "product two"
}
]
},
{
"id_category": 13,
"products": [
{
"id": 994,
"product_name": "product three"
}
]
},
]
}
With model being:
struct ProductDataResponse: Codable {
let timestamp: Double
let status: String
let message: String
let data: [Data]
struct Data: Codable {
let id: Int
let idCategory: Int
let products: [Product]
}
struct Product: Codable {
let id: Int
let productName: String
}
}
struct ProductDataOutput: Codable {
let timestamp: Double
let status: String
let message: String
let data: [Category]
struct ListCategory: Codable {
let idCategory: Int
let category: [Category]
}
struct Category: Codable {
var id: Int
var product: [Product]
}
struct Product: Codable {
let id: Int
let productName: String
}
}
Side note, to be working with your current code, I changed timestamp into a Double & status into a String, else it would cause decoding issue.
I also put them inside each other, to avoid naming. For instance, you'd need to differentiate Swift.Data & ProductDataResponse.Data, and the two Products would collide.
Also, I added a top level structure that you didn't give: ProductDataOutput.
And a helper for converting Product into Product (ie the one with structure of the response, and the one with the target one). Of course, we could use the same struct, but I wanted to separate them, just in case as you wrote it yourself.
extension ProductDataOutput.Product {
init(with otherProduct: ProductDataResponse.Product) {
self.id = otherProduct.id
self.productName = otherProduct.productName
}
}
The following code is inside a
do {
//HERE
} catch {
print("Error: \(error)")
}
That I'll skip, but keep it for debugging and catch issue.
Let's parse into the model as the Response first:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let parsed = try decoder.decode(ProductDataResponse.self, from: Data(initialJSONStr.utf8))
print(parsed)
Then, I'd use reduce(into:_:) to group by category id the products.
let reduced = parsed.data.reduce(into: [Int: [ProductDataOutput.Product]]()) { partialResult, current in
var existing = partialResult[current.idCategory, default: []]
let newProducts = current.products.map { ProductDataOutput.Product(with: $0) }
existing.append(contentsOf: newProducts)
partialResult[current.idCategory] = existing
}
print(reduced)
Then, let's create our target structure. I used a quick map() to transform the reduced into a [ProductDataOutput.Category].
let output = ProductDataOutput(timestamp: parsed.timestamp,
status: parsed.status,
message: parsed.message,
data: reduced.map { ProductDataOutput.Category(id: $0.key, product: $0.value) })
Then, to debug the output as JSON (as it's the sample you gave):
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .prettyPrinted
let encodedOutput = try encoder.encode(output)
print(String(data: encodedOutput, encoding: .utf8)!)
With this, you should be fine.

Decoding JSON array on Swift

I have a simple array that I want to decode. I can deal with arrays in a JSON format with creating a new struct in the model class, and attributing that struct as an array in the main struct. But in this case, the json data is already on an array with 1 element. Therefore I should get the first element in the json response. I think I need to provide a decoder before I can access anything, but I don't know how that decoder model should be. The error I'm getting is:
typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
JSON data I want to decode: (notice the data is on a array)
[
{
"name": "United States",
"topLevelDomain": [
".us"
],
"alpha2Code": "US",
"alpha3Code": "USA",
"callingCodes": [
"1"
],
"capital": "Washington, D.C.",
"altSpellings": [
"US",
"USA",
"United States of America"
],
"region": "Americas",
"subregion": "Northern America",
"population": 321645000,
"latlng": [
38,
-97
],
"demonym": "American",
"area": 9629091,
"gini": 48,
"timezones": [
"UTC-12:00",
"UTC-11:00",
"UTC-10:00",
"UTC-09:00",
"UTC-08:00",
"UTC-07:00",
"UTC-06:00",
"UTC-05:00",
"UTC-04:00",
"UTC+10:00",
"UTC+12:00"
],
"borders": [
"CAN",
"MEX"
],
"nativeName": "United States",
"numericCode": "840",
"currencies": [
"USD",
"USN",
"USS"
],
"languages": [
"en"
],
"translations": {
"de": "Vereinigte Staaten von Amerika",
"es": "Estados Unidos",
"fr": "États-Unis",
"ja": "アメリカ合衆国",
"it": "Stati Uniti D'America"
},
"relevance": "3.5"
} ]
model class:
struct CountryModel: Codable {
let country: [MyResponse]
}
struct MyResponse: Codable {
let name: String
let capital: String
}
Manager class:
struct CountryManager {
let countryURL = "https://restcountries-v1.p.rapidapi.com/name/"
func fetchData(_ countryName: String) {
let urlString = "\(countryURL)\(countryName)"
print(urlString)
performRequest(urlString)
}
func performRequest(_ urlString: String){
if let url = URL(string: urlString){
var request = URLRequest(url:url)
request.setValue("x-rapidapi-key", forHTTPHeaderField: "myapikey")
let session = URLSession(configuration: .default)
let task = session.dataTask(with: request) { (data, response, error) in
if let e = error {
print(e)
return
}
if let safeData = data {
self.parseJSON(safeData)
}
}
task.resume()
}
}
func parseJSON(_ countryData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([CountryModel].self, from: countryData)
print(decodedData[0].country)
}
catch {
print(error)
}
}
}
Most of the fields are missing from your model. Here is how it should look like instead:
struct Country: Codable {
let name: String
let topLevelDomains: [String]
let alpha2Code: String
let alpha3Code: String
let callingCodes: [String]
let capital: String
let altSpellings: [String]
let region: String
let subregion: String
let population: Int
let latlng: [Int]
let demonym: String
let area: Int
let gini: Int
let timezones: [String]
let borders: [String]
let nativeName: String
let numericCode: String
let currencies: [String]
let languages: [String]
let translation: Translation
let relevance: String
}
struct Translation: Codable {
let de: String
let es: String
let fr: String
let ja: String
let it: String
}
I found out that there was a problem in my http request. I used Alamofire in the request part and I don't experience the problem anymore. The issue seemed to be related to the decoding but I don't know. I'm posting the final code if anyone experiences the same issue.
import Foundation
import Alamofire
struct CountryManager {
let countryURL = "https://restcountries-v1.p.rapidapi.com/name/"
func fetchData(_ countryName: String) {
let urlString = "\(countryURL)\(countryName)"
print(urlString)
performRequest(urlString)
}
func performRequest(_ urlString: String){
let headers: HTTPHeaders = [
"x-rapidapi-host": "restcountries-v1.p.rapidapi.com",
"x-rapidapi-key": "apices"
]
AF.request(urlString, headers: headers).responseJSON { response in
self.parseJSON(response.data!)
}
}
func parseJSON(_ countryData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CountryAlias.self, from: countryData)
print(decodedData[0].name)
}
catch {
print(error)
}
}
}

How to get JSON array value in Swift using Codable

I'm having trouble getting the direction values from the following JSON:
"routeOptions": [
{
"name": "Jubilee",
"directions": [
"Wembley Park Underground Station",
"Stanmore Underground Station"
],
"lineIdentifier": {
"id": "jubilee",
"name": "Jubilee",
"uri": "/Line/jubilee",
"type": "Line",
"routeType": "Unknown",
"status": "Unknown"
}
}
]
I believe the directions is a JSON array, which at the moment I'm using Codable as below. I've managed to get the routeOptions name but can't seem to figure out how to get the directions as there's no specific key variable. Please can someone help?
struct RouteOptions: Codable {
let name: String?
let directions: [Directions]?
init(name: String, directions: [Directions]) {
self.name = name
self.directions = directions
}}
struct Directions: Codable {}
You need to handle directions as an array of String
struct RouteOptions: Codable {
let name: String
let directions: [String]
}
Here is an example where I fixed the json to be correct
let data = """
{ "routeOptions": [
{
"name": "Jubilee",
"directions": [
"Wembley Park Underground Station",
"Stanmore Underground Station"
],
"lineIdentifier": {
"id": "jubilee",
"name": "Jubilee",
"uri": "/Line/jubilee",
"type": "Line",
"routeType": "Unknown",
"status": "Unknown"
}
}
]}
""".data(using: .utf8)!
struct Root: Decodable {
let routeOptions: [RouteOptions]
}
struct RouteOptions: Codable {
let name: String
let directions: [String]
}
do {
let result = try JSONDecoder().decode(Root.self, from: data)
print(result.routeOptions)
} catch {
print(error)
}

Swift Codable: Array of Dictionaries

I have a JSON object from Yelp and I cannot figure out how to access the title in categories using Swift Codable. This is the JSON (with some elements removed for ease of reading):
{
"businesses": [
{
"id": "fob-poke-bar-seattle-2",
"name": "FOB Poke Bar",
"is_closed": false,
"review_count": 421,
"categories": [
{
"alias": "poke",
"title": "Poke"
},
{
"alias": "salad",
"title": "Salad"
},
{
"alias": "hawaiian",
"title": "Hawaiian"
}
],
"rating": 5,
"coordinates": {
"latitude": 47.6138005187095,
"longitude": -122.343868017197
},
"price": "$$",
"location": {
"city": "Seattle",
"zip_code": "98121",
"country": "US",
"state": "WA",
"display_address": [
"220 Blanchard St",
"Seattle, WA 98121"
]
},
}
Here it is in JSON Viewer
I access first level properties like name easily, and I access lattitude and longitude like so:
class Business: Codable {
var name: String
var rating: Double
var coordinates: Coordinates
struct Coordinates: Codable {
var latitude: Double
var longitude: Double
init(lat: Double, long: Double) {
self.latitude = lat
self.longitude = long
}
}
init(name: String, rating: Double, coordinates: Coordinates) {
self.name = name
self.rating = rating
self.coordinates = coordinates
self.categories = categories
}
}
Could anyone please point me in the right direction towards accessing categories -> title? Coordinates was easy to access but categories is an array of dictionaries. Thank you!
It's the same pattern like Coordinates except the value for categories is an array:
struct Root : Decodable {
let businesses : [Business]
}
struct Business: Decodable {
let name: String
let rating: Int
let coordinates: Coordinates
let categories : [Category]
struct Coordinates: Codable {
let latitude: Double
let longitude: Double
}
struct Category: Codable {
let alias: String
let title: String
}
}
let root = try decoder.decode(Root.self, from: data)
for business in root.businesses {
for category in business.categories {
print(category.title)
}
}

Resources