Reputation: 191
I have a struct that has a method to return a dictionary representation. The member variables were a combination of different types (String and Double?)
With the following code example, there would be a warning from Xcode (Expression implicitly coerced from 'Double?' to Any)
struct Record {
let name: String
let frequency: Double?
init(name: String, frequency: Double?) {
self.name = name
self.frequency = frequency
}
func toDictionary() -> [String: Any] {
return [
"name": name,
"frequency": frequency
]
}
}
However if it was returning a type [String: Any?], the warning goes away:
struct Record {
let name: String
let frequency: Double?
init(name: String, frequency: Double?) {
self.name = name
self.frequency = frequency
}
func toDictionary() -> [String: Any?] {
return [
"name": name,
"frequency": frequency
]
}
}
My question is: Is this correct? And if it is, can you point me to some Swift documentation that explains this?
If it isn't, what should it be?
== EDIT ==
The following works too:
struct Record {
let name: String
let frequency: Double?
init(name: String, frequency: Double?) {
self.name = name
self.frequency = frequency
}
func toDictionary() -> [String: Any] {
return [
"name": name,
"frequency": frequency as Any
]
}
}
Upvotes: 0
Views: 281
Reputation: 18591
You can cast frequency
to Any
since the latter can hold any type. It is like casting instances of specific Swift type to the Objective-C id type. Eventually, you'll have to downcast objects of the type Any
to a specific class to be able to call methods and access properties.
I would not recommend structuring data in your code using Any
, or if you want to be specific Any?
(when the object may or may not hold some value). That would be a sign of bad data-modeling.
From the documentation:
Any can represent an instance of any type at all, including function types.[...] Use Any and AnyObject only when you explicitly need the behavior and capabilities they provide. It is always better to be specific about the types you expect to work within your code.
(emphasis is mine)
Instead, use the Data
type. And you would be able to decode Record
or encode it from and into Data
:
struct Record : Codable {
let name: String
let frequency: Double?
init(name: String, frequency: Double?) {
self.name = name
self.frequency = frequency
}
init(data: Data) throws {
self = try JSONDecoder().decode(Record.self, from: data)
}
func toData() -> Data {
guard let data = try? JSONEncoder().encode(self) else {
fatalError("Could not encode Record into Data")
}
return data
}
}
And use it like so:
let record = Record(name: "Hello", frequency: 13.0)
let data = record.toData()
let decodedRecord = try Record(data: data)
print(decodedRecord.name)
print(decodedRecord.frequency ?? "No frequency")
Upvotes: 1
Reputation: 32813
I'd recommend adding Codable
conformance, and letting JSONEncoder
do all the heavy lifting. If however you are constrained to the toDictionary
approach, then I would advise against [String:Any?]
, since that might result in undefined behaviour (try to print the dictionary for more details).
A possible solution for toDictionary
is to use an array of tuples that gets converted to a dictionary:
func toDictionary() -> [String: Any] {
let propsMap: [(String, Any?)] = [
("name", name),
("frequency", frequency)
]
return propsMap.reduce(into: [String:Any]()) { $0[$1.0] = $1.1 }
}
This way the nil properties simply don't receive entries in the output dictionary.
Upvotes: 0