I'm doing the same operation again and again, namely converting a dictionary to JSON and NSData.
Using Dan Kogai's JSON class https://github.com/dankogai/swift-json, I want to create an extension as follows:
extension Dictionary {
func toDataJSON() -> NSData? {
return JSON(self as Dictionary<String, AnyObject>)
.toString(pretty: false)
.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
}
}
I realize that not all Dictionary<key, value> sets are compatible with JSON, but for the sensible combination like Dictionary<String, AnyObject>I would like the above to work.
The compiler says:
'Key' is not identical to 'String'
Without casting self to Dictionary<String, AnyObject> it says:
Type 'Dictionary' does not conform to protocol 'AnyObject'
Any insights?
Since Dictionary is a template class, and since you can't extend specific variants of a template class (you want extension Dictionary<String, AnyObject>, you won't be able to do this like you want. Your best bet is probably a global function:
func toDataJSON(dict:Dictionary<String, AnyObject>) -> NSData? {
...
}
Something else which might make things more legible is to create a typealias:
typealias JSONObject = Dictionary<String, AnyObject>
Related
I am getting a error from Xcode like this Subscript 'subscript(_:)' requires that 'T' conform to 'RangeExpression' when I use this code below:
func test<T>(key: T) where T: Hashable {
let dic: Dictionary<String, String> = ["1": "a"]
if let unwrappedValue = dic[key] {
print(unwrappedValue)
}
}
I really do not see any reason that Xcode should complain! Because the Apple documentation says:
#frozen public struct Dictionary<Key, Value> where Key : Hashable { ...
So as you can see in my code, I gave what should be done! So why am I receiving this error?!
The error message isn't great, but the issue is that T isn't related to the Key type of your dic (which is always String).
Your function is generic over any unbounded T, which allows a caller to pass non-String values to key, which wouldn't be compatible with your dic.
You either need to make your dictionary generic, or your function non-generic.
Here is the working code:
func test(key: String) {
let dic: Dictionary<String, String> = ["1": "a"]
if let unwrappedValue = dic[key] {
print(unwrappedValue)
}
}
I face the following issue,
I have a swift function that takes a T.Type as parameter
public static func register<T:Decodable>(_ type:T.Type, clousure:#escaping()->NSDictionary){
public static func make<T:Decodable>(_ type:T.Type, _ overload:NSDictionary? = nil) -> T?{
I register it to a dictionary of type
static var factories = [String: () -> NSDictionary]()
This way I can create a new object from a dictionary by just decoding it
when calling it this way Factory.make(MyType.self)
However, I want it to be a bit more powerful and be able to do factories from a dictionary containing another class
Factory.make(MyType.self) {[
"name": A random name,
"subObject": SubOject.self
}]
And here is when I face the problem, to detect this suboject and call make to it
finalDict.allKeys.forEach { key in
if let className = finalDict[key] as? AnyClass {
print(className)
//finalDict[key] = Factory.make(className.self)
//finalDict[key] = Factory.make(className)
}
}
I can't do that, I've tried casting to AnyClass, AnyObject.Type... but them all make the compiler fail,
Do you know how to fix this?
Thank you!
It turns out that within a Dictionary extension, the subscript is quite useless since it says Ambiguous reference to member 'subscript'. It seems I'll either have to do what Swift does in its subscript(Key) or call a function. Any ideas?
For example,
public extension Dictionary {
public func bool(_ key: String) -> Bool? {
return self[key] as? Bool
}
}
won't work, since the subscript is said to be ambiguous.
ADDED My misunderstanding came from the fact that I assumed that Key is an AssociatedType instead of a generic parameter.
Swift type Dictionary has two generic parameters Key and Value, and Key may not be String.
This works:
public extension Dictionary {
public func bool(_ key: Key) -> Bool? {
return self[key] as? Bool
}
}
let dict: [String: Any] = [
"a": true,
"b": 0,
]
if let a = dict.bool("a") {
print(a) //->true
}
if let b = dict.bool("b") {
print(b) //not executed
}
For ADDED part.
If you introduce a new generic parameter T in extension of Dictionary, the method needs to work for all possible combination of Key(:Hashable), Value and T(:Hashable). Key and T may not be the same type.
(For example, Key may be String and T may be Int (both Hashable). You know you cannot subscript with Int when Key is String.)
So, you cannot subscript with key of type T.
For updated ADDED part.
Seems to be a reasonable misunderstanding. And you have found a good example that explains protocol with associated type is not just a generic protocol.
In Swift 2.x, I had a nice little setup that allowed me to store and retrieve dictionary values using enum members:
public enum UserDefaultsKey : String {
case mainWindowFrame
case selectedTabIndex
case recentSearches
}
extension Dictionary where Key : String {
public subscript(key: UserDefaultsKey) -> Value? {
get { return self[key.rawValue] }
set { self[key.rawValue] = newValue }
}
}
This allowed me to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[.recentSearches] as? [String] {
// Populate "Recent" menu items
}
… instead of having to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[UserDefaultsKey.recentSearches.rawValue] as? [String] {
// Populate "Recent" menu items
}
Note: The use of a string literal to access the dictionary from NSUserDefaults is for example purposes only. I wouldn't actually go out of my way to use an enum for dictionary keys, only to use a string literal to access the dictionary itself. :-)
Anyway, this has worked great for my needs, and it made reading and maintaining code involving NSUserDefaults a lot more pleasant.
Since migrating my project to Swift 3, however, I'm getting the following error:
extension Dictionary where Key: String {
public subscript(key: UserDefaultsKey) -> Value? { <---- Use of undeclared type 'Value'
~~~~~~
get {
return self[key.rawValue]
}
set {
self[key.rawValue] = newValue
}
}
}
I looked at the generated headers for Dictionary, and the generic Key and Value arguments are still present in the Generic Argument Clause of the Dictionary struct, so I'm not too sure what the issue is.
Do I need to rewrite the where clause to conform to some new Swift 3 grammar I'm unaware of? Or … can one no longer access generic placeholder types in extensions?
I just don't know what to do!
My project has only 28 migration errors left to resolve. I'm so close to actually getting to use Swift 3, so I'd love any pointers (as long as they're not Unsafe and/or Raw).
Thanks!
A generic parameter of a concrete type cannot be constrained to a concrete type, currently. This means that something like
extension Dictionary where Key == String
won't compile. It's a limitation of the generics system, and it hopefully won't be a problem in Swift 4.
There is a workaround though, but it's a bit hacky:
protocol StringConvertible {
init(_ string: String)
}
extension String: StringConvertible {}
extension Dictionary where Key: StringConvertible {
subscript(key: UserDefaultsKey) -> Value? {
get { return self[Key(key.rawValue)] }
set { self[Key(key.rawValue)] = newValue }
}
}
I am trying to merge together two Dictionaries, both of this type:
var dict = Dictionary<String, Any>
I have been using code like this to merge them:
extension Dictionary {
mutating func extend(with:Dictionary){
for (key,value) in with {
self.updateValue(value, forKey:key)
}
}
}
...
dict.extend(["num":123,"str":"abc","obj":UIColor.blueColor()])
This last line now produces an error:
Cannot convert the expression's type '(with: Dictionary<String, Any>)' to type 'Dictionary<String, NSObject>'
So Ive been trying to solve this by changing the declaration of extend to:
mutating func extend(with:Dictionary<String, Any>
But this results in this line self.updateValue in producing this error:
'protocol<>' is not convertible to 'Value'
My best guess here is that .updateValue is an Objective C method, and Objective C struggles with the Any data type.
So my next move was to try and convert my <String, Any> object into a <String, NSObject> object in the hope that .updateValue will accept it. However when I try this:
var dict2 = Dictionary<String, NSObject>
Xcode wants to put a semi colon after the NSObject. If I do that it complains about something else. Ive seen this before, it seems like Xcode can tell something is not right, but it cant put its finger on it.
So my issue is this:
I have two dictionaries.
They both have to be <String, Any>.
I have to merge them together into one.
Anyone have any idea's on how to do this?
The problem is here:
dict.extend(with:["num":123,"str":"abc","obj":UIColor.blueColor()])
^^^^^
the first parameter of class/struct methods has no automatic external name, so you have to call as:
dict.extend(["num":123,"str":"abc","obj":UIColor.blueColor()])
If you want to use the external name, you have to explicitly declare it:
mutating func extend(withDict with:Dictionary) {
^^^^^^^^
or if you want to use the same as the local name:
mutating func extend(# with:Dictionary){
^
Suggested reading: External Parameter Names
This is the code I used for testing:
var dict = Dictionary<String, Any>()
extension Dictionary {
mutating func extend(with:Dictionary){
for (key,value) in with {
self.updateValue(value, forKey:key)
}
}
}
dict.extend(["num":123,"str":"abc","obj":UIColor.blueColor()])