RangeExpression issue for Key of Dictionary even the Key is Hashable - swift

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)
}
}

Related

Decoding objects without knowing their type first

There is a likelihood this is an XY problem, I am open to these suggestions as well !
I am trying to work with Minecraft save data. Minecraft encodes Entities (basically anything that is not strictly a block) with their type inside an id property . The file then contains a big array of entities, which I want to decode and instantiate.
The problem is that, using Decodable, I must know an object's type before I start instantiating it like container.decode(Zombie.self). I can't figure out how to create a function that would read the id and return the right type of entity ?
I think this explains what I need better than any explanation could :
//Entity objects don't actually store their ID since re-encoding it is trivial.
protocol Entity : Decodable {var someProperty : Int {get set}}
struct Zombie : Entity {var someProperty : Int}
struct Skeleton : Entity {var someProperty : Int}
//Using JSON instead of SNBT so we can use JSONDecoder
let jsonData = """
[
{
"id":"zombie",
"someProperty":"3"
},
{
"id" : "skeleton",
"someProperty":"3"
}
]
"""
struct EntityList : Decodable {
var list : [any Entity] = []
init(from decoder : Decoder) throws {
var container = try decoder.unkeyedContainer()
//What should we put here ?
}
}
let decoder = JSONDecoder()
let entityList = try decoder.decode(EntityList.self, from: Data(jsonData.utf8))
//entityList should be [Zombie, Skeleton]
At the moment I'm looking into the Factory pattern, maybe that's an interesting lead ? In any case, thank you for your help !
( Please note this question has nothing to do with decoding the actual binary contents of the file, it was honestly quite hard to do but I already have a working Encoder / Decoder. It is only about unpacking those contents, hence why I just used JSON in the example above, since we have a common Decoder for that. )
I honestly haven't used the new any syntax enough to know if that can help but I have done what you're trying to do numerous times and here is how I do it.
Set up the data first
We first declare what a Zombie and a Skeleton are. They could just inherit from a protocol or they could be separate structs...
struct Zombie: Decodable {
let someProperty: Int
}
struct Skeleton: Decodable {
let someProperty: Int
let skeletonSpecificProperty: String
}
Then we can turn your array of [anyEntityType] into a homogeneous array by using an enum and embedding the entities into it...
enum Entity: Decodable {
case zombie(Zombie)
case skeleton(Skeleton)
}
Decode the enum given your JSON structure
We have to provide a custom decoder for the Entity type...
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKeys.self)
// First get the `id` value from the JSON object
let type = try container.decode(String.self, forKey: .id)
// check the value for each type of entity we can decode
switch type {
// for each value of `id` create the related type
case "zombie":
let zombie = try Zombie(from: decoder)
self = .zombie(zombie)
case "skeleton":
let skeleton = try Skeleton(from: decoder)
self = .skeleton(skeleton)
default:
// throw an error here... unsupported type or something
}
}
This should now let you decode an array of Entities from JSON into an [Entity] array.
Deal with "unknown" types
There is an extra step required for dealing with the "unknown" types. For instance, in the code above. If the JSON contains "id": "creeper" this will error as it can't deal with that. And you'll end up with your whole array failing to decode.
I've created a couple of helper functions that help with that...
If you create an object like...
struct Minecraft: Decodable {
let entities: [Entity]
enum RootKeys: String, CodingKey {
case entities
}
}
And these helpers...
extension KeyedDecodingContainer {
func decodeAny<T: Decodable>(_ type: T.Type, forKey key: K) throws -> [T] {
var items = try nestedUnkeyedContainer(forKey: key)
var itemsArray: [T] = []
while !items.isAtEnd {
guard let item = try? items.decode(T.self) else {
try items.skip()
continue
}
itemsArray.append(item)
}
return itemsArray
}
}
private struct Empty: Decodable { }
extension UnkeyedDecodingContainer {
mutating func skip() throws {
_ = try decode(Empty.self)
}
}
You can create a custom decoder for the Minecraft type like this...
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKeys.self)
self.entities = try container.decodeAny(Entity.self, forKey: .entities)
}

Is there a shorter way of declaring CodingKeys?

Say you have a struct for a model of your API response. Let's say it has 50 members. However, 5-7 members are non-standard casing, you could have AUsernAme or _BTmember, but the rest are all snake case credit_score or status_code.
Rather than writing all members like this:
struct MyStruct {
let aUserName: String
// +50 more...
private enum CodingKeys: String, CodingKey {
case aUserName = "AUsernAme"
// +50 more...
}
}
Is there a way that we can write it like this?
struct MyStruct {
#CodingKey("AUsernAme") let aUserName: String
let creditScore: Int
// +50 more ...
}
Edit: I guess this is not possible with the current Swift version, but does anyone know if this would somehow be included in the future versions of Swift?
The solution which Sweeper provided is a great solution to your problem, but IMO it may display great complexity to your problem and to the next developers who will read this code.
If I were you, I would just stick to writing all the CodingKeys for simplicity. If your worry is writing a lot of lines of cases, you can write all the cases that doesn't need custom keys in one line and just add the keys with unusual/non-standard casing on new lines:
case property1, property2, property3, property4, property5...
case property50 = "_property50"
And since you mentioned that the rest are in snake case, not sure if you know yet, but we have JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase.
Hope this helps `tol! :)
How about setting a custom keyDecodingStrategy just before you decode instead?
struct AnyCodingKey: CodingKey, Hashable {
var stringValue: String
init(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
}
let mapping = [
"AUsernAme": "aUserName",
// other mappings...
]
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
let key = codingPath[0].stringValue
guard let mapped = mapping[key] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
})
This assumes your JSON has a single level flat structure. You can make this into an extension:
extension JSONDecoder.KeyDecodingStrategy {
static func mappingRootKeys(_ dict: [String: String]) -> JSONDecoder.KeyDecodingStrategy {
return .custom { codingPath in
let key = codingPath[0].stringValue
guard let mapped = dict[key] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
}
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .mappingRootKeys(mapping)
If your JSON has more levels, you can change the type of the dictionary to [JSONPath: String], where JSONPath is a type that you can create that represents a key in a nested JSON. Then add a bit of code that converts the coding path, which is just an array of coding keys, to JSONPath. This should not be hard to write on your own.
A simple way is to just use [AnyCodingKey] as JSONPath, but there are many other ways too, and I encourage you to experiment and find the one you like the best.
typealias JSONPath = [AnyCodingKey]
extension AnyCodingKey {
init(codingKey: CodingKey) {
if let int = codingKey.intValue {
self.init(intValue: int)
} else {
self.init(stringValue: codingKey.stringValue)
}
}
}
extension JSONDecoder.KeyDecodingStrategy {
static func mappingRootKeys(_ dict: [JSONPath: String]) -> JSONDecoder.KeyDecodingStrategy {
return .custom { codingPath in
guard let mapped = dict[codingPath.map(AnyCodingKey.init(codingKey:))] else { return codingPath.last! }
return AnyCodingKey(stringValue: mapped)
}
}
}
let mapping = [
[AnyCodingKey(stringValue: "AUsernAme")]: "aUserName"
]
It is not possible to use a property wrapper for this. Your property wrapper #CodingKey("AUsernAme") let aUserName: String will be compiled to something like this (as per here):
private var _aUserName: CodingKey<String> = CodingKey("AUsernAme")
var aUserName: String {
get { _aUserName.wrappedValue }
set { _aUserName.wrappedValue = newValue }
}
There are two main problems with this:
Assuming you don't want to write init(from:) for all the 50+ properties in MyStruct, code will be synthesised to decode it, assigning to its _aUserName property. You only have control over the init(from:) initialiser of the CodingKey property wrapper, and you cannot do anything about how MyStruct is decoded in there. If MyStruct is contained in another struct:
struct AnotherStruct: Decodable {
let myStruct: MyStruct
}
Then you can indeed control the coding keys used to decode myStruct by marking it with a property wrapper. You can do whatever you want in the decoding process by implementing the property wrapper's init(from:), which brings us to the second problem:
The coding key you pass to the CodingKey property wrapper is passed via an initialiser of the form init(_ key: String). But you control the decoding via the initialiser init(from decoder: Decoder) because that is what will be called when the struct is decoded. In other words, there is no way for you to send the key mappings to the property wrapper.

Swift Dictionary access value using key within extension

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.

Referencing self in Dictionary extension

I'm trying to extend Dictionary but can't reference self with a key. I'm confused as to why this is.
extension Dictionary {
func foo() {
var result = self["key"]
}
}
I get this error :
Type 'DictionaryIndex' does not conform to protocol 'StringLiteralConvertible'
If anyone has any insight, it would be appreciated.
Dictionary is a Generic struct. It is generic on its Key and Value.
Thus in your extension you have to use Key as the type for the dictionary keys, and Value as the type for dictionary values.
The compiler is complaining because you are using the wrong type for the dictionary key extension.
Here is an example:
extension Dictionary {
func ext(key: Key) {
if let value = self[key] {
// use your value
println("Key is present")
} else {
println("No value for key")
}
}
}
let dic = ["A": 20]
dic.ext("A")
dic.ext("B")
Here is how you can do something similar ... it might make clearer why your test didn't work:
extension Dictionary {
var foo: String? {
if let key = "key" as? Key {
return self[key] as? String
}
return nil
}
}
let dic1 = ["A": "an A", "key": "the value"]
dic1.foo // "the value" as optional
dic.foo // nil since dic value type is Int
Since Dictionary is a generic struct, you might reconsider extending it as if it is a specific concrete type.

Swift dictionary to JSON+NSData extension

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>