I'm trying to read a text file and return an array of dictionary in swift
the text file has the following data:
13582;Name 1;12345;5
13583;Name 2;23456;5
13585;Name 3;EX934;6
13598;Name 4;XE345_c;6
13600;Name 5;XF8765;6
func machineNumberToName() -> [[String: String]] {
var dic1 = [String: String]()
var dic2 = [String: String]()
var dic3 = [String: String]()
var dic4 = [String: String]()
// FileName for machines
let fileName = "Machines.txt";
if let path = Bundle.main.path(forResource: fileName, ofType: nil) {
do {
let contents = try! String(contentsOfFile: path)
let lines = contents.split(separator: "\n")
for line in lines {
var entries = lines.split(separator: ";")
dic1["machineNumber"] = entries[0]
dic2["machineName"] = entries[1]
dic3["machineXML"] = entries[2]
dic4["wifi"] = entries[3]
return [dic1, dic2, dic3, dic4]
}
} catch {
print(error.localizedDescription)
}
} else {
NSLog("file not found: \(fileName)")
return []
}
}
however I get the error
Cannot assign value of type 'Array<String.SubSequence>.SubSequence' (aka 'ArraySlice<Substring>') to subscript of type 'String'
Not sure what I'm doing wrong!
entries is not an array of String, it is an array of ArraySlice<Substring>, or informally an array of substrings.
You can use String(entries[0]) to get a string to put in your dictionary.
You have another problem though; You will only end up with the first line in the dictionaries, since you return out of the loop. Even if you fix that, returning an array of dictionaries is icky. Create an appropriate struct and return an array of those structs
struct MachineDetails {
let machineNumber: String
let machineName: String
let machineXML: String
let machineWiFi: String
}
func getMachineDetails() -> [MachineDetails] {
var details = [MachineDetails]()
let fileName = "Machines.txt";
if let path = Bundle.main.path(forResource: fileName, ofType: nil) {
do {
let contents = try String(contentsOfFile: path)
let lines = contents.split(separator: "\n")
for line in lines {
let entries = line.split(separator: ";").map { String($0) }
if entries.count == 4 {
let newMachine = MachineDetails(machineNumber:entries[0],
machineName:entries[1],
machineXML:entries[2],
machineWiFi:entries[3])
details.append(newMachine)
} else {
print("Malformed line \(line)")
}
}
} catch {
print(error.localizedDescription)
}
} else {
NSLog("file not found: \(fileName)")
}
return details
}
I have an App written in Swift 3.0 and I declared the following data types:
var movies = [Movie]()
var getPlist = NSMutableDictionary()
var movieItems = NSMutableDictionary()
And I have the following method which is loading the content of a plist:
// Connect to plist and get the data
if let plist = PlistHandler(name: "MovieData") {
getPlist = plist.getMutablePlistDict()!
// Load the movie items into the table view data source
for i in 0..<getPlist.count {
movieItems = (getPlist.object(forKey: "Item\(i)") as! NSMutableDictionary) as! [String: String] as! NSMutableDictionary
let newName = movieItems.object(forKey: "Name")
let newRemark = movieItems.object(forKey: "Remark")
if newName as? String != "" {
movies.append(Movie(name: newName as? String, remark: newRemark as? String)
)}
}
} else {
print("Unable to get Plist")
}
It calls a method called getMutablePlistDict() from another class:
// Get the values from plist -> MutableDirectory
func getMutablePlistDict() -> NSMutableDictionary? {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destPath!) {
guard let dict = NSMutableDictionary(contentsOfFile: destPath!) else { return .none }
return dict
} else {
return .none
}
}
When I run the App I get the error above (see question title). But this is new. In Xcode 8 I didn't get this error. What is the reason for this and how I have to change my code to avoid that?
You can use like this :
Changed NSMutableDictionary to [String: Any] :
var movies = [Movie]()
var getPlist: [String: Any] = [:]
var movieItems: [String: Any] = [:]
func getMutablePlistDict() -> [String: Any] {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destPath!) {
if let dict = NSDictionary(contentsOfFile: destPath!) as? [String: Any] {
return dict
}
} else {
return [:]
}
}
if let plist = PlistHandler(name: "MovieData") {
let getPlist = plist.getMutablePlistDict()
// Load the movie items into the table view data source
for i in 0..<getPlist.count {
if let movieItemsCheck = getPlist["Item\(i)"] as? [String: Any] {
movieItems = movieItemsCheck
if let newName = movieItems["Name"] as? String, let newRemark = movieItems["Remark"] as? String, newName != "" {
movies.append(Movie(name: newName, remark: newRemark))
}
}
}
} else {
print("Unable to get Plist")
}
So here you can see my method which fetches JSON. My problem is that I want my loop to go through every object. Not one object 10 times like it is now. I know that player["1"] is causing it to fetch first object repeatedly, it is just for the sake of the example. I need to get every player information. Could someone please fix this logic and little bit explain the situation.
var homeTeamPlayers: [MatchUp]? = []
let urlRequest = URLRequest(url: URL(string: "http://www.fibalivestats.com/data/586746/data.json")!)
func fetchHomeTeam() {
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error!)
return
}
homeTeamPlayers = [MatchUp]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
if let teamsFromJSON = json["tm"] as? [String : AnyObject] {
if let homeTeam = teamsFromJSON["1"] as? [String : AnyObject] {
if let player = homeTeam["pl"] as? [String : AnyObject] {
for _ in player {
let homeTeamPlayer = MatchUp()
if let firstPlayer = player["1"] as? [String : AnyObject] {
if let name = firstPlayer["name"] as? String {
homeTeamPlayer.homeTeam_name = name
}
}
homeTeamPlayers?.append(homeTeamPlayer)
}
}
}
}
} catch let error {
print(error)
}
}
task.resume()
}
Here is the JSON I would like to fetch...
{
tm: {
1: {
pl: {
1: {
name: "R. Miniotas"
},
2: {
name: "T. Delininkaitis"
},
3: {
name: "V. Cizauskas"
},
4: {
name: "T. Klimavicius"
},
5: {
name: "V. Lipkevicius"
},
6: {
name: "M. LinkeviÄius"
},
7: {
name: "D. Seskus"
},
8: {
name: "T. Michnevicius"
},
9: {
name: "D. Gvezdauskas"
},
11: {
name: "A. Valeika"
}
}
You need to enumerate the dictionary using for (key, value) in syntax:
if let players = homeTeam["pl"] as? [String : Any] {
for (_, value) in players {
let homeTeamPlayer = MatchUp()
if let currentPlayer = value as? [String : Any],
let name = currentPlayer["name"] as? String {
homeTeamPlayer.homeTeam_name = name
}
homeTeamPlayers?.append(homeTeamPlayer)
}
}
However to avoid empty Matchup instances I'd recommend
if let players = homeTeam["pl"] as? [String : Any] {
for (_, value) in players {
if let currentPlayer = value as? [String : Any],
let name = currentPlayer["name"] as? String {
let homeTeamPlayer = MatchUp()
homeTeamPlayer.homeTeam_name = name
homeTeamPlayers?.append(homeTeamPlayer)
}
}
}
Note: The JSON dictionary in Swift 3 is [String:Any] and why is the homeTeamPlayers array optional?
And finally – as always - .mutableContainers is completely meaningless in Swift.
Your JSON data is a dictionary of dictionaries of dictionaries.
It looks like there is an outer key "tm" (teams) that then contains a dictionary with keys "1", "2", etc for each team, and then inside the teams, there are more dictionaries that again use the keys "1", "2", "3", etc for the players. This is not a great structure.
If you don't care about the order in which you fetch the items from your dictionaries then you can use the syntax:
for (key, value) in dictionary
...to loop through all the key/value pairs in a dictionary, but you do need to be aware that the order you get the key/value pairs is not defined. It might give you the entries in key order sometimes, and not in order other times.
If order is important then you would need to fetch the keys first, sort them, and then fetch the items (or some other technique). Getting the values sorted by key might look like this:
let keys = dictionary.keys.sorted($0 < $1)
for aKey in keys {
let aValue = dictionary[aKey]
//Do whatever you need to do with this entry
}
EDIT:
Your JSON data needed some editing to make it legal JSON:
{
"tm" : {
"1" : {
"pl" : {
"7" : {
"name" : "D. Seskus"
},
"3" : {
"name" : "V. Cizauskas"
},
"8" : {
"name" : "T. Michnevicius"
},
"4" : {
"name" : "T. Klimavicius"
},
"11" : {
"name" : "A. Valeika"
},
"9" : {
"name" : "D. Gvezdauskas"
},
"5" : {
"name" : "V. Lipkevicius"
},
"1" : {
"name" : "R. Miniotas"
},
"6" : {
"name" : "M. LinkeviÃÂius"
},
"2" : {
"name" : "T. Delininkaitis"
}
}
}
}
}
(All the keys and values had to be enclosed in quotes, and I needed to add closing braces to terminate the object graph)
I wrote code that took the above and read it into a JSON object, and then wrote it back out to JSON without whitespace. I then converted all the quotes to \".
Here is tested code that parses the JSON into objects, and then walks your data structure, with quite a bit of error checking:
let jsonString = "{\"tm\":{\"1\":{\"pl\":{\"7\":{\"name\":\"D. Seskus\"},\"3\":{\"name\":\"V. Cizauskas\"},\"8\":{\"name\":\"T. Michnevicius\"},\"4\":{\"name\":\"T. Klimavicius\"},\"11\":{\"name\":\"A. Valeika\"},\"9\":{\"name\":\"D. Gvezdauskas\"},\"5\":{\"name\":\"V. Lipkevicius\"},\"1\":{\"name\":\"R. Miniotas\"},\"6\":{\"name\":\"M. LinkeviÃius\"},\"2\":{\"name\":\"T. Delininkaitis\"}}}}}"
guard let data = jsonString.data(using: .utf8) else {
fatalError("Unable to convert string to Data")
}
var jsonObjectOptional: Any? = nil
do {
jsonObjectOptional = try JSONSerialization.jsonObject(with: data, options: [])
} catch {
print("reading JSON failed with error \(error)")
}
guard let jsonObject = jsonObjectOptional as? [String: Any] else {
fatalError("Unable to read JSON data")
}
//Fetch the value for the "tm" key in the outer dictionary
guard let teamsFromJSON = jsonObject["tm"] as? [String : Any] else {
fatalError("Can't fetch dictionary from JSON[\"tm\"]")
}
let teamKeys = teamsFromJSON
.keys //Fetch all the keys from the teamsFromJSON dictionary
.sorted{$0.compare($1, options: .numeric) == .orderedAscending} //Sort the key strings in numeric order
print("teamsFromJSON = \(teamsFromJSON)")
//loop through the (sorted) team keys in teamKeys
for aTeamKey in teamKeys {
guard let aTeam = teamsFromJSON[aTeamKey] as? [String: Any] else {
print("Unable to read array of teams")
continue
}
//Fetch the dictionary of players for this team
guard let playersDict = aTeam["pl"] as? [String: Any] else {
print("Unable to read players dictionary from team \(aTeamKey)")
continue
}
//Fetch a sorted list of player keys
let playerKeys = playersDict
.keys
.sorted{$0.compare($1, options: .numeric) == .orderedAscending}
print("playerKeys = \(playerKeys)")
//Loop through the sorted array of player keys
for aPlayerKey in playerKeys {
//Fetch the value for this player key
guard let aPlayer = playersDict[aPlayerKey] as? [String: String] else {
print("Unable to cast aPlayer to type [String: String]")
continue
}
//Attempt to read the "name" entry for this player.
guard let playerName = aPlayer["name"] else {
continue
}
//Deal with a player in this team
print("In team \(aTeamKey), player \(aPlayerKey) name = \(playerName)")
}
}
if let jsonData = try? JSONSerialization.data(withJSONObject: jsonObject, options: []),
let jsonString = String(data: jsonData, encoding: .ascii) {
print("\n\njsonString = \n\(jsonString)")
}
The output of that code is:
playerKeys = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "11"]
In team 1, player 1 name = R. Miniotas
In team 1, player 2 name = T. Delininkaitis
In team 1, player 3 name = V. Cizauskas
In team 1, player 4 name = T. Klimavicius
In team 1, player 5 name = V. Lipkevicius
In team 1, player 6 name = M. LinkeviÃius
In team 1, player 7 name = D. Seskus
In team 1, player 8 name = T. Michnevicius
In team 1, player 9 name = D. Gvezdauskas
In team 1, player 11 name = A. Valeika
(Your data is missing an entry for player 10.)
Suppose this is my URL:
URL: https://maps.googleapis.com/maps/api/directions/json?origin=19.0176147,72.8561644&destination=26.98228,75.77469&sensor=false
The URL mentioned above when entered in browser gives me a JSON response from google.
In this response, I have to fetch the value of distance and duration.
So my swift code to fetch the value is:
do{
let data = try Data(contentsOf: url!)
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject]
if let routes = json?["routes"] as AnyObject? as? [[String: AnyObject]] {
//print(routes)
for r in routes {
if let legs = r["legs"] as? [[String: AnyObject]] {
for l in legs {
//Start Address
print("Start Address: \((l["start_address"]!) as Any)")
// End Address
print("End Address: \((l["end_address"]!) as Any)")
//Distance
if let distance = l["distance"] as? [String: AnyObject] {
distanceResult = (distance["text"]!) as Any as? String
print("Distance: \(distanceResult!)")
}
// Duration
if let duration = l["duration"] as? [String: AnyObject] {
durationResult = (duration["text"]!) as Any as? String
print("Duration: \(durationResult!)")
}
}
googleResult = distanceResult+durationResult
}
}
}
}
catch
{
distanceResult = ""
durationResult = ""
print("error in JSONSerialization")
}
if(googleResult != ""){
googleResult = "Distance: \(distanceResult!), Duration: \(durationResult!)"
}
else{
print("googleResult is nil")
distanceResult = ""
durationResult = ""
}
return googleResult
Im updating my app to swift 3
I am getting a couple of errors
for (k, v): (AnyObject, AnyObject) in value {
Gets an NSDictionary.Iterator.Element is not convertable to (Anyobject, Anyobject)
Subsiquently Im getting this error
var artworks = [Artwork]()
func loadInitialData() {
// 1
let fileName = Bundle.main.path(forResource: "PublicArt", ofType: "json");
let data: Data = try! Data(contentsOf: URL(fileURLWithPath: fileName!), options: NSData.ReadingOptions(rawValue: 0))
// 2
var error: NSError?
let jsonObject: AnyObject!
do {
jsonObject = try JSONSerialization.jsonObject(with: data,
options: JSONSerialization.ReadingOptions(rawValue: 0))
} catch let error1 as NSError {
error = error1
jsonObject = nil
}
// 3
if let jsonObject = jsonObject as? [String: AnyObject], error == nil,
// 4
let jsonData = JSONValue.fromObject(jsonObject)?["data"]?.array {
for artworkJSON in jsonData {
if let artworkJSON = artworkJSON.array,
// 5
let artwork = Artwork.fromJSON(artworkJSON) {
artworks.append(artwork)
}
}
}
}
JsonObject produces 'Any' not the expected contextual result type
'AnyObject'
and
Argument type [String : AnyObject] does not conform to expected type
'AnyObject'
Im assuming this is an easy one but I havent coded in a year and would be very appreciative of the help
Thanks
Travis
UPDATE
So I just updated the code
but getting an error in the JSON.swift file
static func fromObject(_ object: AnyObject) -> JSONValue? {
switch object {
case let value as NSString:
return JSONValue.jsonString(value as String)
case let value as NSNumber:
return JSONValue.jsonNumber(value)
case _ as NSNull:
return JSONValue.jsonNull
case let value as NSDictionary:
var jsonObject: [String:JSONValue] = [:]
for (k, v): (AnyObject, AnyObject) in value {
if let k = k as? NSString {
if let v = JSONValue.fromObject(v) {
jsonObject[k as String] = v
} else {
return nil
}
}
}
return JSONValue.jsonObject(jsonObject)
case let value as NSArray:
var jsonArray: [JSONValue] = []
for v in value {
if let v = JSONValue.fromObject(v as AnyObject) {
jsonArray.append(v)
} else {
return nil
}
}
return JSONValue.jsonArray(jsonArray)
default:
return nil
}
}
}
error is:
nsdictionary.iterate.element '(aka (key: Any, value: Any)') is not
convertible to 'AnyObject, AnyObject)'
for code line
for (k, v): (AnyObject, AnyObject) in value {
Sorry for the late reply
Regards
Travis
You are using too much AnyObject aka it's-an-object-but-I-don't-know-the-type.
Since the JSON file is in your bundle you know exactly the types of all objects.
In Swift 3 a JSON dictionary is [String:Any] and a JSON array is [[String:Any]].
However I don't know the exact structure of the JSON and I don't know what JSONValue does – a third party library is actually not necessary – but this might be a starting point to get the array for key data.
func loadInitialData() {
let fileURL = Bundle.main.url(forResource: "PublicArt", withExtension: "json")!
do {
let data = try Data(contentsOf: fileURL, options: [])
let jsonObject = try JSONSerialization.jsonObject(with: data) as! [String: Any]
let jsonData = jsonObject["data"] as! [[String:Any]]
for artworkJSON in jsonData {
print(artworkJSON)
// ... create Artwork items
}
} catch {
print(error)
fatalError("This should never happen")
}
}
I have a plist file like
<dict>
<key>horse</key>
<dict>
<key>level</key>
<integer>2</integer>
</dict>
</dict>
and I load it by code bellow
let path = NSBundle.mainBundle().pathForResource("test", ofType: "plist")
dataBase = NSDictionary(contentsOfFile: path!)
let array = NSMutableArray()
for member in (dataBase.allKeys) {
let level = member.valueForKey("level") as! Int
if () {
//do something
}
}
when the app runs, it will crash. The reason I think is that member in the dataBase can't be cast to NSDictionary since it's NSTaggedPointerString, I don't know how to make it work.
update:
The previous question, I have addressed the answer by #Wonzigii, and now I got the right dict using this method I defined validateCard(), which is #Wonzigii said bellow.
func validateCard() -> NSArray?
Now I want to randomly retrieve its item inside the new array to use it to do something, what I'm solving this issue is using a random index, but seem I can't get the array item by index, since if I did so, I can't use it to get the next layer dict. what should I do next?
if let avaliableCardDeck = self.validateCard() {
for _ in 0...16 {
let maxAvaliableCards = avaliableCardDeck.count
let index = Int(arc4random()) % maxAvaliableCards
let cardInfo = avaliableCardDeck[index]
let newCard = PlayCard()
newCard.rank = cardInfo.valueForKey("number") as! Int
newCard.imageName = cardInfo.valueForKey("imageSource") as? String
addCard(newCard)
}
}
The reason is allKeys will return an array contains type AnyObject.
So yet member, in this case, is AnyObject type.
for member in (dataBase!.allKeys) where member as? String == "horse" {
let integer = dataBase?.valueForKeyPath("horse.level") as! Int
print(integer)
}
Note that allKeys always return top level keys.
Edit:
The below code will use member as the key to loop through the dictionary to retrieve deeper value.
for member in (dataBase!.allKeys) {
if let dic = dataBase?.valueForKey(member as! String) {
// get the level value or something else
let integer = dic.valueForKey("level") as! Int
print(integer)
}
}
More Swiftly way(without forced unwrapping, aka, !):
if let path = NSBundle.mainBundle().pathForResource("test", ofType: "plist"), db = NSDictionary(contentsOfFile: path) as? [String: AnyObject] {
for member in db.keys {
if let dic = db[member] {
// get the level value or something else
let integer = dic.valueForKey("level") as! Int
print(integer)
}
}
}
Try this out. If the path and all are correct this code must work, or else it will not execute. Will surely not crash
guard let path = NSBundle.mainBundle().pathForResource("test", ofType: "plist") else {
return
}
if let dataBase = NSDictionary(contentsOfFile: path), member = dataBase["horse"] as? [String: AnyObject] {
if let level = member["level"] as? Int {
//do something
}
}
Edit: Giving a generic one
guard let path = NSBundle.mainBundle().pathForResource("test", ofType: "plist") else {
return
}
if let dataBase = NSDictionary(contentsOfFile: path) {
for (_, value) in dataBase {
if value is [String: AnyObject], let level = value["level"] as? Int {
//do something
}
}
}