Home > Software design >  Swift - Return type of struct parameters without default values
Swift - Return type of struct parameters without default values

Time:03-30

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
}

CodePudding user response:

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"}

CodePudding user response:

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.

CodePudding user response:

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)
  • Related