Get all keys in the Codable (or Decodable) - swift

Given the following codable struct:
struct MyBanana: Codable {
var b: String?
var z: String?
enum CodingKeys: String, CodingKey {
case b = "B"
case z = "ZOOM"
}
}
How can i get an array of strings like so: ["B", "ZOOM"] without an instance of MyBanana?
Additionally, is there a way to get this from auto-synthesised CodingKeys, e.g.
struct MyBanana: Codable {
var b: String?
var z: String?
}
would return ["b","z"]
I tried the following for the first question:
func whatever<T, Key>(keyedBy: Key.Type) -> [T] where T: Codable, Key: CodingKey
{
let allKeys: [Key] = [Key]()
for k in keyedBy {
allKeys.append(k)
}
return [T]()
}
But i get
Type 'Key.Type' does not conform to protocol 'Sequence'
Update
For declared CodingKeys #Cameron Porter's answer works fine (add CaseIterable to the enum)
For synthesized ones this works, albeit pretty manual still:
extension Decodable {
func getAllCodingKeys(_ activator: (String) -> String) -> [String] {
let mirror = Mirror(reflecting: self)
return mirror.children.filter({ $0.label != nil }).map({ activator($0.label!) })
}
}
Then in your Codable, add
func getAllKeys() -> [String] {
return self.getAllCodingKeys { CodingKeys(stringValue: $0)!.stringValue }
}

struct MyBanana: Codable {
var b: String?
var z: String?
enum CodingKeys: String, CodingKey, CaseIterable {
case b = "B"
case z = "ZOOM"
}
static var allCases: [String] {
return CodingKeys.allCases.map { $0.rawValue }
}
}
And then you can get your array of coding keys as
MyBanana.allCases
Doesn't work for auto-synthesised coding keys however

Related

Function enum parameter type

I am new on swift. I have 2+ enums and i would like to create enum to object generator function. I couldn't find what type I should use for the Enum parameter in my function.
My enums;
public enum Animal: String, CaseIterable {
case DOG = "dog"
case CAT = "cat"
case BIRD = "bird"
}
public enum Car: String, CaseIterable {
case BMW = "bmw"
case AUDI = "audi"
}
My Function;
func enumToObj(Enum:TYPE?) -> Dictionary <String, String> {
var enumsObject: [String:String] = [:];
for enumData in Enum.allCases {
let value = enumData.rawValue;
enumsObject[value] = value;
}
return enumsObject;
}
//my expectation
enumToObj(Animal) && enumToObj(Car)
You can define a generic function which take as argument a type that is CaseIterable and RawRepresentable:
func enumToObj<T>(_ Enum:T.Type) -> [String: T.RawValue]
where T: CaseIterable & RawRepresentable
{
var enumsObject: [String: T.RawValue] = [:];
for enumData in Enum.allCases {
let value = enumData.rawValue;
enumsObject["\(enumData)"] = value;
}
return enumsObject;
}
print(enumToObj(Animal.self))
// ["CAT": "cat", "DOG": "dog", "BIRD": "bird"]
print(enumToObj(Car.self))
// ["AUDI": "audi", "BMW": "bmw"]
Using reduce(into:_:) this can also be written as
func enumToObj<T>(_ Enum:T.Type) -> [String: T.RawValue]
where T: CaseIterable & RawRepresentable
{
return Enum.allCases.reduce(into: [:]) { (enumsObject, enumData) in
let value = enumData.rawValue;
enumsObject["\(enumData)"] = value;
}
}

Is reflection on Metatype?

While it's possible to get a list of properties from an instance: it requires an instance which is troublesome if the Type isn't trivial and doesn't have a straightforward .init(). Similarly, can't we test the existence of a nested type?
struct SomeStruct: Codable {
var x: Int
var y: Int
}
class CoolClass: Codable {
var thing: SomeStruct
var name: String
enum CodingKeys: String, CodingKey {
case name = "title", name = "stuff"
}
}
func testA(_ type: Any.Type) {
if type is Codable.Protocol {
print("is Codable")
} else {
print("is NOT Codable")
}
let mirror = Mirror(reflecting: type)
mirror.children // << empty collection!!
}
func testB<T>(_ type: T.Type) where T: Codable {
// test if `type` defines `CodingKeys` or if it is synthesized.
}
testA(SomeStruct.self) // "is NOT Codable", can't get list of ["x", "y"]
// , nor can't get CodingKeys...
If there is no reflection for metatypes, my attempts at piggybacking off of Codable's CodingKeys (explicit or synthesized) have all failed. Is this possible?
I don't know how to solve your main problem, but you are not checking correctly if type is codable. Since type is already a structure/class type (not an object), you should try to cast it from Any.Type to Codable.Type, which will only succeed if type implements Codable:
if type as? Codable.Type != nil {
print("is Codable")
} else {
print("is NOT Codable")
}

Swift protocol for things that convert to and from String

I'd like to have a GenericThing with a template parameter that is any type that can sensibly be converted to and from a string.
// ConvertsToAndFromString is a made up protocol here – what should I use instead?
struct GenericThing<Id: ConvertsToAndFromString> {
}
I should then be able to use GenericThing with any type that has a reasonable encoding as a string. For example, it should work for Int, String (well, dah), and ideally, any RawRepresentable where the RawValue itself will convert to and from a string.
Example:
enum Tubbies: String {
case dipsy
case laalaa
case po
}
// I'd like to be able to do this.
let genericThing = GenericThing<Tubbies>
I can't see how to easily do this.
I was hoping I could use LosslessStringConvertible instead of my made up ConvertsToAndFromString.
I tried this, and it works for Int and such. But it doesn't work for Tubbies. I couldn't see a way to make all RawRepresentable where RawValue: LosslessStringConvertible also conform to LosslessStringConvertible.
This is how you extend RawRespresentable to be conditionally LosslessStringConvertible depending on its RawValue:
extension RawRepresentable where RawValue: LosslessStringConvertible {
init?(_ rv: RawValue) {
self.init(rawValue: rv)
}
var description: String { return self.rawValue.description }
}
Here it is in action:
struct GenericThing<Id: LosslessStringConvertible> {
}
enum Tubbies: String, LosslessStringConvertible {
case dipsy
case laalaa
case po
}
let genericThing = GenericThing<Tubbies>()
print(Tubbies.po is LosslessStringConvertible) // => true
Had an issue in Swift 5.2 at least where extending RawRepresentable was causing CodingKeys to fail compiling.
public extension RawRepresentable where RawValue: LosslessStringConvertible {
init?(_ rv: RawValue) { self.init(rawValue: rv) }
var description: String { rawValue.description }
}
struct Test: Codable {
public var test: String
enum CodingKeys: String, CodingKey { // Error: Type 'Test.CodingKeys' does not conform to protocol 'CustomStringConvertible'
case test = "foo"
}
}
My workaround was to instead explicitly add conformance using the same strategy, it requires being able to change the enum but allows CodingKeys to compile.
public protocol LosslessStringConvertibleEnum: LosslessStringConvertible,
RawRepresentable where RawValue: LosslessStringConvertible {}
public extension LosslessStringConvertibleEnum {
init?(_ rawValue: RawValue) { self.init(rawValue: rawValue) }
var description: String { rawValue.description }
}
enum Tubbies: String, LosslessStringConvertibleEnum {
case dipsy
case laalaa
case po
}

Copying ENUM hash and raw values into a dictionary

Is there any way to copy all the values of an enumeration into a dictionary without polling them in any FOR loop?
For example, from this enumeration:
enum FruitPriority: String {
case PEARS
case ORANGES
case APPLES
}
to call some single function from within the ENUM, something like this:
var fruitPriorityArray: [String : Int] = FruitPriority.someFunction()
and get this result (sorted according to hash value):
["PEARS": 0, "ORANGES": 1, "APPLES": 2]
The preference would be to make only a single call to the ENUM.
Thank you.
enum FruitPriority: String, CaseIterable {
case PEARS
case ORANGES
case APPLES
}
let result = FruitPriority.allCases.enumerated().reduce([String: Int]()) { dict, fruit in
var dict = dict
dict[fruit.element.rawValue] = fruit.offset
return dict
}
print(result)
The result is:
["PEARS": 0, "ORANGES": 1, "APPLES": 2]
For version Swift 4.1 and earlier the implementation of CaseIterable:
#if swift(>=4.2)
#else
public protocol CaseIterable {
associatedtype AllCases: Collection where AllCases.Element == Self
static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
static var allCases: [Self] {
return [Self](AnySequence { () -> AnyIterator<Self> in
var raw = 0
return AnyIterator {
let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
guard current.hashValue == raw else {
return nil
}
raw += 1
return current
}
})
}
}
#endif
Original post of CaseIterable implementation.
If you need just single call to the enum:
Add CaseIterable to your enum and then just create function getDictionary in enum which returns you dictionary (for each enum case will be rawValue assigned as key and hashValue as value )
enum FruitPriority: String, CaseIterable {
case PEARS
case ORANGES
case APPLES
func getDictionary() -> [String: Int] {
var dictionary = [String: Int]()
FruitPriority.allCases.forEach {
dictionary[$0.rawValue] = $0.hashValue
}
return dictionary
}
}
then you can just call
var fruitPriorityArray: [String : Int] = FruitPriority.getDictionary()
Note: if you're using earlier versions of Swift you can see this to create CaseIterable protocol
If you make your enum CaseIterable, you can construct a dictionary using reduce(into:). Since the hashValue can now change from run to run, I would recommend using enumerated() to number the cases in order:
enum FruitPriority: String, CaseIterable {
case PEARS
case ORANGES
case APPLES
}
let result = FruitPriority.allCases.enumerated().reduce(into: [:]) { $0[$1.element.rawValue] = $1.offset }
print(result)
// ["ORANGES": 1, "APPLES": 2, "PEARS": 0]

Swift. Protocol type as dictionary key

I have been thinking about a way to use a Protocol type as a dictionary key. I made a simple test code in the playground:
//Custom Protocol conforms to Hashable
protocol Transport: Hashable {
var name: String {get set}
var quantity: Int {get set}
}
//Transport Protocol extension
extension Transport {
var hashValue: Int {
return name.hashValue ^ quantity.hashValue
}
}
//Equatable Protocol (Since Hashable conforms to Equatable)
func ==<T:Transport>(lhs: T, rhs: T) -> Bool {
return lhs.hashValue == rhs.hashValue
}
//Struct Car
struct Car: Transport {
var name: String = ""
var quantity: Int = 1
init(name: String, quantity: Int) {
self.name = name
self.quantity = quantity
}
}
//Struct Plane
struct Plane: Transport {
var name: String = ""
var quantity: Int = 1
init(name: String, quantity: Int) {
self.name = name
self.quantity = quantity
}
}
let t1 = Car(name: "Ford", quantity: 10)
let t2 = Plane(name: "Airbus", quantity: 2)
Next line gives an error: Binary operator '==' cannot be applied to operands of type 'Car' and 'Plane'
if t1 == t2 {
print("It's the same transport")
}
Next line gives this error: Using 'Transport' as a concrete type conforming to protocol 'Hashable' is not supported
var arrayOfTransports = [Transport:[Int]]()
What I am trying to do is to compare structures that conform to 'Transport' Protocol, and be able to use these structs as Dictionary keys.