How can I convert a property name of struct to String?

I want to write some extensions for some existing classes(like NSPredicate), which allows using the property names(String) to avoid typos and improve the efficiency of code writing. I have tried many ways to no avail. Thx.

2 Likes

Are you perhaps referring to keypaths? Can you post an example code of what youre trying to do?

Yes, but I can't convert a struct KeyPath to String, here is what I want:

struct AStruct {
    let age: Int = 10
}

let s = AStruct()
let predicate = NSPredicate(format: "%K == %@", s.properties.age, 10)

I used to consider using Mirror, but it seems too slow, and I also got some errors when I debug it in the playground.

I've struggled with this as well and haven't found an elegant solution that doesn't introduce potential errors from typos.

Aside from Mirror, the only other built-in way I know of is to add Codable conformance, which at compile time will automatically synthesize a private enum inside the struct named CodingKeys... which is its own conundrum because it's private and you can't easily access its values outside of the struct, such as in your NSPredicate example.

1 Like

You can always add your own, non-private CodingKeys enum and statically reference it. You can even name it something like properties to make it read better.

struct Thing: Codable {
    let name: String
    let id: Int
    
    enum properties: String, CodingKey {
        case name, id
    }
}

print(Thing.properties.name.stringValue)

ok, here you converting s.properties.age to "age". please show us what do you do with the predicate later. (i guess at some point there'll be the opposite "age" -> s.age translation, but you didn't show it.)

Yeah, this is a way to achieve it, but I also need to write a lot of code.

I will use the predicate to query data from Core Data and translate it to the struct.

@azone I’m not sure if this is still maintained but here is a library / code generation tool for creating type safe predicates and queries for Core Data. It reads your model files and outputs structs that mirror the attributes and can be used in where statements which create an NSPredicte behind the scene.

If the intention is to create a predicate for fetching objects of some Core Data subclass then you can use #keyPath:

class MyMOClass: NSManagedObject {
    @NSManaged var age: Int
}

let predicate = NSPredicate(format: "%K == %ld", #keyPath(MyMOClass.age), 10)

However, this does not work with structs, only with properties of classes which are exposed to Objective-C.

If your query string is hardcoded then you should consider using Swift's type system to articulate the query instead. Note that NSPredicate (block:) is not supported in CoreData as the query is converted to SQL (it should work with SQLite callbacks but hey ho).

Hopefully some time soon, this kind of thing will be built in.

import Foundation

extension NSPredicate {
    convenience init<Root, Value>(keyPath: KeyPath<Root, Value>, value: Value) where Value: Equatable {
        self.init(block: { object, _ in
            guard let object = object as? Root else { return false}
            return object[keyPath: keyPath] == value
        })
    }
}

func ==<Root, Value>(lhs: KeyPath<Root, Value>, rhs: Value) -> NSPredicate where Value: Equatable {
    return NSPredicate(keyPath: lhs, value: rhs)
}

struct AStruct {
    let age: Int = 10
}

let s = AStruct()

do {
    let predicate = NSPredicate(keyPath: \AStruct.age, value: 10)
    print(predicate.evaluate(with: s))
}

do {
    let predicate = \AStruct.age == 10
    print(predicate.evaluate(with: s))
}