Why Swift Use a Struct as Dictionary Key Instead of a String Here

Swift - Struct or Dictionary

Just to clarify one point when you are worried about memory resources by using struct. A structure is value copied passed type. It's not stored in the memory heap unlike classes (Reference types).

So go and use struct, it's recommended by Apple itself.

Dictionary of structs in a struct

You have a few things going on here. For starters, you are initialising an empty Dict not an empty Array of dictionaries. Your property within your Thingy is a let which is a constant. Like mentioned earlier adding to things needs to be in a function inside a Struct and if you are changing that property you are mutating it.

struct Thing{
let title: String
}

struct Thingy {

var things = [[String:Thing]]()

mutating func createSomeThings() {
let thingo = ["thing1" : Thing(title:"stuff")]
things.append(thingo)
}

mutating func addAnotherThingWith(name: String, title: String) {
let thingo = [name : Thing(title:title)]
things.append(thingo)
}

}

There are to ways you can achieve your goal.

Swift: Hashable struct with dictionary property

I think you need to review your data model if you have to use a whole struct as a dictionary key. Anyhow, here's one way to do it:

internal struct MapKey: Hashable {
internal let id: String
internal let values: [String:String]

var hashValue: Int {
get {
var hashString = self.id + ";"
for key in values.keys.sort() {
hashString += key + ";" + values[key]!
}

return hashString.hashValue
}
}
}

func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}

This assumes that you don't have semicolon (;) in id or in the keys and values of values. Hasable implies Equatable so you don't need to declare it conforming to Equatable again.

Is it possible to use a Type as a dictionary key in Swift?

Is it possible to use a Type as a dictionary key in Swift?

Well its possible, here is one way:

protocol Growable { ... }

struct S : Growable { ... }
class C : Growable { ... }

extension Dictionary where Key : LosslessStringConvertible
{
subscript(index: Growable.Type) -> Value?
{
get
{
return self[String(describing: index) as! Key]
}
set(newValue)
{
self[String(describing: index) as! Key] = newValue
}
}
}

var d : [String:Int] = [:]
d[S.self] = 42
d[C.self] = 24
print(d)

prints:

["C": 24, "S": 42]

If you change the subscript definition to:

subscript(index: Any.Type) -> Value?

you can of course use any type as a key:

var d : [String:Int] = [:]
d[S.self] = 42
d[C.self] = 24
d[type(of:d)] = 18
print(d)

prints:

["C": 24, "S": 42, "Dictionary<String, Int>": 18]

I'll leave it up to you to decide whether this is wise, but its clearly possible.

[Note: you cannot constrain Key to be String hence the use of the protocol LosslessStringConvertible; there might be a better choice, the Swift standard library is a moving target...]

HTH

Should a dictionary be converted to a class or struct in Swift?

To access it like this you need to convert your dictionary to Struct as follow:

edit/update: Swift 3.x

struct Job: CustomStringConvertible {
let number: Int
let name, client: String
init(dictionary: [String: Any]) {
self.number = dictionary["jobNumber"] as? Int ?? 0
self.name = dictionary["jobName"] as? String ?? ""
self.client = dictionary["client"] as? String ?? ""
}
var description: String {
return "Job#: " + String(number) + " - name: " + name + " - client: " + client
}
}

let dict: [String: Any] = ["jobNumber": 1234,
"jobName" : "Awards Ceremony",
"client" : "ACME Productions"]

let job = Job(dictionary: dict)
print(job.number) // 1234
print(job.name) // "Awards Ceremony"
print(job.client) // "ACME Productions"
print(job) // "Job#: 1234 - name: Awards Ceremony - client: ACME Productions"""

edit/update:

Swift 4 or later you can use JSON Codable protocol:

struct Job {
let number: Int
let name, client: String
}
extension Job: Codable {
init(dictionary: [String: Any]) throws {
self = try JSONDecoder().decode(Job.self, from: JSONSerialization.data(withJSONObject: dictionary))
}
private enum CodingKeys: String, CodingKey {
case number = "jobNumber", name = "jobName", client
}
}
extension Job: CustomStringConvertible {
var description: String {
return "Job#: " + String(number) + " - name: " + name + " - client: " + client
}
}

let dict: [String: Any] = ["jobNumber": 1234,
"jobName" : "Awards Ceremony",
"client" : "ACME Productions"]
do {
let job = try Job(dictionary: dict)
print(job.number) // 1234
print(job.name) // "Awards Ceremony"
print(job.client) // "ACME Productions"
print(job) // "Job#: 1234 - name: Awards Ceremony - client: ACME Productions\n"
} catch {
print(error)
}

Swift: Filter a dictionary key from a struct from an array, which is optional

It's not completely clear to me what you're attempting to do, however, this will filter your cases array to only Test objects that contain non-nil values in the message dictionary:

let nonNil = cases.filter { (test) -> Bool in
return Array(test.message.values).filter({ (value) -> Bool in
return value == nil
}).count <= 0
}

The variable nonNil now contains the Test objects where title is "2" and title is "4".

You could further filter that if you want a [tags:preview] dictionary. Something like this would do that:

let tags = nonNil.map( { $0.message} ).flatMap { $0 }.reduce([String:String]()) { (accumulator, current) -> [String:String] in
guard let key = current.key, let value = current.value else { return accumulator }
var accum = accumulator
accum.updateValue(value, forKey: key)
return accum
}

The tags dictionary now contains: ["tag1": "preview4", "tag2": "preview2"]

Elegantly populate dictionary from a struct checking nil values

It's usually not a good idea to have a dictionary with a value that is optional. Dictionaries use the assignment of nil as an indication that you want to delete a key/value pair from the dictionary. Also, dictionary lookups return an optional value, so if your value is optional you will end up with a double optional that needs to be unwrapped twice.

You can use the fact that assigning nil deletes a dictionary entry to build up a [String : String] dictionary by just assigning the values. The ones that are nil will not go into the dictionary so you won't have to remove them:

struct A {
var first: String?
var second: String?
var third: String?
}

let a = A(first: "one", second: nil, third: "three")

let pairs: [(String, String?)] = [
("first", a.first),
("second", a.second),
("third", a.third)
]

var dictionary = [String : String]()

for (key, value) in pairs {
dictionary[key] = value
}

print(dictionary)
["third": "three", "first": "one"]

As @Hamish noted in the comments, you can use a DictionaryLiteral (which internally is just an array of tuples) for pairs which allows you to use the cleaner dictionary syntax:

let pairs: DictionaryLiteral<String,String?> = [
"first": a.first,
"second": a.second,
"third": a.third
]

All of the other code remains the same.

Note: You can just write DictionaryLiteral and let the compiler infer the types, but I have seen Swift fail to compile or compile very slowly for large dictionary literals. That is why I have shown the use of explicit types here.


Alternatively, you can skip the Array or DictionaryLiteral of pairs and just assign the values directly:

struct A {
var first: String?
var second: String?
var third: String?
}

let a = A(first: "one", second: nil, third: "three")

var dictionary = [String : String]()

dictionary["first"] = a.first
dictionary["second"] = a.second
dictionary["third"] = a.third

print(dictionary)
["third": "three", "first": "one"]

Determing dictionary with nested dictionaries type in Swift

Much much better. Never go back ;) Note that:

  • First letter of structs should be capitalised. ( Linter will shoot if you don't. )
  • You can sometimes embed structs, to enforce scoping. It's up to you and depends of the situation.
  • You don't have to use Array<> and Dictionary<> notations. Prefer brackets.
  • Define a 'string' datatype is not really advised, since it already defines a primitive type. Note that you can use `String` between quotes to redefine primitive types in your scope, but it is a bad practice.
struct Data: Hashable {
struct Text: Hashable {
let translation: String
let pinned: Bool
let order: Int
}

struct Language: Hashable {
let language: String
let target: Bool
let texts: [String: Text]
}

let base: String
let target: String
let languages: [Language]
}

If you need to use Text type outside:

var text: Data.Text

And finally, It's a bit curious to define an array of translations in a Language type. Maybe you should name it Translations and structure a bit differently. for example have a Text struct that contains a Translations dict, with language code as the key. Just a suggestion.. Good luck :)

same value in dictionary in swift

Using the struct approach, you could do something like this (you'll need to adapt it to your code, but that should be straightforward):

struct Categories {
let parentId: String
var values: [String] //notice this needs to be a var, not a let
}

func addItem(categories : inout [Categories], docId: String, name: String) {
if let index = categories.firstIndex(where: { $0.parentId == docId }) {
categories[index].values.append(name)
} else {
categories.append(Categories(parentId: docId, values: [name]))
}
}

func addValues() {
var categories = [Categories]()
addItem(categories: &categories, docId: "4", name: "Test1")
addItem(categories: &categories, docId: "1", name: "Test")
addItem(categories: &categories, docId: "4", name: "Test2")
addItem(categories: &categories, docId: "4", name: "Test3")
print(categories)

//in your code, it'll look more like:
// addItem(categories: &self.newCat, docId: docId, name: result?.name ?? "")
}

Which yields this:

[
StackOverflowPlayground.Categories(parentId: "4", values: ["Test1", "Test2", "Test3"]),
StackOverflowPlayground.Categories(parentId: "1", values: ["Test"])
]

I still wonder whether you maybe just want a Dictionary that is keyed by the parentId, but not knowing your use case, it's hard to say.



Related Topics



Leave a reply



Submit