18

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.

2
  • var countries = [Country](); for countryDict in countries { let aCountry = Country.init(name: countryDict["name"] dial_code:countryDict["dial_code"] code:countryDict[code"]); countries.append(aCountry);}. Code written here, might not compile but it's to give you the idea. Commented Dec 7, 2017 at 14:08
  • 1
    There doesn't appear to be any reason to inherit from NSObject. I would suggest using a struct for this simple data type Commented Dec 7, 2017 at 14:51

10 Answers 10

52

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

Sign up to request clarification or add additional context in comments.

5 Comments

Why do you nest the do-catch?
Classic Xcode. As OP is seems pretty inexperienced, maybe change it to how it "should" be so we don't teach him the wrong patterns?
Also, you can do countries.forEach(print), a little bit cleaner.
Hm. Works for me. Can't explain that.
@OscarApeland forEach(print) doesn't work, because of the signature of print: func print(_ items: Any..., separator: String = default, terminator: String = default) . Unfortunately, the compiler doesn't automatically generate thunks that fill in defaulted arguements, so print (which has type (Any..., String, String) -> Void can't be treated as (Any...) -> Void, as would be necessary for passing it into forEach.
7

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

13 Comments

@PrashantTukadiya I would extract this code into a new initializer of country
@Alexander I didn't get you !! , Yes you need to create Country object for every item in array
@PrashantTukadiya I'm saying that this guard let code should be in its own initializer. Perhaps Conutry.init(fromDict: [String: String])
@Alexander Yes We can !! But for that we need to create of optional Country object as Country init may not been created if full detail not passed. So I picked a straight and simple way
@PrashantTukadiya So use a failable initializer. See my answer
|
5

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)

Comments

4

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:))

4 Comments

Swift can infer which initializer matches the resulting type and flatMap has been replaced by compactMap as you know. .compactMap(Country.init). Btw There is no dict parameter and casting from String to String is pointless.
@LeoDabus The param was meant to be [String: String]. This was written before flatMap. I'm not sure if I should update it. In new code, I would be doing this with codable, and not write any of this haha
You are still using the wrong parameter name. Regarding flatMap that’s why I said as you know. Btw Codable was already released when this was asked.
@LeoDabus Interesting, I have no idea why I didn't use that, then lol. In fairness, I was much more nooby 4 years ago haha :)
2

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

1 Comment

You are still required to explicitly set the return type. Btw OP is using an array of dictionaries not a single dictionary. I would also get rid of that force unwrap syntax.
1

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

}

3 Comments

Better to use map than repeated appends in a for loop
@Alexander - thanks for your remark - let me see what's difference between both. Till date I've tried to check how map is better :)
1) It's more concise, so it's more easy to see what's being done. 2) It allows arrayOfCountries to remain a let constant, unlike repeated-appends which force it to be a var variabele. 3) map automatically calls Array.reserveCapcity(_:), so it removes allocation overhead that can really slow the repeated-append approach.
1

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 }

Comments

0

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

3 Comments

Don't use forEach to do append! Just use map!
What is the difference?
You don't need a separate line to define countryArray, you're not forced to have it be mutable, and map calls reserveCapacity(_:) to prevent reallocation overhead on array expansion.
0

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
}

Comments

-1

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

1 Comment

Don't use forEach to append! Just use map!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.