Swift Get all the properties in generic class - swift

I'm trying to get all the members of a generic class T, I can get the properties based on a specific class.
But, how I can do it using Mirror ?
let mirrored_object = Mirror(reflecting: user)
for (index, attr) in mirrored_object.children.enumerated() {
if let propertyName = attr.label as String! {
print("Attr \(index): \(propertyName) = \(attr.value)")
}
}

I added this as extension
extension NSObject {
public func GetAsJson() -> [[String:Any?]] {
var result:[[String: Any?]] = [[String: Any?]]()
for item in self {
var dict: [String: Any?] = [:]
for property in Mirror(reflecting: self).children {
dict[property.label!] = property.value
}
result.append(dict)
}
return result
}
}

Related

Swift - Return type of struct parameters without default values

I'm very new in Swift so i might be missing some basics.
I have struct:
struct MyStruct {
var a: Int
var b: String
var c: Bool
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
}
and function, that should iterate through given struct properties and return their types in json:
func structProps(){
let elm = MyStruct()
let mirror = Mirror(reflecting: elm)
var exampleDict: [String: String] = [:]
for child in mirror.children {
exampleDict[child.label!] = String(describing:type(of: child.value)) as String
}
if let theJSONData = try? JSONSerialization.data(
withJSONObject: exampleDict,
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
}
}
it kinda return what i need:
JSON string = {"a":"Int","b":"String","c":"Bool"}
Because i'm having a lot of structs and i want to export json from all of them, i'm wondering if there is a way to have generic initializer. Without passing default values.
It means without
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
If I understand correctly , you can create a base protocol and add as an extension to your structures like
protocol BaseFunction {
func getElements() -> [String : String]
func getDict() -> String
}
extension BaseFunction {
func getElements() -> [String : String] {
let mirror = Mirror(reflecting: self)
let propertiesRemoveNil = mirror.children.filter({!(($0.value as AnyObject) is NSNull)})
let properties = propertiesRemoveNil.compactMap({$0.label})
var types = [String]()
_ = mirror.children.forEach({
types.append(String(describing:type(of: $0.value)))
})
return Dictionary(uniqueKeysWithValues: zip(properties, types))
}
func getDict() -> String{
if let theJSONData = try? JSONSerialization.data(
withJSONObject: getElements(),
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
return theJSONText ?? ""
}
return ""
}
}
And usage :
func structProps(){
let elm = MyStruct()
print(elm.getDict())
}
OUTPUT :
{"a":"Int","b":"String","c":"Bool"}
I was going to write a comment about using a protocol but I thought it would be easier to understand as an answer with some code.
To make the usage more generic so you don't need specific code for each type of struct you should use a protocol. Instead of having an init that might clash with already existing init in the struct I prefer a static method that returns a new object, also known as a factory method
protocol PropertyExtract {
static func createEmpty() -> PropertyExtract
}
Then we can make use of the default init for the struct or any supplied to create an object with some initial values by letting the struct conform to the protocol in an extension
extension MyStruct: PropertyExtract {
static func createEmpty() -> PropertyExtract {
MyStruct(a: 0, b: "", c: false)
}
}
And instead of hardcoding or passing a specific type of object to the encoding function we pass the type of it
func structProps(for structType: PropertyExtract.Type)
and use the protocol method to get an instance of the type
let object = structType.createEmpty()
The whole function (with some additional changes)
func structProps(for structType: PropertyExtract.Type) -> String? {
let object = structType.createEmpty()
let mirror = Mirror(reflecting: object)
let exampleDict = mirror.children.reduce(into: [String:String]()) {
guard let label = $1.label else { return }
$0[label] = String(describing:type(of: $1.value))
}
if let data = try? JSONEncoder().encode(exampleDict) {
return String(data: data, encoding: .utf8)
}
return nil
}
And this is then called with the type of the struct
let jsonString = structProps(for: MyStruct.self)
According to Creating a Swift Runtime Library, there is a way to access meta data types without initializer.
Solution for what i was asking for is possible with Runtime library.
Mirror(reflecting:) expects an instance of a type and not a type itself, ref.
One idea is to have a generic function that works with all types conforming to a protocol that provides an empty init. Something like:
protocol EmptyInitializable {
init()
}
struct StructOne {
let a: Bool
let b: String
}
struct StructTwo {
let c: Int
let d: Float
}
extension StructOne: EmptyInitializable {
init() {
a = false
b = ""
}
}
extension StructTwo: EmptyInitializable {
init() {
c = 1
d = 1.0
}
}
func print(subject: EmptyInitializable) -> String? {
let dictionary = Dictionary(uniqueKeysWithValues:
Mirror(reflecting: subject).children.map {
($0.label!, String(describing: type(of: $0.value)))
}
)
return (try? JSONSerialization.data(withJSONObject: dictionary)).flatMap {
String(data: $0, encoding: .utf8)
}
}
print(subject: StructOne()) // "{"a":"Bool","b":"String"}"
print(subject: StructTwo()) // "{"d":"Float","c":"Int"}"
You still have to implement the empty init and assign some values and I'm afraid there's no way currently to avoid that.

Defining swift extensions with generic functions for realm and object mapper

I am working with Realm and ObjectMapper and would like to create some extensions to make my life easier when backing up some data to JSON. I have the following extensions defined:
extension Mappable where Self:Object {
func getCompleteJSONDictionary() throws -> [String: Any]? {
var returnValue: [String: Any]?
if self.isManaged, let realm = self.realm, !realm.isInWriteTransaction {
try realm.write {
var data = self.toJSON()
data["id"] = self.getPrimaryKeyValue()
returnValue = data
}
} else {
var data = self.toJSON()
data["id"] = self.getPrimaryKeyValue()
returnValue = data
}
return returnValue
}
}
extension Results where Element:Object, Element:Mappable {
func getAllCompleteJSONDictionaries() throws -> Array<[String:Any]>? {
var array: Array<[String:Any]> = Array()
for element in self {
if let dictionary = try? element.getCompleteJSONDictionary(), let data = dictionary {
array.append(data)
}
}
if array.count > 0 {
return array
} else {
return nil
}
}
}
extension Realm {
func getJSONBackupData<T>(forTypes types: [T.Type]) throws -> [String: Any] where T:Object, T:Mappable {
var data: [String: Any] = [:]
try self.write {
for type in types {
let entities = self.objects(type)
if let entityJsonData = try entities.getAllCompleteJSONDictionaries() {
data[String(describing: type)] = entityJsonData
}
}
}
return data
}
}
The first two extensions work fine, but as soon as I try to use the last, the country class conforms to both Object and Mappable:
var finalData = realm.getJSONBackupData(forTypes:[Country.self])
I get an error that T cannot be inferred. I still get myself hopelessly muddled when it comes to generics in Swift so am guessing i am just not understanding the problem correctly. Is there an easy fix here or have I asked too much of the compiler?
The problem is that the Realm Objects does not conform to Mappable Protocol. So the Country Object is not Mappable and thus the complier is saying that Type Country.self cannot be inferred as Object and Mappable.

Get Core Data Entity relatives with a generic function

I'm designing a data manager for my Core Data model and I'd like to create a generic function to fetch relatives of a class.
I’ve created a protocol allowing to build managers for each data type. In this protocol I already defined two associated types T and K and several simple functions. Now I’m stuck with a class relatives fetching method — I need to indicate somehow that T has K relatives. I’ve tried in vain to create some protocol indicating this relationship thru mutual properties, so both classes could conform to this protocol. Any idea, is it even possible?
import Foundation
import CoreData
protocol DataManager {
associatedtype T: NSManagedObject, NSFetchRequestResult
associatedtype K: NSManagedObject, NSFetchRequestResult // Relative
static var sharedInstance: Self { get }
static func getAll(sorted: [NSSortDescriptor]?, context: NSManagedObjectContext) -> [T]?
static func insert(item: T)
static func update(item: T)
static func clean()
static func deleteById(id: String)
// Relatives
static func getRelatives(by: T) -> [K]?
static func get(byRelative: K) -> [T]?
}
extension DataManager {
static func getAll(sorted: [NSSortDescriptor]?, context: NSManagedObjectContext) -> [T]? {
guard let fetchRequest: NSFetchRequest<T> = T.fetchRequest() as? NSFetchRequest<T> else { return nil }
fetchRequest.sortDescriptors = sorted
var results: [T]? = nil
do {
results = try context.fetch(fetchRequest)
} catch {
assert(false, error.localizedDescription)
} //TODO: Handle Errors
return results
}
}
protocol Identifiable {
typealias Identity = String
var id: Identity? { get }
}
extension DataManager where Self.T: Identifiable {
static func get(by id: T.Identity, context: NSManagedObjectContext) -> T? {
guard let fetchRequest: NSFetchRequest<T> = T.fetchRequest() as? NSFetchRequest<T> else { return nil }
fetchRequest.predicate = NSPredicate(format: "%K == %#", "id", id)
var rawResults: [T]? = nil
do {
rawResults = try context.fetch(fetchRequest)
} catch {
assert(false, error.localizedDescription)
} //TODO: Handle Errors
if let result = rawResults?.first {
return result }
else { return nil }
}
}
Well, I've created one solution.
We can identify all relations with a particular class:
let relationships = T.entity().relationships(forDestination: K.entity())
It allows us to find all IDs of an item for each relationship (we can have many relationships for the same relative Entity):
let relativesIDs = item.objectIDs(forRelationshipNamed: relationship.name)
So, we can use these IDs to fetch records from another class.
static func getRelatives(of item: T, context:NSManagedObjectContext) -> [K]? {
guard let fetchRequest: NSFetchRequest<K> = K.fetchRequest() as? NSFetchRequest<K> else { return nil }
fetchRequest.fetchBatchSize = 100
var results: [K]? = nil
var resultSet: Set<K> = [] // doesn't allow duplicates
let relationships = T.entity().relationships(forDestination: K.entity())
for relationship in relationships {
let relativesIDs = item.objectIDs(forRelationshipNamed: relationship.name)
let predicate = NSPredicate(format: "self IN %#", relativesIDs)
fetchRequest.predicate = predicate
var batchResults: [K] = []
do {
batchResults = try context.fetch(fetchRequest)
} catch {
assert(false, error.localizedDescription)
} //TODO: Handle Errors
if batchResults.count > 0 { resultSet = resultSet.union(Set(batchResults)) }
}
if resultSet.count > 0 { results = Array(resultSet) }
return results
}
I'm not sure that this is the most elegant solution, but it works :-)

Casting to protocol and using value?

Basically the issue is the RawRepresentable part of the code, I need to be able to get the value of it, but because I need to cast to a protocol it doesn't allow me to use the rawValue. Any workaround for this?
public protocol Serializable {
func dictionary() -> [String: Any]
}
extension Serializable {
func dictionary() -> [String: Any] {
var result = [String: Any]()
let mirror = Mirror(reflecting: self)
for child in mirror.children {
guard let label = child.label else { continue }
switch child.value {
case let serializable as Serializable:
result[label] = serializable.dictionary()
// Compile error here
case let rawRepresentable as RawRepresentable:
result[label] = rawRepresentable.rawValue
default:
result[label] = child.value
}
}
return result
}
}
I think this comes down to issues trying to use an associatedType outside of the enum.
I fixed it like this:
public protocol Serializable {
func dictionary() -> [String: Any]
}
extension Serializable {
func dictionary() -> [String: Any] {
var result = [String: Any]()
let mirror = Mirror(reflecting: self)
for child in mirror.children {
guard let label = child.label else { continue }
switch child.value {
case let serializable as Serializable:
result[label] = serializable.dictionary()
case let rawRepresentable as RawRepresentable:
let value = rawRepresentable.getValueAsAny()
result[label] = value
default:
result[label] = child.value
}
}
return result
}
}
extension RawRepresentable {
func getValueAsAny() -> Any {
return rawValue as Any
}
}

Get a type of Element of an array in Swift (through reflection)

Let say I have following code
class Foo {
}
var fooArray : Array<Foo> = Array<Foo>()
// This is important because in my code I will get Any (vs Array<Foo)
var fooArrayAny : Any = foo
I want to be able to get a Type Foo out of variable fooArrayAny.
If I had fooArray, I would do something like that:
let type = fooArray.dynamicType.Element().dynamicType
However, this doesn't work with fooArrayAny. It says that it has no member Element()
If you set NSObject as the base class of Foo, then you could use the following code:
class EVReflectionTests: XCTestCase {
func testArrayInstance() {
let fooArray : Array<Foo> = Array<Foo>()
let fooArrayAny : Any = fooArray
if let arr = fooArray as? Array {
let i = arr.getArrayTypeInstance(arr)
print("i = \(i)")
}
}
}
class Foo: NSObject {
}
extension Array {
public func getArrayTypeInstance<T>(arr:Array<T>) -> T {
return arr.getTypeInstance()
}
public func getTypeInstance<T>() -> T {
let nsobjectype : NSObject.Type = T.self as! NSObject.Type
let nsobject: NSObject = nsobjectype.init()
return nsobject as! T
}
}
This code is a snippet of my library EVReflection
Update:
I noticed a mistake in the code above. I used fooArray instead of fooArrayAny. When changing that to fooArrayAny I get the same error as you that the compiler does not have the Element. After playing around with this, I found out a solution that does work. Again it has parts of code of my EVReflection library.
class EVReflectionTests: XCTestCase {
func testArrayInstance() {
let fooArray : Array<Foo> = Array<Foo>()
let fooArrayAny : Any = fooArray
if let _ = fooArrayAny as? NSArray {
var subtype: String = "\(Mirror(reflecting: fooArrayAny))"
subtype = subtype.substringFromIndex((subtype.componentsSeparatedByString("<") [0] + "<").endIndex)
subtype = subtype.substringToIndex(subtype.endIndex.predecessor())
print("The type of the array elements = \(subtype)")
if let instance = swiftClassFromString(subtype) {
print("An instance of the array element = \(instance)")
let type = instance.dynamicType
print("An instance of the array element = \(type)")
}
}
}
// All code below is a copy from the EVReflection library.
func swiftClassFromString(className: String) -> NSObject? {
var result: NSObject? = nil
if className == "NSObject" {
return NSObject()
}
if let anyobjectype : AnyObject.Type = swiftClassTypeFromString(className) {
if let nsobjectype : NSObject.Type = anyobjectype as? NSObject.Type {
let nsobject: NSObject = nsobjectype.init()
result = nsobject
}
}
return result
}
func swiftClassTypeFromString(className: String) -> AnyClass! {
if className.hasPrefix("_Tt") {
return NSClassFromString(className)
}
var classStringName = className
if className.rangeOfString(".", options: NSStringCompareOptions.CaseInsensitiveSearch) == nil {
let appName = getCleanAppName()
classStringName = "\(appName).\(className)"
}
return NSClassFromString(classStringName)
}
func getCleanAppName(forObject: NSObject? = nil)-> String {
var bundle = NSBundle.mainBundle()
if forObject != nil {
bundle = NSBundle(forClass: forObject!.dynamicType)
}
var appName = bundle.infoDictionary?["CFBundleName"] as? String ?? ""
if appName == "" {
if bundle.bundleIdentifier == nil {
bundle = NSBundle(forClass: EVReflection().dynamicType)
}
appName = (bundle.bundleIdentifier!).characters.split(isSeparator: {$0 == "."}).map({ String($0) }).last ?? ""
}
let cleanAppName = appName
.stringByReplacingOccurrencesOfString(" ", withString: "_", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
.stringByReplacingOccurrencesOfString("-", withString: "_", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
return cleanAppName
}
}
class Foo: NSObject {
}
The output of this code will be:
The type of the array elements = Foo
An instance of the array element = <EVReflection_iOS_Tests.Foo: 0x7fd6c20173d0>
An instance of the array element = Foo
Swift 5
Its old but I want to share my version if someone needs it.
I use ModelProtocol and I suggests you use protocol so we can do operation to model via protocol (ex: static instantiating).
protocol ModelProtocol {}
class Foo: ModelProtocol {
}
Since I can't check type is Array, I use CollectionProtocol and create Array extension to get Element via protocol.
protocol CollectionProtocol {
static func getElement() -> Any.Type
}
extension Array: CollectionProtocol {
static func getElement() -> Any.Type {
return Element.self
}
}
Testing.
var fooArray: Array<Foo> = Array<Foo>()
var fooArrayAny: Any = fooArray
let arrayMirrorType = type(of: fooArrayAny)
String(describing: "arrayMirrorType: \(arrayMirrorType)")
if arrayMirrorType is CollectionProtocol.Type {
let collectionType = arrayMirrorType as! CollectionProtocol.Type
String(describing: "collectionType: \(collectionType)")
let elementType = collectionType.getElement()
String(describing: "elementType: \(elementType)")
let modelType = elementType as! ModelProtocol.Type
String(describing: "modelType: \(modelType)")
// ... now you can do operation to modelType via ModelProtocol
}
Printing.
arrayMirrorType: Array<Foo>
collectionType: Array<Foo>
elementType: Foo
modelType: Foo
class Foo {
var foo: Int = 1
}
struct Boo {
var boo: String = "alfa"
}
func f(array: Any) {
let mirror = Mirror(reflecting: array)
let arraytype = mirror.subjectType
switch arraytype {
case is Array<Foo>.Type:
let fooArray = array as! Array<Foo>
print(fooArray)
case is Array<Boo>.Type:
let booArray = array as! Array<Boo>
print(booArray)
default:
print("array is not Array<Foo> nor Array<Boo>")
break
}
}
var fooArray : Array<Foo> = []
fooArray.append(Foo())
var anyArray : Any = fooArray // cast as Any
f(anyArray) // [Foo]
var booArray : Array<Boo> = []
booArray.append(Boo())
anyArray = booArray // cast as Any
f(anyArray) // [Boo(boo: "alfa")]
var intArray : Array<Int> = []
anyArray = intArray // cast as Any
f(anyArray) // array is not Array<Foo> nor Array<Boo>