Is there a way to use guard statements more concisely? - swift

I'm using Gloss for my JSON instantiation. Here is a sample class:
public class MyObj: Decodable
{
let id_user : String
let contact_addr1 : String
let contact_addr2 : String?
let contact_city : String
let contact_state : String
let contact_zip : String
let points : Int
// Deserialization
required public init?(json: JSON)
{
guard let id_user : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let contact_addr1 : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let contact_city : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let contact_state : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let contact_zip : String = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
guard let points : Int = "somekey" <~~ json else {
assertionFailure("MyObj - invalid JSON. Missing key: wouldbenicetonotwritethisforeachmember")
return nil
}
self.id_user = id_user
self.contact_addr1 = contact_addr1
self.contact_addr2 = "somekey" <~~ json
self.contact_city = contact_city
self.contact_state = contact_state
self.contact_zip = contact_zip
self.contact_points = points
}
}
I have a lot of model classes. Hundreds of members between them. Writing a multi-line guard statement for each one really junks up my code. Is there any way I can encapsulate the guard functionality into something more concise? Maybe a function or something like:
shortGuard("memberName", "jsonKey")
Maybe there is a way to guard against an array of string keys?

There are a huge variety of ways to accomplish this. They all boil down to writing a wrapper function to map your keys to values. Here are a couple quick examples I thought of, but as I say there are many ways to do this depending on what you're after:
enum JSONError: Error {
case keyNotFound(String)
}
extension JSON {
func values<T>(for keys: [String]) throws -> [T] {
var values = [T]()
for key in keys {
guard let value: T = key <~~ self else {
throw JSONError.keyNotFound(key)
}
values.append(value)
}
return values
}
func values<T>(for keys: [String], closure: ((_ key: String, _ value: T) -> Void)) throws {
for key in keys {
guard let value: T = key <~~ self else {
throw JSONError.keyNotFound(key)
}
closure(key, value)
}
}
}
The first validates all keys before you can use any of them and will throw if one isn't present. You'd use it like so:
do {
let keys = ["foo", "bar"]
// The type of the values constant is important.
// In this example we're saying look for values of type Int.
let values: [Int] = try json.values(for: keys)
for (index, key) in keys.enumerated() {
print("value for \(key): \(values[index])")
}
} catch JSONError.keyNotFound(let key) {
assertionFailure("key not found \(key)")
}
The second one will pass back key, value pairs to a closure as they appear in your keys array and will throw at the first one it finds that doesn't exist.
do {
let keys = ["foo", "bar"]
// The type of the closure's value argument is important.
// In this example we're saying look for values of type String.
try json.values(for: keys) { (key, value: String) in
print("value for key \(key) is \(value)")
}
} catch JSONError.keyNotFound(let key) {
assertionFailure("key not found \(key)")
}
Using the first version in an init?() function for your class, we have something like this:
public struct MyObj: Decodable {
public let id_user : String
public let contact_addr1 : String
public let contact_addr2 : String?
public let points : Int
public init?(json: S) {
do {
let stringKeys = ["id_user", "contact_addr1"]
let stringValues: [String] = try json.values(for: stringKeys)
id_user = stringValues[0]
contact_addr1 = stringValues[1]
// this isn't required, so just extract with no error if it fails
contact_addr2 = "contact_addr2" <~~ json
let intKeys = ["points"]
let intValues: [Int] = try json.values(for: intKeys)
points = intValues[0]
} catch JSONError.keyNotFound(let key) {
assertionFailure("key \(key) not found in JSON")
return nil
} catch {
return nil
}
}
}

I have not used Gloss, and it mostly seems to be unnecessary considering that it is simple enough to parse JSON safely without needing an extra library, or using unfamiliar syntax.
Option 1:
You can group the optional unwrapping in a single guard statement.
Example:
public struct MyObj {
let id_user : String
let contact_addr1 : String
let contact_addr2 : String?
let points : Int
public init?(json: Any) {
guard
let entities = json as? [String : Any],
let id_user = entities["some key"] as? String,
let contact_addr1 = entities["some key"] as? String,
let points = entities["some key"] as? Int
else {
assertionFailure("...")
return nil
}
self.id_user = id_user
self.contact_addr1 = contact_addr1
self.contact_addr2 = entities["some key"] as? String
self.contact_points = points
}
}
Option 2:
Another approach would be to eliminate the guard statements altogether, and let the parser throw an error during parsing, and use an optional try to convert the result to nil.
Example:
// Helper object for parsing values from a dictionary.
// A similar pattern could be used for arrays. i.e. array.stringAt(10)
struct JSONDictionary {
let values: [String : Any]
init(_ json: Any) throws {
guard let values = json as? [String : Any] else {
throw MyError.expectedDictionary
}
self.values = values
}
func string(_ key: String) throws -> String {
guard let value = values[key] as? String else {
throw MyError.expectedString(key)
}
return value
}
func integer(_ key: String) throws -> Int {
guard let value = values[key] as? Int else {
throw MyError.expectedInteger(key)
}
return value
}
}
Parser:
public struct MyObj {
let id_user : String
let contact_addr1 : String
let contact_addr2 : String?
let points : Int
public init(json: Any) throws {
// Instantiate the helper object.
// Ideally the JSONDictionary would be passed by the caller.
let dictionary = try JSONDictionary(json),
self.id_user = try dictionary.string("some key"),
self.contact_addr1 = try dictionary.string("some key"),
self.points = try dictionary.integer("some key")
// Results in an optional if the string call throws an exception
self.contact_addr2 = try? dictionary.string("some key")
}
}
Usage:
// Instantiate MyObj from myJSON.
// myObject will be nil if parsing fails.
let myObject = try? MyObj(json: myJSON)

Related

how to get single variable name from struct

I have a core data framework to handle everything you can do with coredata to make it more cooperateable with codable protocol. Only thing i have left is to update the data. I store and fetch data by mirroring the models i send as a param in their functions. Hence i need the variable names in the models if i wish to only update 1 specific value in the model that i request.
public func updateObject(entityKey: Entities, primKey: String, newInformation: [String: Any]) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityKey.rawValue)
do {
request.predicate = NSPredicate.init(format: "\(entityKey.getPrimaryKey())==%#", primKey)
let fetchedResult = try delegate.context.fetch(request)
print(fetchedResult)
guard let results = fetchedResult as? [NSManagedObject],
results.count > 0 else {
return
}
let key = newInformation.keys.first!
results[0].setValue(newInformation[key],
forKey: key)
try delegate.context.save()
} catch let error {
print(error.localizedDescription)
}
}
As you can see the newInformation param contains the key and new value for the value that should be updated. However, i dont want to pass ("first": "newValue") i want to pass spots.first : "newValue"
So if i have a struct like this:
struct spots {
let first: String
let second: Int
}
How do i only get 1 name from this?
i've tried:
extension Int {
var name: String {
return String.init(describing: self)
let mirror = Mirror.init(reflecting: self)
return mirror.children.first!.label!
}
}
I wan to be able to say something similar to:
spots.first.name
But can't figure out how
Not sure that I understood question, but...what about this?
class Spots: NSObject {
#objc dynamic var first: String = ""
#objc dynamic var second: Int = 0
}
let object = Spots()
let dictionary: [String: Any] = [
#keyPath(Spots.first): "qwerty",
#keyPath(Spots.second): 123,
]
dictionary.forEach { key, value in
object.setValue(value, forKeyPath: key)
}
print(object.first)
print(object.second)
or you can try swift keypath:
struct Spots {
var first: String = ""
var second: Int = 0
}
var spots = Spots()
let second = \Spots.second
let first = \Spots.first
spots[keyPath: first] = "qwerty"
spots[keyPath: second] = 123
print(spots)
however there will be complex (or impossible) problem to solve if you will use dictionary:
let dictionary: [AnyKeyPath: Any] = [
first: "qwerty",
second: 123
]
you will need to cast AnyKeyPath back to WritableKeyPath<Root, Value> and this seems pretty complex (if possible at all).
for path in dictionary.keys {
print(type(of: path).rootType)
print(type(of: path).valueType)
if let writableKeyPath = path as? WritableKeyPath<Root, Value>, let value = value as? Value { //no idea how to cast this for all cases
spots[keyPath: writableKeyPath] = value
}
}

swift dictionaries: map within a map

I'd like to convert a value I get from an API to a specific format.
[String:Any] // format received
[Int:[ContentType:Int]] // required format
ContentType is an Enum
An example of the data might look like this:
["123":["Tables":"25","Chairs":"14"]] // input
[123:[.Tables:25,.Chairs:14]] // output
I think I need to have a map within a map for this to work, but I'm struggling to work out a way forward. I may well be barking up the wrong tree entirely though. I don't really want to manually loop through and add each item one at a time; I'm looking for something more intelligent than that if possible.
enum ContentType: String {
case Tables,Chairs
}
let original_values: [String:Any]
= ["1234":["Tables":"5","Chairs":"2"]]
let values: [Int:[ContentType:Int]]
= Dictionary(uniqueKeysWithValues: original_values.map {
(
Int($0.key)!,
(($0.value as? [String:String]).map { // Error on this line - expects 1 argument but two were used
(
ContentType(rawValue: $1.key)!, // $1 is presumably wrong here?
Int($1.value)
)
}) as? [ContentType:Int]
)
})
Any ideas anybody?
I'd like to convert a value I get from an API to a specific format.
You can make your enum Decodable
enum ContentType: String, Decodable {
case tables, chairs
enum CodingKeys: String, CodingKey {
case Tables = "Tables"
case Chairs = "Chairs"
}
}
Then you can decode received Data and then compactMap it to format (Int, [ContentType: Int]). These tuples you can convert to Dictionary using designed initializer
do {
let decoded = try JSONDecoder().decode([String: [ContentType: Int]].self, from: data)
let mapped = Dictionary(uniqueKeysWithValues: decoded.compactMap { (key,value) -> (Int, [ContentType: Int])? in
if let int = Int(key) {
return (int, value)
} else {
return nil
}
})
} catch {
print(error)
}
On this line:
(($0.value as? [String:String]).map {
You using not Sequence.map, but Optional.map.
Working solution:
/// First let's map plain types to our types
let resultArray = original_values
.compactMap { (key, value) -> (Int, [ContentType: Int])? in
guard let iKey = Int(key), let dValue = value as? [String: String] else { return nil }
let contentValue = dValue.compactMap { (key, value) -> (ContentType, Int)? in
guard let cKey = ContentType(rawValue: key), let iValue = Int(value) else { return nil }
return (cKey, iValue)
}
let contentDict = Dictionary(uniqueKeysWithValues: contentValue)
return (iKey, contentDict)
}
let result = Dictionary(uniqueKeysWithValues: resultArray)
To improve print output add conform to CustomStringConvertible:
extension ContentType: CustomStringConvertible {
var description: String {
switch self {
case .Tables:
return "Tables"
case .Chairs:
return "Chairs"
}
}
}
This is Swift 5 correct syntax
enum ContentType: String {
case tables = "Tables"
case chairs = "Chairs"
}
let originalValues: [String: [String: String]]
= ["1234": ["Tables": "5", "Chairs": "2"]]
let values: [Int: [ContentType: Int]] = Dictionary(uniqueKeysWithValues:
originalValues.map { arg in
let (key, innerDict) = arg
let outMap: [ContentType: Int] = Dictionary(uniqueKeysWithValues:
innerDict.map { innerArg in
let (innerKey, innerValue) = innerArg
return (ContentType.init(rawValue: innerKey)!, Int(innerValue)!)
}
)
return (Int(key)!, outMap)
}
)
print(values)
[1234: [__lldb_expr_5.ContentType.tables: 5, __lldb_expr_5.ContentType.chairs: 2]]

Adding nested dictionary causes JSONSerialization to return nil

I have the following structure that is used to pass JSON data to a REST endpoint. Originally the object contained only one level of key-value pairs. In that scenario, serializing to a JSON object worked properly.
Now I need to add a dictionary as a parameter, which should create a nested dictionary in the resulting JSON. However, adding the nested dictionary causes JSONSerialization to return nil.
Code:
struct ServicePayload:Codable {
private var name:String
private var type:String
private var deviceId:String
private var clientType:String
private var appInstanceId:String
private var version:String
private var addParams:[String:String] // causes failure
init(name:String, type:String, version:String, params:[String:String]) {
self.name = name
self.type = type
self.deviceId = Constants.Device.identifier!
self.version = version
self.clientType = "1"
self.appInstanceId = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
self.addParams = params
}
// json mapper
private enum CodingKeys:String, CodingKey {
case name = "name"
case type = "contentTypes"
case deviceId = "x-DeviceId"
case clientType = "x-ClientType"
case appInstanceId = "x-InstanceId"
case version = "version"
case addParams = "optionalParams"
}
func getJsonObject() -> [String:String]? {
do {
let encoded = try JSONEncoder().encode(self)
if let json = try JSONSerialization.jsonObject(with: encoded, options: []) as? [String : String] {
return json
}
} catch (let error) {
print("Error building JSON: \(error.localizedDescription)")
}
return nil
}
}
Without the the addParams field, the JSONSerialization works as expected. When I add the addParams field, which adds a nested dictionary to the object, the JSONSerialization fails and returns nil.
Can anyone give me a clue as to why I can't add a nested dictionary in this scenario?
Thanks!
It fails as one key (here it's the added addParams ) 's value isn't a String so the cast
as? [String : String] // causes failure
Won't occur , hence a nil json , so Replace
if let json = try JSONSerialization.jsonObject(with: encoded, options: [])
as? [String : String] {
with
if let json = try JSONSerialization.jsonObject(with: encoded, options: [])
as? [String : Any] {
Any encapsulates String and [String:String]

Swift 4: Find value in nested, dynamic Dictionary<String, Any> recursively

In a given Dictionary, I need to find a nested Dictionary ([String : Any]) for a given key.
The general structure of the Dictionary (e.g. nesting levels, value types) is unknown and given dynamically. [1]
Inside of this sub-Dictionary, there is a given value for the key "value" (don't ask) which needs to be fetched.
Here's an example:
let theDictionary: [String : Any] =
[ "rootKey" :
[ "child1Key" : "child1Value",
"child2Key" : "child2Value",
"child3Key" :
[ "child3SubChild1Key" : "child3SubChild1Value",
"child3SubChild2Key" :
[ "comment" : "child3SubChild2Comment",
"value" : "child3SubChild2Value" ]
],
"child4Key" :
[ "child4SubChild1Key" : "child4SubChild1Value",
"child4SubChild2Key" : "child4SubChild2Value",
"child4SubChild3Key" :
[ "child4SubChild3SubChild1Key" :
[ "value" : "child4SubChild3SubChild1Value",
"comment" : "child4SubChild3SubChild1Comment" ]
]
]
]
]
With brute force and pseudo memoization, I managed to hack a function together that iterates through the entire Dictionary and fetches the value for a given key:
func dictionaryFind(_ needle: String, searchDictionary: Dictionary<String, Any>) -> String? {
var theNeedleDictionary = Dictionary<String, Any>()
func recurseDictionary(_ needle: String, theDictionary: Dictionary<String, Any>) -> Dictionary<String, Any> {
var returnValue = Dictionary<String, Any>()
for (key, value) in theDictionary {
if value is Dictionary<String, Any> {
if key == needle {
returnValue = value as! Dictionary<String, Any>
theNeedleDictionary = returnValue
break
} else {
returnValue = recurseDictionary(needle, theDictionary: value as! Dictionary<String, Any>)
}
}
}
return returnValue
}
// Result not used
_ = recurseDictionary(needle, theDictionary: searchDictionary)
if let value = theNeedleDictionary["value"] as? String {
return value
}
return nil
}
This works so far. (For your playground testing pleasure:
let theResult1 = dictionaryFind("child3SubChild2Key", searchDictionary: theDictionary)
print("And the result for child3SubChild2Key is: \(String(describing: theResult1!))")
let theResult2 = dictionaryFind("child4SubChild3SubChild1Key", searchDictionary: theDictionary)
print("And the result for child4SubChild3SubChild1Key is: \(String(describing: theResult2!))")
let theResult3 = dictionaryFind("child4Key", searchDictionary: theDictionary)
print("And the result for child4Key is: \(String(describing: theResult3))")
).
My question here:
What would be a more clean, concise, "swifty", way to iterate through the Dictionary and - especially - break completely out of the routine as soon the needed key has been found?
Could a solution even be achieved using a Dictionary extension?
Thanks all!
[1] A KeyPath as described in Remove nested key from dictionary therefor isn't feasible.
A more compact recursive solution might be:
func search(key:String, in dict:[String:Any], completion:((Any) -> ())) {
if let foundValue = dict[key] {
completion(foundValue)
} else {
dict.values.enumerated().forEach {
if let innerDict = $0.element as? [String:Any] {
search(key: key, in: innerDict, completion: completion)
}
}
}
}
the usage is:
search(key: "child3SubChild2Key", in: theDictionary, completion: { print($0) })
which gives:
["comment": "child3SubChild2Comment", "value": "child3SubChild2Subchild1Value"]
alternatively, if you don't want to use closures, you might use the following:
extension Dictionary {
func search(key:String, in dict:[String:Any] = [:]) -> Any? {
guard var currDict = self as? [String : Any] else { return nil }
currDict = !dict.isEmpty ? dict : currDict
if let foundValue = currDict[key] {
return foundValue
} else {
for val in currDict.values {
if let innerDict = val as? [String:Any], let result = search(key: key, in: innerDict) {
return result
}
}
return nil
}
}
}
usage is:
let result = theDictionary.search(key: "child4SubChild3SubChild1Key")
print(result) // ["comment": "child4SubChild3SubChild1Comment", "value": "child4SubChild3SubChild1Value"]
The following extension can be used for finding values of a key in nested dictionaries, where different levels each can contain the same key associated with a different value.
extension Dictionary where Key==String {
func find<T>(_ key: String) -> [T] {
var keys: [T] = []
if let value = self[key] as? T {
keys.append(value)
}
self.values.compactMap({ $0 as? [String:Any] }).forEach({
keys.append(contentsOf: $0.find(key))
})
return keys
}
}

Can't extract the dictionary as [String : Any] from vapor request context

I'm new to server side swift. I'm using vapor for server side swift. In vapor request I need to get JSON as [String: Any] to check a data type of the value like String, Int or Float. But in request the I can't find the exact data type of the value.
drop.post("post") { (request) -> ResponseRepresentable in
guard let name = request.data["value"]?.string else {
throw Abort.badRequest
}
return value
}
In above method, it directly converts and returns the value as String. I need to check it is a String or Int (some other data types too). I can't check by let condition which is given as below.
guard let name = data["value"] as? String else {
\\do something
}
I need to check it is a String or Int (some other data types too). If anyone has the solution, please let me know.
if you try to get Type of data using a type(of:) function, in result you will get optional any like this.
let dic = ["key":"string"] as [String : Any]
let str = dic["key"] //"string"
type(of: str) //Optional<Any>.Type
you have to check with " if let " for all case.
let dic = ["key":"string"] as [String : Any]
if let str = dic["key"] as? String {
print("String value")
} else if let intvalue = dic["key"] as? Int {
print("Int value")
} else if let boolvalue = dic["key"] as? Bool {
print("Bool value")
}
Hope it will help you
Since you're accessing the JSON, I'd do it like this.
guard let json = request.json else {
throw ...
}
if let string = json.string {
// JSON is a string
} else if let object = json.object {
// JSON is a [String: JSON]
}
// continue unwrapping for different data types
I wouldn't recommend using the type(of:) function for this purpose.