How to pass JSON MongoDB Query object from Swift? - ios

I am passing JSON data from my iOS app to my backend MongoDB server
The JSON data consists of two parent fields userData & queryData
I pass this via POST request as req.body and retrieve the data in the server as req.body.userData and req.body.queryData
I use swift CODABLE to encode this object to jSON and send it to the server
Everything works fine. I am able to extract req.body.userData and play around with it. What I do not understand is, how do I extract a mongo query from req.body.queryData and pass it to a mongo find() method?
For example - How do I pass
{
"fieldOne": {"$in": [1, 2, 3]},
"fieldTwo": {"$ne": "Admin"}
}
The above is MongoDB syntax to filter data. How can I make my codable struct send this format? Right now I am simply declaring the struct as string. I am new to working with server side and JSONS.
Here is my struct - like I said, req.body.userData works fine
struct QueryData: Codable {
var fieldOne: String
var fieldTwo: String
}

import Foundation
let mongo = """
{
"fieldOne": {"$in": [1, 2, 3]},
"fieldTwo": {"$ne": "Admin"}
}
"""
struct FieldOne: Codable {
var `in`: [Int]
private enum CodingKeys: String, CodingKey {
case `in` = "$in"
}
}
struct FieldTwo: Codable {
var ne: String
private enum CodingKeys: String, CodingKey {
case ne = "$ne"
}
}
struct QueryData: Codable {
var fieldOne: FieldOne
var fieldTwo: FieldTwo
}
let jsonObject = try! JSONSerialization.jsonObject(with: mongo.data(using: .utf8)!, options: [])
print(jsonObject)
I try to parse you example in swift playground

Related

Protocols and Enums in Swift with Apollo

I am using Apollo for Swift in an iOS app. I have multiple types that all represent the same object. These types are auto-generated from a schema file and look something like this.
struct CurrentUser {
var id: String
...
}
struct MyUser {
var id: String
...
}
Basically Apollo generates multiple Swift types (one for each query) for the same underlying data type.
I want to create a new struct that unifies these types.
I would like to do something like this:
protocol UserProtocol {
var id: String { get }
}
struct User {
var id: String
...
init(_ data: UserProtocol) {
self.id = data.id
...
}
}
This approach however gives me an error when I try to construct a user object, telling me that "Type MyUser does not conform to UserProtocol". If I try to coerce the type with data as! UserProtocol I get a crash.
The only solution I've found is the following:
enum UserType {
case .currentUser(CurrentUser)
case .myUser(MyUser)
}
struct User {
var id: String
...
init(_ data: UserType) {
switch data {
case .myUser(let user):
self.id = data.id
...
case .currentUser(let user):
self.id = data.id
...
}
}
}
This approach works, but it leads to a lot of duplicated code in the init function. Is there a better way to do this in Swift?
I suspect the problem is that you need to explicitly conform the Apollo generated types to your protocol:
extension CurrentUser: UserProtocol { }
extension MyUser: UserProtocol { }
Remember that Swift is not duck-typed like some other languages, so a type with member var id: String is not UserProtocol until you declare it as such.
If for some reason you need to do some transformation of the Apollo types to fit the app models in the future, those extensions are a good place to do that, too.

Json Decode to swift class with dynamic value class/struct type

I have multiple responses which has similar pattern but one key value has always different object in response of json which i want to decode in base model where one key has variety of object type.
Response be like,
{
"status": true,
"message": "Success",
"data":[]
}
Here in data response it has any kind of array of objects or any single object
struct BaseResponseModel: Codable {
var status: Bool
var message: String
var data: DataClass
enum CodingKeys: String, CodingKey {
case message
case data
case status
}
}
what we can do here to make it single class with data type object pass,
Anyone please..!
Use Swift generics, and provide the type only at the time of decoding:
struct BaseResponseModel<DataType: Codable>: Codable {
var status: Bool
var message: String
var data: DataType
}
Usage:
let myData = try JSONDecoder().decode(BaseResponseModel<MyStruct>.self, from: data).data // For object
let myData = try JSONDecoder().decode(BaseResponseModel<[MyStruct]>.self, from: data).data // For array
Note: You don't need CodingKeys if the rawValues are the same.

How to replace/update dictionary in Array of Dictionary based on Type

I'm getting below JSON response from server, and displaying phone number on screen.
Now user can change/update any of phone number, so we have to update particular mobile number in same object and send it to server.
"phone_numbers": [
{
"type": "MOBILE",
"number": "8091212121"
},
{
"type": "HOME",
"number": "4161212943"
},
{
"type": "BUSINESS",
"number": "8091212344"
}
]
My model class is looks like this:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
I'm struggling to update this JSON object for particular phone number.
For example, if I want to update BUSINESS number only in above array, What's best way to do it.
I'm using XCode 11 and Swift 5.
Because all your properties are defined as constants (let), nothing can be updated. You have to initialize and return a new Contact object with the updated phone numbers.
If you change the properties to var, then you can update:
public enum PhoneType: String, Decodable {
case mobile = "MOBILE"
case home = "HOME"
case business = "BUSINESS"
}
public struct Contact: Decodable {
public var phone_numbers: [Phone]?
mutating func update(phoneNumber: String, for type: PhoneType) {
guard let phone_numbers = self.phone_numbers else { return }
for (i, number) in phone_numbers.enumerated() {
if number.type == type {
self.phone_numbers![i].number = phoneNumber
}
}
}
}
public struct Phone: Decodable {
public var type: PhoneType?
public var number: String?
}
var contact = try! JSONDecoder().decode(Contact.self, from: jsonData)
contact.update(phoneNumber: "123456", for: .business)
I'm struggling to update this JSON object for particular phone number.
It shouldn't be a JSON object when you update it. Think of JSON as just a format for transferring data. Once transferred, you should parse it into something that you can work with, like an array of dictionaries or whatever. If you've done that, then more specific questions you might ask are:
How can I find a specific entry in an array?
How can I modify the fields of a struct?
How can I replace one entry in an array with another?
After looking at the definitions of your structures, I think the problem you're having probably has to do with how you've declared them:
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
Because you used let to declare type and number, those fields cannot be changed after initialization. If you want the fields of a Phone struct to be modifiable, you need to declare them with var instead of let.
The same thing is true for your Contact struct:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
You've declared phone_numbers as an immutable array because you used let instead of var. If you want to be able to add, remove, or modify the array in phone_numbers, you need to use var instead.
The struct declarations you have right now work fine for reading the data from JSON because all the components of the JSON data are constructed using the values from the JSON. But again, you'll need to make those structs modifiable by switching to var declarations if you want to be able to make changes.
There are a couple of ways to approach this (I'm assuming PhoneType is an enum you have somewhere)
You can iterate over the array and guard for only business numbers, like so
for phone in phone_numbers{
guard phone.type == .MOBILE else { continue }
// Code that modifies phone
}
You can filter and iterate over the array, like so
phone_numbers.filter {$0.type == .BUSINESS }.forEach { phone in
// Modify phone here
}
You can then modify the right value in the array with it's index, like this
for (phoneIndex, phone) in phone_numbers.enumerated() {
guard phone.type == .BUSINESS else { continue }
phone_numbers[phoneIndex].type = ANOTHER_TYPE
}
Some can argue that the second is preferred over the first, because it is an higher order function, but in my day to day activities, I tend to use both and believe that this is a matter of taste

Decoding Data Using 'Codable'

I am trying to decode an array of my model objects(Catalog) from a JSON that looks like this after serialization of corresponding 'Data' object.
{ "id" : 5,
"catalogs" : [ {catalogKeyValue1},{catalogKeyValue2}]
}
My model object looks like this
struct Catalog : Codable{
var id : Int
var name : String
var categoryId : Int
var minProductPrice : Int
var maxProductDiscount : Int?
var shareText : String
var collageImage : String
var collageImageAspectRatio : Double?
var shipping : [String : Int]?
var description : String
}
I need to get an array of Catalogs (which is nested against 'catalogs' key in JSON) after decoding.I fully understand using nested containers and writing custom initialaizer for Catalog struct .How can I achieve this without having to write another Codable struct for outer JSOn that looks like this
struct CatalogArray: Codable {
var catalogs : [Catalog]
}
and then do something like this to get a decoded array of Catalogs
let catalogArray = try decoder.decode(CatalogArray.self, from: validData)
My problem is I dont have any need for this catalogArray struct. Is there a way of getting Catalog model objects decoded without having to create unnecessary nested structs.
You can do this instead of making a new Struct everytime: try container.decode([Catalog].self, forKey: "Catalogs")
Arrays of a type that is Codable are automatically Codable.
As per your comment
The problem with more Codable structs is that I will need to create another struct if the same array of catalogs comes with a different key in another API response.
You can create generic struct that can do the same for you. here is a example
struct GeneralResponse<T:Codable>: Codable {
let code: Int
let catalogs: T?
enum CodingKeys: String, CodingKey {
case code = "id"
case catalogs = "catalogs"
}
public init(from decoder:Decoder) throws {
let contaienr = try decoder.container(keyedBy: CodingKeys.self)
code = try contaienr.decode(Int.self, forKey: .code)
do {
let object = try contaienr.decodeIfPresent(T.self, forKey: .data)
catalogs = object
} catch {
catalogs = nil
}
}
}
Now
You can use this with different catalogs type of struct
like
GeneralResponse<[Catalogs]> or GeneralResponse<[CatalogsAnother]>
Hope it is helpful

Convert string JSON response to a boolean using Swift 4 Decodable

I'm refactoring some projects where I'd previously used third-party JSON parsers and I've encountered a goofy site that returns a boolean as a string.
This is the relevant snippet from the JSON response:
{
"delay": "false",
/* a bunch of other keys*/
}
My struct for Decoding looks like this:
struct MyJSONStruct: Decodable {
let delay: Bool
// the rest of the keys
}
How would I convert the string returned in the JSON response into a Bool to match my struct in Swift 4? While this post was helpful, I can't figure out how to turn a string response into a boolean value.
Basically you have to write a custom initializer but if there are many good keys but only one to map from a type to another a computed property might be useful
struct MyJSONStruct: Decodable {
var delay: String
// the rest of the keys
var boolDelay : Bool {
get { return delay == "true" }
set { delay = newValue ? "true" : "false" }
}
}
you need to set the boolEncoding: .literal in the URLEncoding initializer.
boolEncoding: .literal

Resources