Convert string JSON response to a boolean using Swift 4 Decodable - ios

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

Related

Not able to pass value to another class

I want to append a certain "id" to an api. This is the initial portion of how I make the API call in my viewController..
func myAPICall() {
APIHelper(API: WebServices.getAllOrganizations as NSString, json: bodyStr as NSString,
methodType: Constants.Get_Method as NSString....)
}
Now, WebServices.getAllOrganizations is defined elsewhere, in a swift file like so...
public class WebServices {
static let getAllOrganizations: String = "organization/getAllOrganizationDetails
}
MY ATTEMPT TO PASS THE ID TO THE SWIFT FILE :
To pass the value, I assigned it to a global variable like so...
ArrayData.shared.plantIDForOrganization = Int("\(dic["id"]!)")!
And further, I changed my swift file to this...
public class WebServices {
static let getAllOrganizations: String = "organization/getAllOrganizationDetails/\(ArrayData.shared.plantIDForOrganization)"
}
But by doing this, the value is not properly passed to the API. A 0 is passed to the API instead of the actual id number.
What could be a more efficient way of passing value from my viewcontroller to the swift file..?
When is ArrayData.shared.plantIDForOrganization initialized, and when does WebServices.getAllOganizations get its value? Once the latter is set, it won't "react" to changes in plantIDForOrganization.
I suggest you change this to a computed property, like so:
public class WebServices {
static var getAllOrganizations: String {
return "organization/getAllOrganizationDetails/\(ArrayData.shared.plantIDForOrganization)"
}
}
Also, try to eliminate thos force-unwrapping from your code.
If you have to pass a parameter use a static method instead:
public class WebServices {
static func getAllOrganizations(id: String) -> String { "organization/getAllOrganizationDetails/\(id)" }
}

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

Converting Dictionary Key type in Swift 5 with Dictionary(uniqueKeysWithValues:)

I am writing an application with networking capabilities for iOS 13.4 (Swift 5, Xcode 11) using Alamofire 5. I have created my custom type typealias KeyedParameters = [ParameterKeys: Any] to be able to use my API Parameter Keys in a 'swifty' short way (i.e. .login instead of KeyedParameters.login.rawValue).
The problem is when I try do convert this type back to default Alamofire's Parameters, I receive following error: Cannot convert return expression of type 'Dictionary<ParameterKeys, Any>' to return type 'Parameters' (aka 'Dictionary<String, Any>').
Casting:
extension KeyedParameters {
var parameters: Parameters {
Dictionary(uniqueKeysWithValues: map { ($0.key.rawValue, $0.value) })
}
}
ParameterKeys:
enum ParameterKeys: String {
// MARK: - Auth and User
case id, login, password, email, name
case createdAt = "created_at"
...
}
How error looks:
I think this might be just a case of a bad error message.
Your extension KeyedParameters (a typealias for [ParameterKeys: Any]) is actually equivalent to:
extension Dictionary where Key == ParameterKeys, Value: Any { ...
Swift has some odd behaviour when calling an initializer for a generic type within the declaration of that type itself. If the generic types are different, it won't handle that properly.
Here's a simpler example without so many red herrings (type aliases, enum raw values, etc.) and dependencies:
extension Dictionary {
func returnADifferentDict() -> [Character: String] {
let words = [
"apple", "anchovies",
"bacon", "beer",
"celery"
]
return Dictionary(uniqueKeysWithValues:
words.map { ($0.first!, $0) }
)
// fixed:
// return Dictionary<Character, String>(uniqueKeysWithValues:
// words.map { ($0.first!, $0) }
// )
}
}
The solution is to explicitly specify the generic type parameters of the generic type you're initializing. In your case,
extension KeyedParameters {
var parameters: Parameters {
Dictionary<String, Any>(uniqueKeysWithValues: map { ($0.key.rawValue, $0.value) })
}
}
You'd better explicitly highlight type like this:
extension KeyedParameters {
var parameters: Parameters {
return Parameters(uniqueKeysWithValues:
self.map { (key, value) in (key.rawValue, value) }
)
}
}
Worked for me.

common functions issues while converting code from objective c to swift

Currently I have been working on a task of converting code from objective c to swift. The work was going smooth until I occured with a common resuable code that works in objective c but I haven't getting any idea how should I do that in swift.
The scenario working in objective c is.
I have a common function in my dataManager class
- (void)saveRequest:(id)request forId:(NSNumber *)requestId {
WebRequest *requestData = [[WebRequest alloc] initWithEntity:[NSEntityDescription entityForName:WEB_REQUEST inManagedObjectContext:self.context] insertIntoManagedObjectContext:self.context];
requestData.data = [request toJSON];
requestData.requestId = requestId;
requestData.timestamp = [NSDate date];
[self save];
}
in my project the request classes are already created which contains the toJSON function.
from my controller according to user changes I created the request object and passes the request object to this function and this function calls the toJSON function in the request class and everything works in objective c.
But when I convert this function in swift then it didn't support id as function input variable and if I use Any in place of id then it gives an error that Any don't have any toJSON function.
As this function is common different request objects will come from different controllers.
I don't have any idea how should I go further from hear, If anyone have any idea please help me out
Your class should be like
class WebRequest:NSObject
{
var data :Data?
var requestId: NSNumber?
var timestamp: Date?
init(entity:String , insertIntoManagedObjectContext:NSManagedObjectContext)
{
//your code here
}
}
and your code will be as follows
func saveRequest(request:Request, requestId:NSNumber)
{
let requestData = WebRequest(entity: "entityName", insertIntoManagedObjectContext:self.context)
requestData.data = request.toJSON();
requestData.requestId = requestId;
requestData.timestamp = Date()
}
and Request class in which toJson() present
class Request: NSObject
{
//has some members
func toJSON()->Data
{
return Data()
}
}
There is an existing Swift protocol, Codable (or you can do just Encodable if you want, as Codable is merely Encodable and Decodable), which is designed explicitly for representing an object in JSON (or other formats).
You then use JSONEncoder (rather than JSONSerialization, for example) to encode the object into JSON. See Encoding and Decoding Custom Types:
Consider a Landmark structure that stores the name and founding year of a landmark:
struct Landmark {
var name: String
var foundingYear: Int
}
Adding Codable to the inheritance list for Landmark triggers an automatic conformance that satisfies all of the protocol requirements from Encodable and Decodable:
struct Landmark: Codable {
var name: String
var foundingYear: Int
}
You can then do:
let landmark = Landmark(name: "Big Ben", foundingYear: 1859)
do {
let data = try JSONEncoder().encode(landmark)
print(String(data: data, encoding: .utf8)!)
} catch {
print(error)
}
That will product JSON like so:
{
"name": "Big Ben",
"foundingYear": 1859
}
See that Encoding and Decoding Custom Types for more information.
But, if you make your types Codable/Encodable, you could then retire your toJSON method entirely. There’s no need to write code to encode JSON anymore.
If you’re looking for a more tactical edit to your project as you convert it from Objective-C to Swift, you could define your own protocol, say JsonRepresentable, that has a single method requirement, your toJSON (or to whatever you’ve renamed this method during your conversion process).
protocol JsonRepresentable {
func toJSON() -> Data
}
And then, for all of the types that have implemented this method, just add this conformance.
Ideally, go back to those individual files and move the method into an extension for that protocol, e.g., for your first object type:
extension RequestObject1: JsonRepresentable {
func toJSON() -> Data {
...
}
}
And for your second:
extension RequestObject2: JsonRepresentable {
func toJSON() -> Data {
...
}
}
Etc.
is not there a simpler way rather than changing it in whole project
I would suggest that the above is best, but, if you don’t want to go back to all of those individual type declarations, you can just add conformance with an empty extension right where you defined JsonRepresentable:
extension RequestObject1: JsonRepresentable { }
extension RequestObject2: JsonRepresentable { }
As long as those types have implemented that method, these extensions will let the compiler know about their conformance to your protocol.
Anyway, this method can then use this protocol:
func save(_ request: JsonRepresentable, requestId: Int) {
let requestData = ...
requestData.data = request.toJSON()
requestData.requestId = requestId
requestData.timestamp = Date()
save()
}

Swift dynamic property setting based on array keys

I know a few questions already exist that seem to kinda cover what I'm asking (such as Dynamically set properties from Dictionary<String, Any?> in Swift), but this doesn't seem to be quite what I'm looking for.
Basically, I have a base object where I want to set the properties based on an object, like so:
init(data: Dictionary<String,String>)
{
for (key, value) in data
{
//TODO set property as value here?
}
}
I want to be able to pass a Dictionary in of keys/values and have them be dynamically added as properties of the object. I know that this behavior is possible in PHP, for example by doing $this->{$key} = $value, but I am somewhat unfamiliar with Swift so I haven't been able to figure out how to do this yet.
Also, if anybody could tell me the name of the functionality I'm trying to achieve here, that'd be really helpful. Not knowing what this concept is called is making searching for answers difficult :c
I want to expand on the example given by #Okapi. If your class is a subclass of NSObject, then the setValue:forKey: and valueForKey: method are present by default. So, you could simply set your properties using the following code,
class Foo: NSObject {
var x:String=""
var y:String=""
var z:String=""
init(data: Dictionary<String,String>) {
super.init()
for (key,value) in data {
self.setValue(value, forKey: key)
}
}
}
In your object define (or override) setValue:forKey:, then call this from you init method like so:
class Foo {
var x:String=""
var y:String=""
var z:String=""
init(data: Dictionary<String,String>) {
for (key,value) in data {
self.setValue(value, forKey: key)
}
}
func setValue(value: AnyObject?, forKey key: String) {
switch key {
case "x" where value is String:
self.x=value as String
case "y" where value is String:
self.y=value as String
case "z" where value is String:
self.z=value as String
default:
//super.setValue(value, forKey: key)
return
}
}
}

Resources