Hello I'm new in Swift and I try to write a parser for json through extension with JsonSerialization
I want to implement a function to parse json file and in the same extension to implement the computable var json, which will form json
// Model
struct Todoitem {
let id = {
return UUID().uuidString
}()
let text : String
let importance : Importance
let deadline : Date?
}
enum Importance {
case usual, important, unimportant
}
//Parser
extension Todoitem {
static func parse(json : Any) -> Todoitem? {
if let file = try? JSONSerialization.jsonObject(with: json) as? [String : Any] {
}
return nil
}
}
Related
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.
I want to implement IgListKit for my UICollectionView. This Library requires me to use “Class Model : ListDiffable”
Accoding to my current architecture I have “Struct Model : Decodable” As I use JSON Decoder in my NetworkService to retrieve Data
I have 2 struct, 1 for the Root and the 2 for my Array.
struct Talents : Decodable {
let status : String?
let error : String?
let response : String?
}
struct Talent: Decodable {
let id: String
let name : String
let smallDesc: String
let largeDesc : String
}
\\I also have enum CodingKeys to match the keys for both structs
Following is the Struct output ,Works well to be used in my UICollectionView
when I change these structs to class
class Talents : Decodable {
var status : String?
var error : String?
var response : String?
init( status : String,error : String, response : String){
self.status = status
self.error = error
self.response = response
}
}
This is the Class Output I get, Which I am not sure how to use.
What are the changes I should make in order to resolve this, and apply : ListDiffable Protocol stubs to my Model class?
Service File with the instance of which in viewDidLoad of my CollectionVC I take the Data in an array.
static func getCategoryTalents(category:String,completion: #escaping (Bool, [Talent]?, Error?) -> Void) {
let parameters: Parameters = [
"filter": category,
"shuffle": 1
]
AF.request(Constants.baseUrl,
parameters : parameters ).response { response in
guard let data = response.data else {
DispatchQueue.main.async {
print("\(Error.self)")
completion(false, nil, Error.self as? Error)
}
return}
do {
let talentsResponse = try JSONDecoder().decode(Talents.self, from: data)
print(talentsResponse)
let firstJSONString = talentsResponse.response?.replacingOccurrences(of: "\\", with: "")
let secondJSONString = firstJSONString?.replacingOccurrences(of: "\"{", with: "{").replacingOccurrences(of: "}\"", with: "}")
guard let talentArray = try! JSONDecoder().decode([Talent]?.self, from: (secondJSONString?.data(using: .utf8)!)!) else {
return }
print(talentArray)
var talents = [Talent]()
for talent in talentArray {
guard let individualTalent = talent as Talent? else { continue }
talents.append(individualTalent)
}
DispatchQueue.main.async {
completion(true, talents, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
}
You don't need to change the existing struct to class create a new class and make an initializer which accepts struct as the parameter:
struct TalentDataModel: Decodable {
let status : String?
let error : String?
let response : String?
}
class Talents: Decodable {
var status : String?
var error : String?
var response : String?
init(dataModel: TalentDataModel) {
status = dataModel.status
error = dataModel.error
response = dataModel.response
}
}
Since it is a whole lot of work to make serve Models with srtucts as seen here
I changed my class to make it work with IGListKit.
import Foundation
import IGListKit
class Talents: NSObject,Decodable {
let status : String
let error : String
let response : String
init(status:String,error:String,response:String) {
self.status = status
self.error = error
self.response = response
}
}
extension NSObject: ListDiffable {
public func diffIdentifier() -> NSObjectProtocol {
return self
}
public func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
return isEqual(object)
}
}
Hi I am struggle to solve the problem dynamic protocol conformance in swift language. Please see code.
Protocol:
protocol Object {
init(by object: [String: Any])
}
Custom structs with protocol object conformance:
struct Tree: Object {
let treeName: String
init(by object: [String: Any]) {
self.treeName = object["tree"] as? String ?? "Notree"
}
}
struct Plant: Object {
let plantName: String
init(by object: [String : Any]) {
self.plantName = object["tree"] as? String ?? ""
}
}
The above code just fine until the object is [String: Any]. I can't use [[String: Any]] like below.
let coconut = ["tree":"Coconut"] // => This fine
let allTrees = [["tree":"Apple"],["tree":"Orange"],["tree":"Jakfruit"]] //=> Here is the problem
let aTree = Tree(by: coconut)
let bTree = Tree(by: ["data":allTrees])
let cTree = Plant(by: ["data":allTrees])
I can't use array of objects. So, I used to store objects in to key "data". Now I used extension: Array confirm protocol object.
extension Array: Object where Element == Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) -> Object in
// return Plant.init(by: object) // => Works, But I need dynamic confirmance
// return Tree.init(by: object) // => Works, But I need dynamic confirmance
return Object.init(by: object) //=> How can I do?
})
}else{
self = []
}
}
}
The return Object shows error Protocol type 'Object' cannot be instantiated. I tried lot to solve but not able.
Can someone suggest better idea or solution for this problem? Thank you in advance...
First, you should not use the constraint == Object. You want to say that not only [Object] is an Object, but also [Plant] and [Tree] are Objects too, right? For that, you should use the : Object constraint. Second, you can use Element.init to initialise a new Element of the array. Because of the constraint Element : Object, we know that a init(by:) initialiser exists:
extension Array: Object where Element: Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) in
return Element.init(by: object)
})
}else{
self = []
}
}
}
Usage:
let trees = [Tree](by: ["data": allTrees])
Here's what I think a more Swifty version of your code, making use of failable initialisers - initialisers that return nil when they fail to initialise the object:
protocol Object {
init?(by object: [String: Any])
}
struct Tree: Object {
let treeName: String
init?(by object: [String: Any]) {
if let treeName = object["tree"] as? String {
self.treeName = treeName
} else {
return nil
}
}
}
struct Plant: Object {
let plantName: String
init?(by object: [String : Any]) {
if let plantName = object["tree"] as? String {
self.plantName = plantName
} else {
return nil
}
}
}
extension Array: Object where Element: Object{
init?(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.compactMap(Element.init)
}else{
return nil
}
}
}
when i call API and get response from server with Alamofire, i want use "data" object from json
this data come from API
{
"code": 200,
"hasError": false,
"data": [
{
"userSession": "43a1bd70-26bf-11e9-9ccd-00163eaf6bb4"
}
],
"message": "ok"
}
and i want map data to my AuthModel
this is my AuthModel:
struct AuthModel: Codable {
let userSession: String
enum CodingKeys: String, CodingKey {
case userSession = "userSession"
}
}
i coded this lines but it isn't work:
if let responseObject = response.result.value as? Dictionary<String,Any> {
if let hasError = responseObject["hasError"] as? Bool {
guard !hasError else { return }
do {
let decoder = JSONDecoder()
let authModel = try decoder.decode(AuthModel.self, from: responseObject["data"])
} catch {
print("Parse Error: ",error)
}
}
}
this does not work because responseObject["data"] is not NSData Type
Cannot convert value of type '[String : Any]' to expected argument type 'Data'
I think your API response is a pattern that indicates:
Do we have any problem (error)?
Do we have our expected data?
Based on these, we can use Enum and Generics. For example:
class ResponseObject<T: Codable>: Codable {
private var code : Int
private var hasError : Bool
private var message : String
private var data : T?
var result: Result {
guard !hasError else { return .error(code, message) }
guard let data = data else { return .error(0, "Data is not ready.") }
return .value(data)
}
enum Result {
case error(Int, String)
case value(T)
}
}
and we can use ResponseObject with our expected data:
let responseString = """
{
"code": 200,
"hasError": false,
"data": [
{
"userSession": "43a1bd70-26bf-11e9-9ccd-00163eaf6bb4"
}
],
"message": "ok"
}
"""
class AuthObject: Codable {
var userSession : String
}
if let jsonData = responseString.data(using: .utf8) {
do {
//ResponseObject<[AuthObject]> means: if we don't have error, the `data` object in response, will represent `[AuthObject]`.
let responseObject = try JSONDecoder().decode(ResponseObject<[AuthObject]>.self, from: jsonData)
//Using ResponseObject.Result Enum: We have error with related code and message, OR, we have our expected data.
switch responseObject.result {
case .error(let code, let message):
print("Error: \(code) - \(message)")
case .value(let authObjects):
print(authObjects.first!.userSession)
}
} catch {
print(error.localizedDescription)
}
}
Get the Data response rather than the deserialized Dictionary for example
Alamofire.request(url).responseData { response in
and decode
let decoder = JSONDecoder()
let authModel = try decoder.decode(AuthModel.self, from: response.data!)
into these structs
struct AuthModel : Decodable {
let code : Int
let hasError : Bool
let message : String
let data : [Session]
}
struct Session : Decodable {
let userSession: String
}
All CodingKeys are synthesized.
I'm implementing a model:
It has structs ClientSummary and ClientDetails
ClientDetails struct has all properties of ClientSummary struct + some extra properties
Both structs have main initializer init(jsonDictionary: [String: Any])
inits of ClientSummary and ClientDetails share big part of the code
There is an extension which will work with shared functionality of those structs.
The most straightforward solution which came to my mind is just classic inheritance, but it doesn't work for value types.
I'm trying to solve that with protocols, but I can't implement those "shared inits". I was trying to move shared part of the init to the protocol extension but can't really make it. There are various errors.
Here is the test code.
protocol Client {
var name: String { get }
var age: Int { get }
var dateOfBirth: Date { get }
init?(jsonDictionary: [String: Any])
}
struct ClientSummary: Client {
let name: String
let age: Int
let dateOfBirth: Date
init?(jsonDictionary: [String: Any]) {
guard let name = jsonDictionary["name"] as? String else {
return nil
}
self.name = name
age = 1
dateOfBirth = Date()
}
}
struct ClientDetails: Client {
let name: String
let age: Int
let dateOfBirth: Date
let visitHistory: [Date: String]?
init?(jsonDictionary: [String: Any]) {
guard let name = jsonDictionary["name"] as? String else {
return nil
}
self.name = name
age = 1
dateOfBirth = Date()
visitHistory = [Date(): "Test"]
}
}
extension Client {
// A lot of helper methods here
var stringDOB: String {
return formatter.string(from: dateOfBirth)
}
}
Inheritance is the wrong tool here. It doesn't make sense to say "details IS-A summary." Details are not a kind of summary. Step away from the structural question of whether they share a lot of methods, and focus on the essential question of whether one is a kind of the other. (Sometimes renaming things can make that true, but as long as they're "summary" and "detail" it doesn't make sense to inherit.)
What can make sense is to say that details HAS-A summary. Composition, not inheritance. So you wind up with something like:
struct ClientDetails {
let summary: ClientSummary
let visitHistory: [Date: String]?
init?(jsonDictionary: [String: Any]) {
guard let summary = ClientSummary(jsonDictionary: jsonDictionary) else {
return nil
}
self.summary = summary
visitHistory = [Date(): "Test"]
}
// You can add these if you need them, or to conform to Client if that's still useful.
var name: String { return summary.name }
var age: Int { return summary.age }
var dateOfBirth: Date { return summary.dateOfBirth }
}
I often wish that Swift had a built-in way to separate out parts of init methods. However, it can be done, admittedly somewhat awkwardly, with tuples, as below:
struct S {
let foo: String
let bar: Int
let baz: Bool
init() {
(self.foo, self.bar, self.baz) = S.sharedSetup()
}
static func sharedSetup() -> (String, Int, Bool) {
...
}
}
In your case, the sharedSetup() method can be moved to the protocol extension, or wherever it's convenient to have it.
For structs you can use composition instead of relying on inheritance. Let's suppose you already have ClientSummary struct defined with the Client protocol:
protocol Client {
var name: String { get }
var age: Int { get }
var dateOfBirth: Date { get }
init?(jsonDictionary: [String: Any])
}
struct ClientSummary: Client {
let name: String
let age: Int
let dateOfBirth: Date
init?(jsonDictionary: [String: Any]) {
guard let name = jsonDictionary["name"] as? String else {
return nil
}
self.name = name
age = 1
dateOfBirth = Date()
}
}
Now to create ClientDetails sharing ClientSummary logic you can just create a ClientSummary property in ClientDetails. This way have the same initializer as ClientSummary with your additional type specific logic and with use of dynamicMemberLookup you can access ClientSummary properties on ClientDetails type:
#dynamicMemberLookup
struct ClientDetails {
var summary: ClientSummary
let visitHistory: [Date: String]?
init?(jsonDictionary: [String: Any]) {
guard let summary = ClientSummary(jsonDictionary: jsonDictionary) else {
return nil
}
self.summary = summary
visitHistory = [Date(): "Test"]
}
subscript<T>(dynamicMember path: KeyPath<ClientSummary, T>) -> T {
return summary[keyPath: path]
}
subscript<T>(dynamicMember path: WritableKeyPath<ClientSummary, T>) -> T {
get {
return summary[keyPath: path]
}
set {
summary[keyPath: path] = newValue
}
}
subscript<T>(dynamicMember path: ReferenceWritableKeyPath<ClientSummary, T>) -> T {
get {
return summary[keyPath: path]
}
set {
summary[keyPath: path] = newValue
}
}
}
There is an extension which will work with shared functionality of those structs.
Now sharing code between ClientSummary and ClientDetails is tricky. By using dynamicMemberLookup you will be able to access all the properties in ClientSummary from ClientDetails but methods from ClientSummary can't be invoked this way. There is proposal to fulfill protocol requirements with dynamicMemberLookup which should allow you to share methods between ClientSummary and ClientDetails for now you have to invoke ClientSummary methods on ClientDetails using the summary property.