I've got a problem with parsing data from the server. I've got JSON which has an array of objects, something like this:
{
"items": [
{
"itemType": {
"id": 12,
"tagId": "FCHA78558D"
},
"parts": [
{
"partId": 52,
"manufacturer": "xxx"
},
{
"partId": 53,
"manufacturer": "xxx"
},
{
"partId": 54,
"manufacturer": "xxx"
}
],
"description": "yyy"
},
{
"itemType": {
"id": 13,
"tagId": "FCHA755158D"
},
"parts": [
{
"partId": 64,
"manufacturer": "xxx"
},
{
"partId": 65,
"manufacturer": "xxx"
}
],
"description": "zzz"
}
]
}
I only want to obtain this one array of objects so I implemented this class like this:
class User : Object, Decodable {
var items = List<Equipment>()
}
in the Alamofire I'm downloading the JSON, parsing it to data and then in do-catch block I receive an error:
let items = try JSONDecoder().decode(User.self, from: receivedValue)
error:
▿ DecodingError
▿ typeMismatch : 2 elements
- .0 : Swift.Array<Any>
▿ .1 : Context
▿ codingPath : 2 elements
- 0 : CodingKeys(stringValue: "items", intValue: nil)
▿ 1 : _JSONKey(stringValue: "Index 0", intValue: 0)
- stringValue : "Index 0"
▿ intValue : Optional<Int>
- some : 0
- debugDescription : "Expected to decode Array<Any> but found a dictionary instead."
- underlyingError : nil
That's weird because it is an array of objects for sure. I tried setting my items property to String to see the result and then I got:
- debugDescription : "Expected to decode String but found an array instead."
I had this error couple of times but I always managed to find the solution.
I suppose you were using the List conditional conformance to Decodable from my answer to your previous question. I don't fully understand why it doesn't work in this specific case, but I'll investigate.
Until then, you can make decoding work by manually implementing the init(from decoder:Decoder) function.
class User : Object, Decodable {
let items = List<Equipment>()
private enum CodingKeys: String, CodingKey {
case items
}
required convenience init(from decoder:Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
let itemsArray = try container.decode([Equipment].self, forKey: .items)
self.items.append(objectsIn: itemsArray)
}
}
Related
I'm new to Swift and I am having trouble understanding how to structure relational data in a larger app.
Consider this api json response
// posts
{
"entities": {
"posts": [
{
"id": "1",
"content": "I am a post!"
"user": {
"id": "1",
"username": "user1"
}
},
{
"id": "2",
"content": "I am another post!"
"user": {
"id": "1",
"username": "user1"
}
}
]
}
}
// posts/featured
{
"entities": {
"posts": [
{
"id": "2",
"content": "I am another post!"
"user": {
"id": "1",
// username is not needed in the featured posts UI
}
}
]
}
}
There are a couple of things to keep in mind:
posts and posts/featured represent two independent screens on the app, each displaying a separate set of posts. Overlaps are allowed, as in the response above.
The api is tied to the app views, i.e. it will return only the data that is directly used in the app. posts/featured does not show the username, hence it is not returned by the api.
If a post's content is changed in posts, that update should automatically be applied to the same post in posts/featured only if it is there.
My attempt
Here is how I attempted to model this.
The Post and User structs
struct Post {
let id: String
var content: String?
var user: String?
init(json: [String: Any]) {
let id = json["id"] as! String
let content = json["content"] as? String ?? nil
let user = json["user"] as? [String: Any] ?? nil
self.id = id
self.content = content
self.user = user != nil ? user!["id"] as? String : nil
}
}
struct User {
let id: String
var username: String?
init(json: [String: Any]) {
let id = json["id"] as! String
let username = json["username"] as? String ?? nil
self.id = id
self.username = username
}
}
My entityState
class EntityState: ObservableObject {
#Published var posts: [String: Post]
#Published var users: [String: User]
... more stuff
}
Updating data across screens
My only solution is to normalize the state, and have Post.user be a String representing the User.id. With that, I keep in EntityState the dictionaries of posts and users, and in my ViewModels, I keep a local array of strings representing the postIds of the specific screen.
To update the data, only one update is required in the EntityState model and it will propagate everywhere it is being referenced by id.
Using Post and User struct in the code
Since User.username will sometimes be empty depending on where it is requested, it forces me to deal with conditionals or worse, use User.username! everywhere, which I don't think is correct, yet I am unsure of a better way.
What is the best way to structure this? I also control the api, so I am flexible in returning different data.
let json = """
{
"entities": {
"posts": [
{
"id": "1",
"content": "I am a post!",
"user": {
"id": "1",
"username": "user1"
}
},
{
"id": "2",
"content": "I am another post!",
"user": {
"id": "1",
"username": "user1"
}
},
{
"id": "2",
"content": "I am another post!",
"user": {
"id": "1"
}
}
]
}
}
"""
// Simulate data received from a network call
let data = json.data(using: .utf8)!
struct Entity: Codable {
var entities: [String : [Post]]
}
struct Post: Codable {
let id: String
var content: String?
var user: User?
}
struct User: Codable {
let id: String
var username: String?
}
do {
let entity = try JSONDecoder().decode(Entity.self, from: data)
print(entity)
}catch {
print(error)
}
Inspecting in the console:
po entity
▿ Entity
▿ entities : 1 element
▿ 0 : 2 elements
- key : "posts"
▿ value : 3 elements
▿ 0 : Post
- id : "1"
▿ content : Optional<String>
- some : "I am a post!"
▿ user : Optional<User>
▿ some : User
- id : "1"
▿ username : Optional<String>
- some : "user1"
▿ 1 : Post
- id : "2"
▿ content : Optional<String>
- some : "I am another post!"
▿ user : Optional<User>
▿ some : User
- id : "1"
▿ username : Optional<String>
- some : "user1"
▿ 2 : Post
- id : "2"
▿ content : Optional<String>
- some : "I am another post!"
▿ user : Optional<User>
▿ some : User
- id : "1"
- username : nil
RE: Well, this structure doesn't provide any details/explanation regarding state normalisation and why you didn't use it, nor does it provide any explanation for dealing with optional properties of structs in the code. It also doesn't address the problem of updating one Post/User entity and having that update be reflected across the app in multiple states. I've listed in my question 3 "requirements" to keep in mind, and my attempted solution for each at the end. I was hoping for a more complete answer around those points of discussion. – Darius Mandres 3 hours ago
State management in SwiftUI does not happen in the data. These structs are used for bringing in json to a more useable format. A popular way to do that in swift is by using structs. This will likely happen async on a background thread depending how you are getting your json. If you want to lean about state management in SwiftUI I suggest looking here https://developer.apple.com/documentation/swiftui/state-and-data-flow
2)Optionals are a part of swift. They replaced nil pointers of objective-c which were also very popular. If you want to avoid them you should consider two struct one Posts one Featured. In most cases if I'm dealing with ui personally I like to use a default value. Such as Text(userName ?? "") or Text(userName ?? "User Name Unavailable"), but that's up to you as a programmer, If by mistake a featured finds its way into posts should your app crash so you can find it?
Structs in swift are copies they are passed by copy not reference. If you modify a copy it applies to that copy. You can use a class and copy a pointer throughout the app, or you can notify people using that same struct of changes.
I think you may want to consider breaking your question up into specifics and posting individual questions if you still need help.
Hi i am trying to give to alamofire parameters called "addons" that are in array...array can contain 3 or X items. I am trying to use FOR cycle to ad dictionary to another another one set of items, but...it only shows the last one...that seems it override the previous one. I tried everything I know...Even try to use SwiftyJSON framework....but alamofire only take pure dictionary type.
let itemsArr = ["Skirts", "Coat", "Shirt"]
let priceArr = ["7.00", "7.00", "2.90"]
let quantityArr = ["2", "5", "1"]
let personalInfo: [String : Any] = [
"phone" : phone,
"notes" : descNote
]
var para: [String: Any] = [
"pieces" : pieces,
"personal_info" : personalInfo,
"payment_method" : paymentMethod
]
for i in 0..<itemsArr.count {
let addons: [String: Any] = [
"name":itemsArr[i],
"price":priceArr[i],
"quantity":quantityArr[i]
]
print(addons)
para["addons"] = addons
}
well I need something like this
{
"pieces": 12,
"personal_info": {
"phone": "+420783199102",
"notes": "Plz be fast, I need to play Game of War"
},
"payment_method": "cod",
"addons": [
{
"name": "Select day Tue",
"price": 3.5,
"quantity": 1
},
{
"name": "Select day Thu",
"price": 3.5,
"quantity": 1
}
]
}
Your problem is that in loop you are overwriting variable every single iteration with single result. That's why only last one is left for you.
What you should do is:
//create an array to store the addons outside of the loop
var addons: [[String: Any]] = []
for i in 0..<itemsArr.count {
let addon: [String: Any] = [
"name":itemsArr[i],
"price":priceArr[i],
"quantity":quantityArr[i]
]
//append a single addon to our array prepared before the loop
addons.append(addon)
}
//once we gathered all addons, append results to `para` dictionary
para["addons"] = addons
I want to pass a parameter in my POST method. This is the valid parameter to be sent.
{
"feedback" : "Sample Feedback",
"orderId" : 1240,
"ratings" : [
{
"rateType" : 1,
"rating" : 3
},
{
"rateType" : 2,
"rating": 4
}
],
"sellerId" : 598,
"title" : "Sample title"
}
I am using this(below) to achieve that format(above) but the server returns invalid feedback. Please help me if I'm missing something or doing wrong to achieve the format above.
let parameter: [String: AnyObject] = [
"sellerId": self.sellerId,
"orderId": self.orderId,
"title": "Seller Feedback",
"feedback": "Sample Feedback",
"ratings": [[
"rateType": 1,
"rating": self.rate1
], [
"rateType": 2,
"rating": self.rate2
]]
]
Thank you!
Dictionaries are not ordered. you'll have to implement your own ordered dictionary or use a struct. here is one from http://timekl.com/blog/2014/06/02/learning-swift-ordered-dictionaries/
struct OrderedDictionary<Tk: Hashable, Tv> {
/* ... vars and init ... */
subscript(key: Tk) -> Tv? {
get {
return self.values[key]
}
set(newValue) {
if newValue == nil {
self.values.removeValueForKey(key)
self.keys.filter {$0 != key}
return
}
let oldValue = self.values.updateValue(newValue!, forKey: key)
if oldValue == nil {
self.keys.append(key)
}
}
}
}
I am trying to create a dictionary that I can make into a JSON formatted object and send to the server.
Example:
var users = [
[
"First": "Albert",
"Last": "Einstein",
"Address":[
"Street": "112 Mercer Street",
"City": "Princeton"]
],
[
"First": "Marie",
"Last": "Curie",
"Address":[
"Street": "108 boulevard Kellermann",
"City": "Paris"]]
]
I use this function
func nsobjectToJSON(swiftObject: NSObject) -> NSString {
var jsonCreationError: NSError?
let jsonData: NSData = NSJSONSerialization.dataWithJSONObject(swiftObject, options: NSJSONWritingOptions.PrettyPrinted, error: &jsonCreationError)!
var strJSON = NSString()
if jsonCreationError != nil {
println("Errors: \(jsonCreationError)")
}
else {
// everything is fine and we have our json stored as an NSData object. We can convert into NSString
strJSON = NSString(data: jsonData, encoding: NSUTF8StringEncoding)!
println("\(strJSON)")
}
return strJSON
}
But my result is this:
[
{
"First" : "Albert",
"Address" : {
"Street" : "112 Mercer Street",
"City" : "Princeton"
},
"Last" : "Einstein"
},
{
"First" : "Marie",
"Address" : {
"Street" : "108 boulevard Kellermann",
"City" : "Paris"
},
"Last" : "Curie"
}
]
Problem: why is the last name last? I think it should be above address. Please let me know what I am doing wrong with the NSDictionary for this to come out wrong. Any help would be very much appreciated - thank you.
To post what has already been said in comments: Dictionaries are "unordered collections". They do not have any order at all to their key/value pairs. Period.
If you want an ordered collection, use something other than a dictionary. (an array of single-item dictionaries is one way to do it.) You can also write code that loads a dictionary's keys into a mutable array, sorts the array, then uses the sorted array of keys to fetch key/value pairs in the desired order.
You could also create your own collection type that uses strings as indexes and keeps the items in sorted order. Swift makes that straightforward, although it would be computationally expensive.
I did like this.
let stagesDict = NSDictionary()
if let strVal = sleepItemDict["stages"] as? NSDictionary {
stagesDict = strVal
let sortedKeys = (stagesDict.allKeys as! [String]).sorted(by: <)
var sortedValues : [Int] = []
for key in sortedKeys {
let value = stagesDict[key]!
print("\(key): \(value)")
sortedValues.append(value as! Int)
}
}
I'm having a bit of trouble structuring my parameters so that our server API would be able to read it as valid JSON.
Alamofire uses parameters like this in swift language
let parameters : [String: AnyObject] =
[
"string": str
"params": HOW I INSERT A VALID JSON ARRAY HERE
]
The problem is that AnyObject does not seem to accept JSON so how would I send / create a structure like this with swift?
{
"string": str, "params" : [
{
"param1" : "something",
"param2" : 1,
"param3" : 2,
"param" : false
},
{
"param1" : "something",
"param2" : 1,
"param3" : 2,
"param" : false
}]
}
Taken from Alamofire's GitHub page:
let parameters = [
"foo": [1,2,3],
"bar": [
"baz": "qux"
]
]
Alamofire.request(.POST, "http://httpbin.org/post", parameters: parameters, encoding: .JSON)
// HTTP body: {"foo": [1, 2, 3], "bar": {"baz": "qux"}}
EDIT: And from your example:
let parameters = [
"string": "str",
"params": [[
"param1" : "something",
"param2" : 1,
"param3" : 2,
"param" : false
],[
"param1" : "something",
"param2" : 1,
"param3" : 2,
"param" : false
]
]
]
Solved this myself. I can just do
parameters =
[
"params": array
]
Where array is Dictionary (String, AnyObject). The problem I initially had with this solution was that you can't insert booleans into this kind of dictionary, they will just be converted into integers. But apparently alamofire JSON encoding (I think) sends them as true/false values nevertheless.
In case, there is a need to pass array directly as a parameter for a alamofire request, the following method worked for me.
source: https://github.com/Alamofire/Alamofire/issues/1508
let headers = NetworkManager.sharedInstance.headers
var urlRequest = URLRequest(url: URL(string: (ServerURL + Api))!)
urlRequest.httpMethod = "post"
urlRequest.allHTTPHeaderFields = headers
let jsonArrayencoding = JSONDocumentArrayEncoding(array: documents)
let jsonAryEncodedRequest = try? jsonArrayencoding.encode(urlRequest, with: nil)
var request: Alamofire.DataRequest? = customAlamofireManager.request(jsonAryEncodedRequest!)
request?.validate{request, response, data in
return .success
}
.responseJSON {
You need to create a NSArray object for array parameters.
var yourParameters = [
"String": "a string",
"Int": 1,
"Array": NSArray(
array: [
"a", "b", "c"
])
]
Swift 2.2 and using SwiftyJSON.swift
You can use like this.
var arrayList : [String: AnyObject]//one item of array
var list: [JSON] = []//data array
for i in 0..<10//loop if you need
{
arrayList = [
"param1":"",
"param1":"",
"param2":["","",""]
]
list.append(JSON(arrayList))//append to your list
}
//params
let params: [String : AnyObject]=[
"Id":"3456291",
"List":"\(list)"//set
]
if you are using SwiftyJSON, you can write like this
let array = ["2010-12-13T5:03:20","2010-12-13T5:03:20"]
let paramsJSON = JSON(array)
var arrString = paramsJSON.rawString(NSUTF8StringEncoding)
Using Swift 5
You can use like this.
var authParams:[String:Any] = [:]
var authParamsObject:[String:Any] = [:]
authParamsObject["is_urgent"] = data_any
authParamsObject["body"] = data_any
authParams = ["note_set" : authParamsObject]
Json Result :
{
"note_set":
{
"is_urgent": true,
"body": "string"
}
}