Artyom
Artyom

Reputation: 391

Convert Array of Dictionary to custom object swift

I have:

    let countries : [[String : Any]] = [
            [
                "name" : "Afghanistan",
                "dial_code": "+93",
                "code": "AF"
            ],
            [
                "name": "Aland Islands",
                "dial_code": "+358",
                "code": "AX"
            ],
            [
                "name": "Albania",
                "dial_code": "+355",
                "code": "AL"
            ],
            [
                "name": "Algeria",
                "dial_code": "+213",
                "code": "DZ"
            ]
]

I want to add all this array of dictionary to my custom object like

let country:[Country] = countries

My custom object looks like this:

class Country: NSObject {
        let name: String
        let dial_code : String
        let code: String

        init(name: String, dial_code: String, code: String) {
            self.name = name
            self.dial_code = dial_code
            self.code = code
        }
    }

I understand that I need a loop thru the array but idk what is the next step. Would be great to have an example.

Upvotes: 18

Views: 26662

Answers (10)

Okhan Okbay
Okhan Okbay

Reputation: 1394

Here is a useful extension that infers the type from the pre-defined return type:

extension Dictionary {
    func castToObject<T: Decodable>() -> T? {
        let json = try? JSONSerialization.data(withJSONObject: self)
        return json == nil ? nil : try? JSONDecoder().decode(T.self, from: json!)
    }
}

Usage would be:

struct Person: Decodable {
  let name: String
}

let person: Person? = ["name": "John"].castToObject()
print(person?.name) // Optional("John")

Upvotes: 2

Alexander
Alexander

Reputation: 63137

There are a lot of answers already, but I find that there are short comings with most of them. This is what I would suggest:

extension Country {
    init?(fromDict dict: [String: Any]) {
        guard let name = dict["name"] as? String, 
              let dialCode = dict["dial_code"] as? String, 
              let code = dict["code"] as? String else {
            return nil
        }
        self.init(name: name, dialCode: dialCode, code: code)
    }
}

let countries = countryDictionaries.map { dict -> Country in
    if let country = Country(fromDict: dict) { return Country }
    else {
        preconditionFailure("Tried to convert an invalid dict into a country")
        // TODO: handle error appropriately
    }
}

If you just want to ignore invalid country dictionaries, that's even easier:

let countries = countryDictionaries.flatMap(Country.init(fromDict:))

Upvotes: 4

Prashant Tukadiya
Prashant Tukadiya

Reputation: 16416

Not related but remove NSObject until you are required

That is very simple thing you just need to think a bit

Create Object like this

var arr = [Country]()

Now Loop your array of dictionary

  for dict in countries {
      // Condition required to check for type safety :)
        guard let name = dict["name"] as? String, 
              let dialCode = dict["dial_code"] as? String, 
              let code = dict["code"] as? String else {
              print("Something is not well")
             continue
         }
        let object = Country(name: name, dial_code:dialCode, code:code)
         arr.append(object)
    }
  

That's it You have converted array of dict to Custom Object

Hope it is helpful to you

Upvotes: 7

Leo Dabus
Leo Dabus

Reputation: 236260

You should make your Country conform to Codable protocol, convert your dictionary to JSON data using JSONSerialization and then just decode the Data using JSONDecoder, note that you can set its keyDecodingStrategy property to convertFromSnakeCase auto avoid the need to declare custom coding keys like dial_Code:

struct Country: Codable {
    let name: String
    let dialCode : String
    let code: String
}

do {
    let json = try JSONSerialization.data(withJSONObject: countries)
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let decodedCountries = try decoder.decode([Country].self, from: json)
    decodedCountries.forEach{print($0)}
} catch {
    print(error)
}

Country(name: "Afghanistan", dialCode: "+93", code: "AF")

Country(name: "Aland Islands", dialCode: "+358", code: "AX")

Country(name: "Albania", dialCode: "+355", code: "AL")

Country(name: "Algeria", dialCode: "+213", code: "DZ")

Upvotes: 53

user9026796
user9026796

Reputation:

Create a custom country class with param json [String : Any]

class Country: NSObject {
    var name: String?
    var dialCode: String?
    var code: String?

    init(json: [String : Any]) {
       self.name = json["name"] as? String
       self.dialCode = json["dial_code"] as? String
       self.code = json["code"] as? String
    }
}

Later you can map the dictionary into the array of country using

let _ = countries.flatMap { Country.init }

Upvotes: 1

Krunal
Krunal

Reputation: 79636

Very simple and clear solution:

  • Create custom initializer with param json [String : Any] in your class Country.
  • Init all variables of class using loop in custom initializer.

Try this code:

class Country: NSObject {
    var name: String = ""
    var dial_code: String = ""
    var code: String = ""

    // Sol: 1
    init(json: [String : Any]) {
        if let name = json["name"] as? String, let dial_code = json["dial_code"] as? String, let code = json["name"] as? String {
            self.name = name
            self.dial_code = dial_code
            self.code = code
        }
    }

    // or Sol: 2
    init(name: String, dial_code: String, code: String) {
        self.name = name
        self.dial_code = dial_code
        self.code = code
    }
}
  • Create an instance of class Countries using element of array countries and collect the same in separate array arrayOfCountries

Try this code:

let countries : [[String : Any]] = [
    [
        "name" : "Afghanistan",
        "dial_code": "+93",
        "code": "AF"
    ],
    [
        "name": "Aland Islands",
        "dial_code": "+358",
        "code": "AX"
    ],
    [
        "name": "Albania",
        "dial_code": "+355",
        "code": "AL"
    ],
    [
        "name": "Algeria",
        "dial_code": "+213",
        "code": "DZ"
    ]
]

var arrayOfCountries = [Country]()

// Sol: 1
for json in countries {
    let country = Country(json: json)
    print("country name - \(country.name)")
    arrayOfCountries.append(country)
}

// Sol: 2
for json in countries {

    if let name = json["name"] as? String, let dial_code = json["dial_code"] as? String, let code = json["name"] as? String {
        let country = Country(name: name, dial_code: dial_code, code: code)
        print("country name - \(country.name)")
        arrayOfCountries.append(country)
    }

}

Upvotes: 1

tanz
tanz

Reputation: 2547

You can map the dictionary into the array. As a dictionary always returns an optional value for key (the value is not guaranteed to exist) you need a guard to make sure you proceed only if that is the case.

In this sample solution I throw if any of the values are not there - but this is really up to you to decide.

struct AnError: Error {}

do {
    let countryObjects: [Country] = try countries.map {
        guard let name = $0["name"] as? String,
              let dial_code = $0["dial_code"] as? String,
              let code = $0["code"] as? String else {throw AnError()}

        return Country(name: name, dial_code: dial_code, code: code)
    }
}
catch {
    //something went worng - handle the error
}

Upvotes: 0

Nizar Ahmed
Nizar Ahmed

Reputation: 180

you can use Array.foreach like this

countries.forEach{country.append(Country($0))}

and u may change init parameters of Country to [String: Any], or cast $0 to [String: Any] and read ur values from it and send them to init

Upvotes: -1

kender
kender

Reputation: 87131

You can use flatMap method of a list to produce the result:

countries.flatMap { (v: [String: Any]) -> Country? in
    if let name = v["name"] as? String, 
       let dial = v["dial_code"] as? String, 
       let code = v["code"] as? String {
        return Country(name: name, dial_code: dial, code: code)
    } else {
        return nil
    }
}

A full example would be:

//: Playground - noun: a place where people can play

import UIKit

let countries : [[String : Any]] = [
    [
        "name" : "Afghanistan",
        "dial_code": "+93",
        "code": "AF"
    ],
    [
        "name": "Aland Islands",
        "dial_code": "+358",
        "code": "AX"
    ],
    [
        "name": "Albania",
        "dial_code": "+355",
        "code": "AL"
    ],
    [
        "name": "Algeria",
        "dial_code": "+213",
        "code": "DZ"
    ]
]

class Country: NSObject {
    let name: String
    let dial_code : String
    let code: String

    init(name: String, dial_code: String, code: String) {
        self.name = name
        self.dial_code = dial_code
        self.code = code
    }
}

let cnt = countries.flatMap { (v: [String: Any]) -> Country? in
    if let name = v["name"] as? String, let dial = v["dial_code"] as? String, let code = v["code"] as? String {
        return Country(name: name, dial_code: dial, code: code)
    } else {
        return nil
    }
}

print (cnt)

Upvotes: 5

Alper
Alper

Reputation: 1425

First you need to initialize an empty array type of Country Class

var countryArray = [Country]()
//then you have to loop thru the countries dictionary 
//and after casting them adding it to this empty array with class initializer  

countries.forEach { (dict) in

    countryArray.append(Country(name: dict["name"] as! String, dial_code: dict["dial_code"] as! String, code: dict["code"] as! String))

}
//this is how you reach to properties
countryArray.forEach { (country) in
    print(country.name)
}

Upvotes: 0

Related Questions