I have a problem to make a JSON from an array of struct in Swift3. I searched in Stack Overflow, nothing help me (here the screenshot). I have a struct like this:
public struct ProductObject {
var prodID: String
var prodName: String
var prodPrice: String
var imageURL: String
var qty: Int
var stock: String
var weight: String
init(prodID: String, prodName: String, prodPrice: String, imageURL: String, qty: Int, stock: String, weight: String){
self.prodID = prodID
self.prodName = prodName
self.prodPrice = prodPrice
self.imageURL = imageURL
self.qty = qty
self.stock = stock
self.weight = weight
}
}
and the array of that struct:
private var productsArray = [ProductObject]()
When the array is not empty, and then I tried to print it in another class, it shows this in debugger:
[app.cartclass.ProductObject(prodID: "2", prodName: "produk 2", prodPrice: "IDR 1000000", imageURL: "someURL", qty: 1, stock: "11", weight: "200")]
The array is not a valid JSON object. How to make it a valid JSON object? And I wonder whether this part "app.cartclass.ProductObject" is a problem or not to make it a valid JSON object?
edit:
Here's how I serialize into a JSON:
var products = [String:Any]()
for j in 0 ..< cart.numberOfItemsInCart() {
products=["\(j)":cart.getAllProduct(atIndex: j)]
}
if let valid = JSONSerialization.isValidJSONObject(products) {
do {
let jsonproducts = try JSONSerialization.data(withJSONObject: products, options: .prettyPrinted) as! [String:Any]
//print(jsonproducts)
} catch let error as NSError {
print(error)
}
} else {
print("it is not a valid JSON object");
}
If you want to make JSON from custom object then first you need to convert your custom object to Dictionary, so make one function like below in your ProductObject struct.
func convertToDictionary() -> [String : Any] {
let dic: [String: Any] = ["prodID":self.prodID, "prodName":self.prodName, "prodPrice":self.prodPrice, "imageURL":self.imageURL, "qty":qty, "stock":stock, "weight":weight]
return dic
}
Now use this function to generate Array of dictionary from Array of custom object ProductObject.
private var productsArray = [ProductObject]()
let dicArray = productsArray.map { $0.convertToDictionary() }
Here dicArray is made of type [[String:Any]], now you can use JSONSerialization to generate JSON string from this dicArray.
if let data = try? JSONSerialization.data(withJSONObject: dicArray, options: .prettyPrinted) {
let str = String(bytes: data, encoding: .utf8)
print(str)
}
Related
I need to create a JSON Object containing two JSON Arrays with a specific structure.
Here is the result I would need:
{"config": [{"battery_state" = "3.12","max_hum" = "33","mode" = "mode"}], "alarms": [{"1" = "12345678"}, {"2" = "22334455"}]}
I tried to use NSMutableDictionary and Arrays but the result is not what I'm expecting
let jsonConfigObject: NSMutableDictionary = NSMutableDictionary()
jsonConfigObject.setValue("33" as String, forKey: "max_hum" as String)
jsonConfigObject.setValue("3.12" as String, forKey: "battery_state" as String)
jsonConfigObject.setValue("mode" as String, forKey: "mode" as String)
let arrayConfig = [jsonConfigObject]
var jsonAlarmObject: NSMutableDictionary = NSMutableDictionary()
jsonAlarmObject.setValue("12345678" as String, forKey: "1" as String)
var arrayAlarms = [jsonAlarmObject]
jsonAlarmObject = NSMutableDictionary()
jsonAlarmObject.setValue("22334455" as String, forKey: "2" as String)
arrayAlarms.append(jsonAlarmObject)
let array = [["config" : arrayConfig], ["alarms" : arrayAlarms]]
The result is the following:
[["config": [{"battery_state" = "3.12";"max_hum" = 33;mode = mode;}]], ["alarms": [{1 = 12345678;}, {2 = 22334455;}]]]
any idea how I can get such JSON Structure ?
EDIT 1
I tried to use the following:
struct Config: Codable {
let battery_state, max_hum, mode: String
enum CodingKeys: String, CodingKey {
case battery_state = "battery_state"
case max_hum = "max_hum"
case mode = "mode"
}
}
var conf = Config(battery_state: "0", max_hum: "5", mode: "A")
var alarms = [[String:String]]()
alarms.append(["1":"123456"])
alarms.append(["2":"123456"])
alarms.append(["nb_alarms":String(Tag.sharedInstance.nb_alarms)])
but it gives me
{"config":[{"current_hum":"56","period":"2","battery_level":"2.9"],"alarms":[{"1":"123456"},{"2":"123456"},{"nb_alarms":"2"}]}
but I would need:
{"config":[{"current_hum":"56","period":"2","battery_level":"2.9"],"alarms":[{"1":"123456","2":"123456","nb_alarms":"2"]}
I need to change my alarm String:String but as I have plenty of data to be added in it I don't know the format of alarm I need to use...
Try this approach, using struct models (MyObject and Config) to ...create a JSON Object containing two JSON Arrays with a specific structure.
This example code shows how to code/decode your object from/to json.
struct MyObject: Codable {
let config: [Config]
let alarms: [[String: String]]
}
struct Config: Codable {
let batteryState, maxHum, mode: String
enum CodingKeys: String, CodingKey {
case batteryState = "battery_state"
case maxHum = "max_hum"
case mode
}
}
// the (corrected) json data
let json = """
{"config": [{"battery_state": "3.12","max_hum": "33","mode" : "mode"}], "alarms": [{"1": "12345678"}, {"2": "22334455"}]}
"""
// initial empty myObject
var myObject = MyObject(config: [], alarms: [])
// json string to MyObject
if let data = json.data(using: .utf8) {
do {
myObject = try JSONDecoder().decode(MyObject.self, from: data)
print("---> myObject: \(myObject)")
} catch {
print("decode error: \(error)")
}
}
// MyObject to json string
do {
let encodedData = try JSONEncoder().encode(myObject)
let theJson = String(data: encodedData, encoding: .utf8)
print("---> theJson: \(theJson!)")
} catch {
print(error)
}
// usage
myObject.config.forEach{ conf in
print("---> conf.batteryState: \(conf.batteryState)")
print("---> conf.maxHum: \(conf.maxHum)")
}
myObject.alarms.forEach{ alarm in
print("---> alarm: \(alarm)")
}
First of all, i am very sorry for the noob question, but i just cant seem to figure this out.
I am very new to coding and just started to get my feet wet with SwiftUI, following a few courses and started to dabble in trying to create some basic apps.
I am currently working on an app that does an API call and displays the data.
My issue is, im trying to put the decoded data into an array, it sounds so simple and I think i am missing something very easy, but for the life of me I cant seem to figure it out.
Below is the codable struct I have
struct Drinks: Codable, Identifiable {
let id = UUID()
let strDrink : String
let strInstructions: String
let strDrinkThumb: String?
let strIngredient1: String?
let strIngredient2: String?
let strIngredient3: String?
let strIngredient4: String?
let strIngredient5: String?
}
I want to put the ingredients into an Array so I can go through them in lists etc
import SwiftUI
struct IngredientView: View {
let drink : Drinks
let ingredientArray : [String] = [] // I want to append the ingredients here
var body: some View {
GroupBox() {
DisclosureGroup("Drink Ingredience") {
ForEach(0..<3) { item in
Divider().padding(.vertical, 2)
HStack {
Group {
// To use the array here
}
.font(Font.system(.body).bold())
Spacer(minLength: 25)
}
}
}
}
}
}
Again, sorry for the noob question that probably has a simple answer, but worth a shot asking :D
Thanks!
you could use this approach to get all your ingredients into an array and use
it in Lists. The idea is to use a function to gather all your ingredients into an array of
Ingredient objects. You could also use a computed property.
It is best to use a Ingredient object and declare it Identifiable
so that when you use them in list and ForEach, each one will be
unique, even if the names are the same.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var drinkList = [Drink]()
var body: some View {
List {
ForEach(drinkList) { drink in
VStack {
Text(drink.strDrink).foregroundColor(.blue)
Text(drink.strInstructions)
ForEach(drink.allIngredients()) { ingr in
HStack {
Text(ingr.name).foregroundColor(.red)
Text(ingr.amount).foregroundColor(.black)
}
}
}
}
}
.task {
let theResponse: ApiResponse? = await getData(from: "https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita")
if let response = theResponse {
drinkList = response.drinks
}
}
}
func getData<T: Decodable>(from urlString: String) async -> T? {
guard let url = URL(string: urlString) else {
print(URLError(.badURL))
return nil // <-- todo, deal with errors
}
do {
let (data, response) = try await URLSession.shared.data(for: URLRequest(url: url))
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print(URLError(.badServerResponse))
return nil // <-- todo, deal with errors
}
return try JSONDecoder().decode(T.self, from: data)
}
catch {
print("---> error: \(error)")
return nil // <-- todo, deal with errors
}
}
}
struct ApiResponse: Decodable {
var drinks: [Drink]
}
struct Drink: Decodable, Identifiable {
let id = UUID()
let idDrink: String
let strDrink: String
let strDrinkThumb: String
let strAlcoholic: String
let strGlass: String
let strInstructions: String
let strIngredient1: String?
let strIngredient2: String?
let strIngredient3: String?
let strIngredient4: String?
let strIngredient5: String?
let strIngredient6: String?
let strIngredient7: String?
let strIngredient8: String?
let strIngredient9: String?
let strIngredient10: String?
var strMeasure1: String?
var strMeasure2: String?
var strMeasure3: String?
var strMeasure4: String?
var strMeasure5: String?
var strMeasure6: String?
var strMeasure7: String?
var strMeasure8: String?
var strMeasure9: String?
var strMeasure10: String?
// --- here adjust to your needs, could also use a computed property
func allIngredients() -> [Ingredient] {
return [
Ingredient(name: strIngredient1 ?? "", amount: strMeasure1 ?? ""),
Ingredient(name: strIngredient2 ?? "", amount: strMeasure2 ?? ""),
Ingredient(name: strIngredient3 ?? "", amount: strMeasure3 ?? ""),
Ingredient(name: strIngredient4 ?? "", amount: strMeasure4 ?? ""),
Ingredient(name: strIngredient5 ?? "", amount: strMeasure5 ?? ""),
Ingredient(name: strIngredient6 ?? "", amount: strMeasure6 ?? ""),
Ingredient(name: strIngredient7 ?? "", amount: strMeasure7 ?? ""),
Ingredient(name: strIngredient8 ?? "", amount: strMeasure8 ?? ""),
Ingredient(name: strIngredient9 ?? "", amount: strMeasure9 ?? ""),
Ingredient(name: strIngredient10 ?? "", amount: strMeasure10 ?? "")
].filter{!$0.name.isEmpty}
}
}
struct Ingredient: Identifiable {
let id = UUID()
var name: String
var amount: String
}
change you struct to
struct Drink: Codable, Identifiable {
let id = UUID()
let strDrink : String
let strInstructions: String
let strDrinkThumb: String?
let strIngredients: [String] = []
}
wherever you want to loop through ingredients you can use drink.strIngredients array
First of all your design cannot work because you are ignoring the root object, a struct with a key drinks
struct Root : Decodable {
let drinks : [Drink]
}
A possible solution is to write a custom init method. However this a bit tricky because you have to inject dynamic CodingKeys to be able to decode the ingredients in a loop
First create a custom CodingKey struct
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { return nil } // will never be called
}
In the Drink struct – by the way it's supposed to be named in singular form – specify a second container, create the ingredient keys on the fly, decode the ingredients in a loop and append the results to an array until the value is nil. Somebody should tell the owners of the service that their JSON structure is very amateurish. As you have to specify the CodingKeys anyway I mapped the keys to more meaningful and less redundant struct member names.
struct Drink: Decodable, Identifiable {
private enum CodingKeys : String, CodingKey {
case name = "strDrink", instructions = "strInstructions", thumbnail = "strDrinkThumb"
}
let id = UUID()
let name: String
let instructions: String
let thumbnail: URL
let ingredients: [String]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.instructions = try container.decode(String.self, forKey: .instructions)
self.thumbnail = try container.decode(URL.self, forKey: .thumbnail)
var counter = 1
var temp = [String]()
let anyContainer = try decoder.container(keyedBy: AnyKey.self)
while true {
let ingredientKey = AnyKey(stringValue: "strIngredient\(counter)")!
guard let ingredient = try anyContainer.decodeIfPresent(String.self, forKey: ingredientKey) else { break }
temp.append(ingredient)
counter += 1
}
ingredients = temp
}
}
In the view display the ingredients like this
struct IngredientView: View {
let drink : Drink
var body: some View {
GroupBox() {
DisclosureGroup("Drink Ingredience") {
ForEach(drink.ingredients) { ingredient in
Divider().padding(.vertical, 2)
HStack {
Group {
Text(ingredient)
}
.font(Font.system(.body).bold())
Spacer(minLength: 25)
}
}
}
}
}
}
I'm trying to pass an array of objects inside another object
struct CreationData: Encodable {
enum CodingKeys: String, CodingKey {
case property1
case amount
case participants
case code
}
var property1: String?
var amount: Double?
var tipAmount: Double?
var participants: [Participant]
var currency: Currency
init() {
name = nil
property1 = nil
participants = []
tipAmount = nil
currency = .hryvna
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let name = name {
try container.encode(name, forKey: .name)
}
if let property1 = property1 {
try container.encode(property1, forKey: .property1)
}
if let tipAmount = tipAmount {
try container.encode(tipAmount, forKey: .tipAmount)
}
if !participants.isEmpty {
try container.encode(participants, forKey: .participants)
}
try container.encode(currency, forKey: .currencyCode)
}
}
where object in array is
struct Participant: Hashable, Encodable {
enum CodingKeys: String, CodingKey {
case amount
case id
}
var image: Data?
var displayName: String
var amount: Double? = 100
var id: Int?
var name: String?
var isSelected: Bool = false
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(amount, forKey: .amount)
try container.encode(id, forKey: .id)
}
}
so my JSON looks something like this
{
"property1" : 123,
"code" : "zen",
"name" : "No name",
"participants" : [
{
"amount" : 100,
"id" : 1
}
]
}
On the backend I use this handler
func createHandler(_ req: Request, splitCreate: Create) throws -> Future<SplitResponse> {
let logger = try req.make(Logger.self)
let user = try req.requireAuthenticated(User.self)
guard let id = user.id else {
throw Abort(.badRequest, reason: "Couldn't get an id for current user")
}
...
with this request content object
struct Create: Content {
struct Participant: Content {
var id: Int?
var amount: Double?
}
var amount: Double?
var tipAmount: Double?
var name: String?
var participants: [Participant]
var currencyCode: String
}
I create pivots by the participants array, and the problem is that instead of getting a single participant from an array like so
[App.Create.Participant(id: Optional(1), amount: Optional(100.0))]
on the backend from the specified JSON I get these participants
[App.Create.Participant(id: Optional(1), amount: nil), App.Create.Participant(id: Optional(1), amount: Optional(100.0))]
Seems like my array json is being decoded incorrectly, but I'm unable to find a way to fix it
For some reason adding Content-Type header to URLSessionConfiguration.additionalHeaders didn't change the encoding to JSON.
So I had to set Alamofire's request encoding to JSONEncoding.default explicitly.
I am trying to access a particular an embedded array of dictionaries to create my swift objects. I am unsure about how to access that array in the JSON dictionary.
Here is the definition of my Swift object = StarWarsPeople
class StarWarsPeople: Decodable {
var name: String?
var height: String?
var weight: String?
var hair_color: String?
var skin_color: String?
var eye_color: String?
var birth_year: String?
var gender: String?
}
Here is my APIClient class:
class StarWarsPeopleAPIClient
{
class func getStarWarsPeopleInformation (page: Int, completion:#escaping ([StarWarsPeople])-> ()) throws {
let starWarsPeopleURL = "https://swapi.co/api/people/?page=\(page)"
let convertedStarWarsPeopleURL = URL(string: starWarsPeopleURL)
guard let unwrappedConvertedStarWarsPeopleURL = convertedStarWarsPeopleURL else { print("unwrappedConvertedStarWarsPeopleURL did not unwrap"); return}
let request = URLRequest(url: unwrappedConvertedStarWarsPeopleURL)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}
do {
let starWarsPeopleDataArray = try JSONDecoder().decode([StarWarsPeople].self, from: unwrappedData)
completion(starWarsPeopleDataArray)
}
catch let error {
print("Error occured here: \(error.localizedDescription)")
}
}
task.resume()
}
}
Here is my Json, it is the results array that I would like to access, it is an array of dictionaries, which I need to iterate over to create my StarWarsPeople Object.
{
"count": 87,
"next": "url",
"previous": null,
"results": [
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "url",
"films": [
"url",
"url",
"url",
"url",
"url"
],
"species": [
"url"
],
"vehicles": [
"url",
"url"
Please read the JSON. You are ignoring the enclosing object
struct Root: Decodable {
let count: Int
let next: URL?
let previous: URL?
let results : [StarWarsPeople]
}
struct StarWarsPeople: Decodable {
private enum CodingKeys: String, CodingKey {
case name, height, mass
case hairColor = "hair_color", skinColor = "skin_color"
case eyeColor = "eye_color", birthYear = "birth_year", gender
}
let name: String
let height: String
let mass: String
let hairColor: String
let skinColor: String
let eyeColor: String
let birthYear: String
let gender: String
}
...
let root = try JSONDecoder().decode(Root.self, from: unwrappedData)
let starWarsPeopleDataArray = root.results
...
Notes:
A struct is sufficient.
Map the snake_cased keys to camelCased properties.
In almost all cases the properties can be declared as constants (let).
Don't declare all properties schematically as optional. Declare only those as optional which corresponding key can be missing or the value can be null.
Simply define a wrapper struct that holds the results property from the JSON response.
struct ApiResponse: Decodable {
results: [StarWarsPeople]
}
and later use
let apiResponse = try JSONDecoder().decode(ApiResponse.self, from: unwrappedData)
let starWarsPeopleDataArray = apiResponse.results
to parse it.
Results you are trying to fetch is actually present in results keys. Also we need to use properties same as parameter name( we can use CodingKeys enum too for overriding this).
So, first parse outer JSON , In new struct say StarWarsPeopleParent
class StarWarsPeopleParent: Decodable {
var count: Int?
var results: [StarWarsPeople]?
}
Update your StarWarsPeople struct's properties as:
class StarWarsPeople: Decodable {
var name: String?
var height: String?
var mass: String?
var hair_color: String?
var skin_color: String?
var eye_color: String?
var birth_year: String?
var gender: String?
}
Then parse it like:
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}
do {
let starWarsPeopleDataArray = try JSONDecoder().decode(StarWarsPeopleParent.self, from: unwrappedData)
completion(starWarsPeopleDataArray)
}
catch let error {
print("Error occured here: \(error.localizedDescription)")
}
}
Hope this is fine for you to understand.
I have a problem when trying to update an array of struct with the JSON's data. Here's my code:
This is how the struct is defined:
Struct
struct Shot {
var title: String
var desc: String
var img: String
init(title: String, desc: String, img: String) {
self.title = title
self.desc = desc
self.img = img
}
}
Here I try to update the array, but it doesn't work:
Code
func authDribbble() {
let endURL = "https://api.dribbble.com/v1/shots/"
let token = "***"
Alamofire.request(.GET, endURL, parameters: ["access_token" : token])
.responseJSON { response in
if let JSON = response.result.value {
for i in 0..<JSON.count {
let titleA: String = String(JSON[i]["title"])
let descA: String = String(JSON[i]["description"])
let imgA: String = String(JSON[i]["images"])
self.data += [Shot(title: titleA, desc: descA, img: imgA)]
}
}
}
}
The problem is that after the function is performed, the array stays void. What's wrong?
Thank you.
Working under the assumption that you've called this method as follows:
authDribbble()
print("my data: \(self.data)") // Prints empty array.
The issue is that Alamofire is performing the request asynchronously. That is, the work isn't done yet when your code proceeds to the next line (the print statement). You need to implement a callback mechanism. One way to do this is to pass a function as a parameter of your authDribbble() function. That would change the function to something like:
func authDribbble(completion: () -> Void) {
// create the url
// create parameters
Alamofire.request(.GET, url!, parameters: params)
.responseJSON { response in
guard let JSON = response.result.value as? [[String : AnyObject]] else {
// Wrong type in JSON response.
print(response.result.error)
}
self.data = JSON.map { dict in
guard let title = dict["title"] as? String,
desc = dict["description"] as? String,
img = dict["images"] as? String else {
// Handle error creating Shot from JSON
}
return Shot(title: title, desc: desc, img: img)
}
completion()
}
}
}
You would then replace the call to authDribbble() with:
authDribbble() {
print("data: \(self.data)") // Prints populated array.
}