I'm trying to use URLSessions to get and post data. I don't have problems with get requests on Swift, just post. I followed this
I have a codable class that looks like this:
class Item: Codable {
var _id: String?
var name: String = ""
var color: String = ""
var rating: Int = 0
init(name: String, color: String, rating: Int){
self.name = name
self.color = color
self.rating = rating
}
}
And json data that looks like this:
[ {
"_id" : "5e50a10c4ea5d87f0001c9da",
"name" : "Pepper",
"color" : "blue",
"rating" : 4
},
{
"_id" : "5e50a10c4ea5d87f0001c9db",
"name" : "Pepper",
"color" : "blue",
"rating" : 2
},
{
"_id" : "5e50a10c4ea5d87f0001c9dc",
"name" : "Pepper"
"color" : "blue"
"rating" : 6
}
]
I'm currently using restdb.io for my database, and have tested my requests using Postman for all Get,Post,Put,...etc.
On Postman, when I create a POST request with Json body with just name, color and rating elements, it will generate a unique _id without me having to specify.
When I do this on Swift and send a post request with Item object using the init() method, where I left _id as an optional in the class, my code crashes with an "Unexpectedly found nil while implicitly unwrapping an Optional value". How do I work around that?
If you don't need _id in your swift code you can ignore it and not include it in your codable struct. In this way, the codable will ignore it and not process it
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.
I have a Post Request, in which I am trying to create an Array of json which the user types and then send to the server, I have used dictionary and it is working for a single request but not for multiple requests.
The JSON structure to be sent is
{
"id" : "u_101"
"data" : [
{ "name" : "Shubham"
"age" : "23"
},
{
"name" : "S"
"age" : "20"
}
]
}
Here is what I am using in swift for setting the parameters of alamofire request.
func setData (id: String, data: [Any]) {
request.httpMethod = post
var parameters = Parameters()
parameters["id"] = id
parameters["data"] = data
}
Then in the view controller I am doing this, (Items contain a dictionary of entered data through the view )
var allData : [Any] = []
for item in items {
var data: [String:String] = [:]
data["name"] = item.key
data["age"] = item.value
allData.append(data)
}
setData(id: "u_101", data: alldata)
This is not working and the server is throwing error.
If I send this to the Alamofire post request.
{
"id" : "u_101"
"data" : [
{ "name" : "Shubham"
"age" : "23"
}
]
}
The server responds with a success.
I am trying to defining a decoding class model to decode this kind of json file:
Here a short extraction to understand the problem, in reality it is more nested.
{
"Title" : "Root",
"Subtitle" : "RootTree",
"Launch" : [
{
"DisplayName" : "Clients",
"Launch" : [
{
"DisplayName" : "Clients Details",
"Launch" : [
{
"DisplayName" : "Item1",
"URI" : "/rest/..."
},
{
"DisplayName" : "Item2",
"URI" : "/rest/..."
},
{
"DisplayName" : "Item3",
"URI" : "/rest/..."
}
]
}
]
}
]
}
Here my structure, I used a class because of the recursive usage:
final class Url: Codable {
let name : String
let uri: String?
let launch: [LaunchStructure]?
enum CodingKeys: String, CodingKey {
case name = "DisplayName"
case uri = "URI"
case launch = "Launch"
}
}
final class LaunchStructure: Codable {
let launch: [Url]
enum CodingKeys: String, CodingKey {
case launch = "Launch"
}
}
In the Title and Subtitle I am not interested, therefore I have excluded it from the class. I would like to get the Displayname and the uri from the items. As I said, the structure is more nested, but always the same structure. Is it possible to read the elements using a recursive way.
I am to decode it in this manner:
...
let result = Result { try JSONDecoder().decode(LaunchStructure.self, from: data) }
Thank you, best regards
Arnold
You don't need two types here at all, just one will do:
struct Item: Codable {
let name : String? // not all entries in your example has it, so it's optional
let uri: String?
let launch: [Item]? // same here, all leaf items doesn't have it
enum CodingKeys: String, CodingKey {
case name = "DisplayName"
case uri = "URI"
case launch = "Launch"
}
}
i'm currently trying to reference a collection called items with the structure below
packageSchema = schema({
recipient: String,
contents: [{item :{type: mongoose.Schema.Types.ObjectId,
ref: 'items', required : true}, amount: String}]
Below is my code for getting one package via its id
getOnePackage : function(id,callback)
{
packageModel.findById(id,callback)
.populate('contents')
}
So when i call the above function i'm expecting to get this result
{
recipient : Dave
contents : [
{item : {
_id:5d2b0c444a3cc6438a7b98ae,
itemname : "Statue",
description : "A statue of Avery"
} ,amount : "2"},
{item : {
_id:5d25ad29e601ef2764100b94,
itemname : "Sugar Pack",
description : "Premium Grade Sugar From China"
} ,amount : "5"},
]
}
But what i got from testing in Postman is this :
{
recipient : Dave,
contents : []
}
May i know where did it went wrong? And also how do i prevent mongoose from automatically insert an objectId for every single element in the contents array....
Because element in contents array is object with item field so your populate should be:
.populate('contents.item')
Data:
[
{ firstName: "Foo", lastName: "Bar" },
{ firstName: "John", lastName: "Doe" }
]
How can I have this kind of structure using swift array and dictionary? This data shows dictionaries inside an array, right? So I suggest:
var persons:Array = [Dictionary<String, String>()]
but this gives me the error:
Cannot convert the expressions type () to type Array<T>
Any ideas?
The correct way is:
var persons = [Dictionary<String, String>]()
which is equivalent to:
var persons = [[String : String]]()
What your code does instead is to create an array filled in with an instance of Dictionary<String, String>, whereas I presume you want an empty instance of the array containing elements of Dictionary<String, String> type.
Which version of Xcode have you got?
Your code should work fine but the line:
var persons:Array = [Dictionary<String, String>()]
create the array with first empty dictionary, try this instead:
var persons:Array = [Dictionary<String, String>]()
var dic1 = ["Name" : "Jon"]
var dic2 = ["Surname" : "Smith"]
persons.append(dic1)
persons.append(dic2)
println(persons)
Are you sure you really want a dictionary within an array? The code you've given indicates more an array with named columns, which can be achieved using something like the following:
struct Name {
var firstName : String
var lastName : String
}
var persons1 : Array<Name> = [
Name(firstName: "Foo", lastName: "Bar"),
Name(firstName: "John", lastName: "Doe")
]
persons1[0].firstName // "Foo"
var persons2 : Array<(firstName: String, lastName:String)> = [
(firstName: "Mary", lastName: "Mean"),
(firstName: "Foo", lastName: "Bar"),
(firstName: "John", lastName: "Doe")
]
persons2[1].firstName // "Bar"
These are proper arrays and adressed as such using subscripts. The dictionary type is usually a combination of key and value, i.e. nickname as key, and name as value.
var nickNames : [String:String] = [
"mame" : "Mary Mean",
"foba" : "Foo Bar",
"jodo" : "John Doe"]
nickNames["mame"]! // "Mary Mean"
And here we lookup on the key value, and get an optional value in return, which I forcefully unwrapped...
All of these can be appended rather easily, but do note that the named tuple variant, persons2, is not following recommended practice. Also note that the Array of Dictionaries allow for inclusion on different keys as suggested in my last injection.
persons1.append( Name(firstName: "Some", lastName: "Guy") )
persons2.append( firstName: "Another", lastName: "Girl" )
nickNames["anna"] = "Ann Nabel"
// Array of Dictionaries
var persons : [[String:String]] = [
[ "firstName" : "Firstly", "lastName" : "Lastly"],
[ "firstName" : "Donald", "lastName" : "Duck"]
]
persons.append( ["firstName" : "Georg", "middleName" : "Friedrich", "lastName" : "Händel"] )
something like this can work for you:
var persons: Array<Dictionary<String, String>> = Array()
and now you can add the names:
persons.append(["firstName": "Foo", "lastName": "Bar"]);
persons.append(["firstName": "John", "lastName": "Doo"]);
NOTE: if you are insecure how to use literals, just don't use them.