Swift dictionary subscript with optional values - swift

Dears,
this is my code testable in a playground:
import Foundation
import UIKit
enum Error : Swift.Error, LocalizedError {
case failedTypeCastingUITableViewCell(name: String, type: Any.Type, reuseIdentifier: String?)
var domain: String {
return ""
}
var code: Int {
return 0
}
var nserror : NSError {
return NSError(domain: self.domain,
code: self.code,
userInfo: self.userInfo)
}
// var userInfo : [String: Any]? {
// var userInfo : [String: Any] = [:]
//
// switch self {
// case .failedTypeCastingUITableViewCell(let name, let type, let reuseIdentifier):
// userInfo = [
// "param_name": name
// ,"param_type": "\(type)"
// ,"param_identifier": reuseIdentifier
// ]
// return userInfo
// }
// }
var userInfo : [String: Any]? {
var userInfo : [String: Any] = [:]
switch self {
case .failedTypeCastingUITableViewCell(let name, let type, let reuseIdentifier):
userInfo = [
"param_name": name
,"param_type": "\(type)"
]
userInfo["param_identifier"] = reuseIdentifier
return userInfo
}
}
}
print(Error.failedTypeCastingUITableViewCell(name: "", type: UITableViewCell.self, reuseIdentifier: nil).userInfo)
this is the result I get from print and is what I want to achieve with commented code:
Optional(["param_name": "", "param_type": "UITableViewCell"])
this is the result I get from commented code instead:
Optional(["param_identifier": nil, "param_type": "UITableViewCell", "param_name": ""])
I know It have to work this way, but my question is can I get rid of this in some way? ie. custom init? custom subscript?

Given the concrete example, this is certainly possible, and in a number of ways.
One way is to use key/value pairs, and filter out what is nil:
let kv = [
("param_name", name),
("param_type", "\(type)"),
("param_identifier", reuseIdentifier)
].filter{$1 != nil}.map{($0, $1!)} // <== this is your magic line
userInfo = Dictionary(uniqueKeysWithValues: kv)
Another way is with reduce:
userInfo = [
("param_name", name),
("param_type", "\(type)"),
("param_identifier", reuseIdentifier)
]
.reduce(into: [:]) { (d, kv) in d[kv.0] = kv.1 }
For more readability, you could extract that to an extension:
extension Dictionary {
init<S>(uniqueKeysWithNonNilValues seq: S)
where S : Sequence, S.Element == (Key, Value?) {
self = seq.reduce(into: [:]) { (d, kv) in d[kv.0] = kv.1 }
}
}
And then the calling code would be pretty nice IMO:
userInfo = Dictionary(uniqueKeysWithNonNilValues: [
("param_name", name),
("param_type", "\(type)"),
("param_identifier", reuseIdentifier)
])
But we could push it just a little closer to your proposed syntax, and maybe that's nicer because it feels like a Dictionary again:
extension Dictionary {
init(withNonNilValues seq: KeyValuePairs<Key, Value?>) {
self = seq.reduce(into: [:]) { (d, kv) in d[kv.0] = kv.1 }
}
}
userInfo = Dictionary(withNonNilValues: [
"param_name": name,
"param_type": "\(type)",
"param_identifier": reuseIdentifier,
])

Yes! Just don't write anything for that subscript, and when you try to access that subscript, it will automatically return nil.
Example:
myDict: [String : Any] = [
"key1" : 1,
"key2" : 2
]
let emptySubscript = myDict["key3"] // this will be nil

Related

Update dictionary values in nested dictionary

The nested dictionary that I am using is build like this
var fDict : [String : Any] = [:]
var errors : [String : Any] = [:]
var error : [String : Any] = [:]
let base : [String : Any] = ["key": "123"]
error["error"] = base
errors["errors"] = error
fDict["profile-response"] = errors
The dictionary is looking like :
{
“profile-response“ : {
“errors” : {
“error” : {
“key“ = “123”
}
}
}
}
I have written code to update the value of key to "abc"
Code :
func replaceErrorWithCustomError( data : inout [String: Any]) {
for (key,value) in data {
if key == "key" {
data.updateValue("abc", forKey: key)
break
} else if var value = value as? [String: Any] {
replaceErrorWithCustomError(data: &value)
}
}
}
The result before update and after update remains same. Please suggest how to make changes in the current dictionary without taking another dictionary.
You can try this -
func replaceErrorWithCustomError(data: inout [String: Any]) {
func updateError(dict: inout [String: Any]) -> [String: Any] {
for (key, value) in dict {
if key == "key" {
dict.updateValue("abc", forKey: key)
break
} else if var value = value as? [String: Any] {
// This is the key change
// Result must be updated back into parent
dict[key] = updateError(dict: &value)
}
}
return dict
}
updateError(dict: &data)
}

Dynamic protocol conformance in Swift

Hi I am struggle to solve the problem dynamic protocol conformance in swift language. Please see code.
Protocol:
protocol Object {
init(by object: [String: Any])
}
Custom structs with protocol object conformance:
struct Tree: Object {
let treeName: String
init(by object: [String: Any]) {
self.treeName = object["tree"] as? String ?? "Notree"
}
}
struct Plant: Object {
let plantName: String
init(by object: [String : Any]) {
self.plantName = object["tree"] as? String ?? ""
}
}
The above code just fine until the object is [String: Any]. I can't use [[String: Any]] like below.
let coconut = ["tree":"Coconut"] // => This fine
let allTrees = [["tree":"Apple"],["tree":"Orange"],["tree":"Jakfruit"]] //=> Here is the problem
let aTree = Tree(by: coconut)
let bTree = Tree(by: ["data":allTrees])
let cTree = Plant(by: ["data":allTrees])
I can't use array of objects. So, I used to store objects in to key "data". Now I used extension: Array confirm protocol object.
extension Array: Object where Element == Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) -> Object in
// return Plant.init(by: object) // => Works, But I need dynamic confirmance
// return Tree.init(by: object) // => Works, But I need dynamic confirmance
return Object.init(by: object) //=> How can I do?
})
}else{
self = []
}
}
}
The return Object shows error Protocol type 'Object' cannot be instantiated. I tried lot to solve but not able.
Can someone suggest better idea or solution for this problem? Thank you in advance...
First, you should not use the constraint == Object. You want to say that not only [Object] is an Object, but also [Plant] and [Tree] are Objects too, right? For that, you should use the : Object constraint. Second, you can use Element.init to initialise a new Element of the array. Because of the constraint Element : Object, we know that a init(by:) initialiser exists:
extension Array: Object where Element: Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) in
return Element.init(by: object)
})
}else{
self = []
}
}
}
Usage:
let trees = [Tree](by: ["data": allTrees])
Here's what I think a more Swifty version of your code, making use of failable initialisers - initialisers that return nil when they fail to initialise the object:
protocol Object {
init?(by object: [String: Any])
}
struct Tree: Object {
let treeName: String
init?(by object: [String: Any]) {
if let treeName = object["tree"] as? String {
self.treeName = treeName
} else {
return nil
}
}
}
struct Plant: Object {
let plantName: String
init?(by object: [String : Any]) {
if let plantName = object["tree"] as? String {
self.plantName = plantName
} else {
return nil
}
}
}
extension Array: Object where Element: Object{
init?(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.compactMap(Element.init)
}else{
return nil
}
}
}

Can Swift convert a class / struct data into dictionary?

For example:
class Test {
var name: String;
var age: Int;
var height: Double;
func convertToDict() -> [String: AnyObject] { ..... }
}
let test = Test();
test.name = "Alex";
test.age = 30;
test.height = 170;
let dict = test.convertToDict();
dict will have content:
{"name": "Alex", "age": 30, height: 170}
Is this possible in Swift?
And can I access a class like a dictionary, for example probably using:
test.value(forKey: "name");
Or something like that?
You can just add a computed property to your struct to return a Dictionary with your values. Note that Swift native dictionary type doesn't have any method called value(forKey:). You would need to cast your Dictionary to NSDictionary:
struct Test {
let name: String
let age: Int
let height: Double
var dictionary: [String: Any] {
return ["name": name,
"age": age,
"height": height]
}
var nsDictionary: NSDictionary {
return dictionary as NSDictionary
}
}
You can also extend Encodable protocol as suggested at the linked answer posted by #ColGraff to make it universal to all Encodable structs:
struct JSON {
static let encoder = JSONEncoder()
}
extension Encodable {
subscript(key: String) -> Any? {
return dictionary[key]
}
var dictionary: [String: Any] {
return (try? JSONSerialization.jsonObject(with: JSON.encoder.encode(self))) as? [String: Any] ?? [:]
}
}
struct Test: Codable {
let name: String
let age: Int
let height: Double
}
let test = Test(name: "Alex", age: 30, height: 170)
test["name"] // Alex
test["age"] // 30
test["height"] // 170
You could use Reflection and Mirror like this to make it more dynamic and ensure you do not forget a property.
struct Person {
var name:String
var position:Int
var good : Bool
var car : String
var asDictionary : [String:Any] {
let mirror = Mirror(reflecting: self)
let dict = Dictionary(uniqueKeysWithValues: mirror.children.lazy.map({ (label:String?, value:Any) -> (String, Any)? in
guard let label = label else { return nil }
return (label, value)
}).compactMap { $0 })
return dict
}
}
let p1 = Person(name: "Ryan", position: 2, good : true, car:"Ford")
print(p1.asDictionary)
["name": "Ryan", "position": 2, "good": true, "car": "Ford"]
A bit late to the party, but I think this is great opportunity for JSONEncoder and JSONSerialization.
The accepted answer does touch on this, this solution saves us calling JSONSerialization every time we access a key, but same idea!
extension Encodable {
/// Encode into JSON and return `Data`
func jsonData() throws -> Data {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .iso8601
return try encoder.encode(self)
}
}
You can then use JSONSerialization to create a Dictionary if the Encodable should be represented as an object in JSON (e.g. Swift Array would be a JSON array)
Here's an example:
struct Car: Encodable {
var name: String
var numberOfDoors: Int
var cost: Double
var isCompanyCar: Bool
var datePurchased: Date
var ownerName: String? // Optional
}
let car = Car(
name: "Mazda 2",
numberOfDoors: 5,
cost: 1234.56,
isCompanyCar: true,
datePurchased: Date(),
ownerName: nil
)
let jsonData = try car.jsonData()
// To get dictionary from `Data`
let json = try JSONSerialization.jsonObject(with: jsonData, options: [])
guard let dictionary = json as? [String : Any] else {
return
}
// Use dictionary
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
return
}
// Print jsonString
print(jsonString)
Output:
{
"numberOfDoors" : 5,
"datePurchased" : "2020-03-04T16:04:13Z",
"name" : "Mazda 2",
"cost" : 1234.5599999999999,
"isCompanyCar" : true
}
Use protocol, it is an elegant solution. 1. encode struct or class to data 2. decode data and transfer to dictionary.
/// define protocol convert Struct or Class to Dictionary
protocol Convertable: Codable {
}
extension Convertable {
/// implement convert Struct or Class to Dictionary
func convertToDict() -> Dictionary<String, Any>? {
var dict: Dictionary<String, Any>? = nil
do {
print("init student")
let encoder = JSONEncoder()
let data = try encoder.encode(self)
print("struct convert to data")
dict = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? Dictionary<String, Any>
} catch {
print(error)
}
return dict
}
}
struct Student: Convertable {
var name: String
var age: Int
var classRoom: String
init(_ name: String, age: Int, classRoom: String) {
self.name = name
self.age = age
self.classRoom = classRoom
}
}
let student = Student("zgpeace", age: 18, classRoom: "class one")
print(student.convertToDict() ?? "nil")
ref: https://a1049145827.github.io/2018/03/02/Swift-%E4%BB%8E%E9%9B%B6%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAStruct%E6%88%96Class%E8%BD%ACDictionary%E7%9A%84%E9%9C%80%E6%B1%82/
This answer is like the above which uses Mirror. But consider the nested class/struct case.
extension Encodable {
func dictionary() -> [String:Any] {
var dict = [String:Any]()
let mirror = Mirror(reflecting: self)
for child in mirror.children {
guard let key = child.label else { continue }
let childMirror = Mirror(reflecting: child.value)
switch childMirror.displayStyle {
case .struct, .class:
let childDict = (child.value as! Encodable).dictionary()
dict[key] = childDict
case .collection:
let childArray = (child.value as! [Encodable]).map({ $0.dictionary() })
dict[key] = childArray
case .set:
let childArray = (child.value as! Set<AnyHashable>).map({ ($0 as! Encodable).dictionary() })
dict[key] = childArray
default:
dict[key] = child.value
}
}
return dict
}
}

Make a dictionary value non-optional as extension

The below playground outlines my issue. The extension will remove nil values from my dictionary, but leave the other values as Optional(Value). What I need is a dictionary that has no nil values and make the optional value type non-optional.
Ex: I have a dictionary of [String:Int?]. I want the jsonSantize() of that called on that dictionary to return a [String:Int].
//: Playground - noun: a place where people can play
import UIKit
import Foundation
protocol OptionalType {
associatedtype Wrapped
var asOptional : Wrapped? { get }
}
extension Optional : OptionalType {
var asOptional : Wrapped? {
return self
}
}
extension Dictionary where Value : OptionalType{
//Sanitizes the current dictionary for json serialization
//Removes nil values entirely. Makes optionals non-optional
func jsonSanitize() -> Dictionary<Key,Value> {
var newDict:[Key:Value] = [:]
for (key, value) in self {
if value.asOptional != nil {
newDict.updateValue(self[key]!, forKey: key)
}
}
return newDict
}
}
var youGood = false
var stringMan:String? = youGood ?
"WOHOO!" :
nil
var dict:[String:Any?] = [
"stuff":"THINGIES",
"things": stringMan
]
var dict2 = dict.jsonSanitize()
print(dict2)
var test = (stringMan != nil)
UPDATE: Suggestion made to use Value.Wrapped as new dictionary type
//: Playground - noun: a place where people can play
import UIKit
import Foundation
protocol OptionalType {
associatedtype Wrapped
var asOptional : Wrapped? { get }
}
extension Optional : OptionalType {
var asOptional : Wrapped? {
return self
}
}
extension Dictionary where Value : OptionalType{
//Sanitizes the current dictionary for json serialization
//Removes nil values entirely. Makes optionals non-optional
func jsonSanitize() -> Dictionary<Key,Value.Wrapped> {
var newDict:[Key:Value.Wrapped] = [:]
for (key, value) in self {
if let v = value.asOptional {
newDict.updateValue(v, forKey: key)
}
}
return newDict
}
}
var youGood = false
var stringMan:String? = youGood ?
"WOHOO!" :
nil
var dict:[String:Any?] = [
"stuff":"THINGIES",
"things": stringMan
]
var dict2:[String:Any] = dict.jsonSanitize()
print(dict2)
var test = (stringMan != nil)
Your method produces a dictionary of the same type [Key: Value]
with Value being some optional type. What you probably want is
to produce a dictionary of type [Key: Value.Wrapped]:
extension Dictionary where Value: OptionalType {
func jsonSanitize() -> [Key: Value.Wrapped] {
var newDict: [Key: Value.Wrapped] = [:]
for (key, value) in self {
if let v = value.asOptional {
newDict.updateValue(v, forKey: key)
}
}
return newDict
}
}
Example:
let dict: [String: Int?] = [
"foo": 1234,
"bar": nil
]
var dict2 = dict.jsonSanitize()
print(dict2) // ["foo": 1234]
Note also that of Swift 3.0.1/Xcode 8.1 beta, optionals
are bridged to NSNull instances automatically, see
SE-0140 – Warn when Optional converts to Any, and bridge Optional As Its Payload Or NSNull

Swift check if value is of type array (of any type)

How can I check in Swift if a value is an Array. The problem is that an array of type Int can apparently not be casted to an array of type Any. Suppose I have an array myArray of type Int and execute the following:
if let array = myArray as? [Any] { return true }
it doesn't return true (which surprises me actually). The same thing appears with dictionaries. I want a dictionary of type String, Any (meaning that Any can be any type). How can I check if it is?
Thanks in advance.
Got it working like this, although it's not as beautiful as I would've hoped:
protocol ArrayType {}
extension Array : ArrayType {}
let intArray : Any = [1, 2, 3]
let stringArray : Any = ["hi", "hello", "world"]
intArray is ArrayType // true
stringArray is ArrayType // true
EDIT: I think I misunderstood your question before, now I got it though:
let intArray = [1, 2, 3]
let anyArray = intArray.map{ $0 as Any }
This is the only way to my knowledge.
You can simply
array is Array<Any>
Got it now, with the idea of #Kametrixom. It's extraordinary ugly but at least it works.
private protocol CJSONArrayType {
func toAny() -> [Any]
}
extension Array: CJSONArrayType {
private func toAny() -> [Any] {
return self.map { $0 as Any }
}
}
extension NSMutableArray: CJSONArrayType { }
extension NSArray: CJSONArrayType {
private func toAny() -> [Any] {
var result = [Any]()
for item in self {
result.append(item as Any)
}
return result
}
}
private protocol CJSONDictionaryType {
func toStringAny() -> [String: Any]
}
extension Dictionary: CJSONDictionaryType {
private func toStringAny() -> [String : Any] {
var result = [String: Any]()
for item in self {
result[item.0 as! String] = item.1 as Any
}
return result
}
}
extension NSMutableDictionary: CJSONDictionaryType { }
extension NSDictionary: CJSONDictionaryType {
private func toStringAny() -> [String : Any] {
var result = [String: Any]()
for item in self {
result[item.0 as! String] = item.1 as Any
}
return result
}
}
If you want to parse JSON, there are only a few supported types which are at least AnyObject rather than Any.
Then it's very easy to check for Array
func checkArray(item : AnyObject) -> Bool {
return item is Array<AnyObject>
}
let integer = 1
let string = "one"
let array = ["one", "two", "three"]
let dictionary = ["one" : 1, "two" : 2]
checkArray(integer) // false
checkArray(string) // false
checkArray(array) // true
checkArray(dictionary) // false
Apple highly recommends to constrain the types at compile time as much as possible