I've struggled with this all day using answers from here, but I cannot find a solution that is working for me, so I thought I'd ask for help:
I have an array of objects that I've extracted from an entity.
All I want to do is to write the objects to a file.
What I've come up with so far is this:
NSLog(#" Exported Records: %i", [exportArray count]);
// the count here is 4 records.
//Each record has about 8 elements.
//I'm just trying to get this working with the first two elements right now
NSString *writeString = nil;
NSError *error = nil;
int i = 0;
NSString *key = nil;
NSString *tempString = nil;
for (i=0; i<[exportArray count]; i++)
{
tempString = [tempString stringByAppendingString: #" \n "];
for (int j=0; j<3; j++)
{
if (j == 0)
{
key = [[exportArray objectAtIndex:i] valueForKey:#"title"];
//[NSString stringWithFormat:tempString2, [[exportArray objectAtIndex:i] valueForKey:#"title"]];
}
if (j == 1)
{
key = [[exportArray objectAtIndex:i] valueForKey:#"username"];
//[NSString stringWithFormat:tempString2, [[exportArray objectAtIndex:i] valueForKey:#"username"]];
}
writeString = [NSString stringWithFormat:tempString, key];
}
}
// Write to the file
[writeString writeToFile:dataFile atomically:YES
encoding:NSUTF8StringEncoding error:&error];
if (error)
{
NSLog(#"%#", error);
}
Right now, all I'm getting the the last item, so I'm overwriting the string. But, there must be a better method to achieve this. Please let me know if there is another answer here matches my question, or please, post some ideas.
UPDATE
When I log the exportArray, I get this:
"<ItemEntity: 0x1ddf6560> (entity: ItemEntity; id: 0x1ddf46d0 <x-coredata://78FBC4A5-AE1A-4344-98AB-978126457D96/ItemEntity/p2> ; data: <fault>)",
"<ItemEntity: 0x1ddf6800> (entity: ItemEntity; id: 0x1ddf46e0 <x-coredata://78FBC4A5-AE1A-4344-98AB-978126457D96/ItemEntity/p2051> ; data: <fault>)",
"<ItemEntity: 0x1ddf6860> (entity: ItemEntity; id: 0x1ddf45d0 <x-coredata://78FBC4A5-AE1A-4344-98AB-978126457D96/ItemEntity/p3075> ; data: <fault>)",
"<ItemEntity: 0x1ddf68c0> (entity: ItemEntity; id: 0x1ddf45e0 <x-coredata://78FBC4A5-AE1A-4344-98AB-978126457D96/ItemEntity/p5124> ; data: <fault>)"
)
When I log the actual values:
NSLog(#" Exported titles: %#", [exportArray valueForKey:#"title"]);
NSLog(#" Exported usernames: %#", [exportArray valueForKey:#"username"]);
I get correct results. I just don't know how to tie these and the other attributes together..
Exported titles: (
1,
2,
4,
6
)
Exported usernames: (
ellis,
david,
bea,
ian
)
If you just have NSDictionaries in your array, you can use writeToFile:atomically: which will save a file in .plist format (which is much easier to read than a CSV).
Check out the Apple Docs for NSArray, and a good write up on plists.
So you would end up having:
[exportArray writeToFile:fileName atomically:YES];
Edit: You cannot save a core data object to a file. So what you want to do is create an interim array with just the data you want, then write that to the file.
NSMutableArray* arrayToSave = [NSMutableArray arrayWithCapacity:exportArray.count];
[exportArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSDictionary* exportDict = [NSDictionary dictionaryWithObjectsAndKeys:
[obj objectForKey:#"username"], #"username",
[obj objectForKey:#"title"], #"title", nil];
[arrayToSave addObject:exportDict];
}];
[arrayToSave writeToFile:fileName atomically:YES];
Use this,
[exportArray writeToFile:dataFile atomically:YES];
NSArray has the method writeToFile: to do this. Check the documentation here. This works fine if there are no custom objects inside the array.
In case you want to save only title or username(Even if it has custom objects in array), you can do it as:
NSArray *array1 = [exportArray valueForKey:#"title"];
[array1 writeToFile:dataFile atomically:YES];
or
NSArray *array2 = [exportArray valueForKey:#"username"];
[array2 writeToFile:dataFile atomically:YES];
[exportArray writeToFile: dataFile atomically:YES];
Creates and writes a .plist file format.
Unless you have objects in the array that are not supported by a plist.
The supported object are: the property list objects `NSString, NSNumber, NSDate, NSData, NSArray and NSDictionary.
UItextField and UItextView are not supported by the plist format so this will not work. If what you want to save is just the text from them put the text in the array.
Try this:
NSMutableString *string = [NSMutableString string];
for (NSDictionary* dict in exportArray) {
for (NSString* key in [dict allKeys]) {
NSObject* value = [dict objectForKey:key];
[string appendFormat:#"%# = %#\n", key, value];
}
}
// Write to the file
NSError* error = nil;
[string writeToFile:dataFile atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error)
NSLog(#"%#", error);
I would consider converting it to NSData first and then saving it as a binary plist rather than a human-readable plist as binary plists are much faster to read (2-3x I believe but I can't remember which WWDC video it was explained in). To do this, you first need to convert the NSArray to an NSData object:
(Source: Rob Napier's answer to this SO question)
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
// "Note that all the objects in array must conform to the NSCoding protocol."
NSString *error;
NSData *data = [NSPropertyListSerialization dataWithPropertyList:array
format:NSPropertyListBinaryFormat_v1_0 options:NULL error:&error];
Then you can write the NSData to file using:
[data writeToFile:path atomically:YorN];
To read it back, you then use:
NSData *rawdata = [[NSData alloc] initWithContentsOfFile:/*file*/];
NSData *arrayData = [NSPropertyListSerialization propertyListWithData:rawdata
options:NULL format:NULL error:&error];
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:data];
You only really need to use the binary process for extremely large .plist files, but it is handy to know about.
Related
In my application I need to build an url like :
https://www.thefootballapi/football/league1/player/stats
In order to be able to build the url, I need to access the objects in an NSDictionary, since NSDictionary is an unordered data set, I need to sort the objects alphabetically in order to build the correct url:
NSDictionary
{
category = "football";
league = " League1 " ;
section = player;
"sub_category" = "stats";
}
I have tried doing this by writing this block of code:
Accessing the objects:
NSArray *keyyy0= [self.redirect allKeys];
id aaKey0 = [keyyy0 objectAtIndex:0];
id aanObject0 = [self.redirect objectForKey:aaKey0];
NSArray *keys = [self.redirect allKeys];
id aKey = [keys objectAtIndex:1];
id anObject = [self.redirect objectForKey:aKey];
NSArray *keyyy = [self.redirect allKeys];
id aaKey = [keyyy objectAtIndex:2];
id aanObject = [self.redirect objectForKey:aaKey];
and building the full url like this :
NSString *fullurl = [NSString stringWithFormat:#"%#%#%#%#", newurl,anObject,aanObject,aanObject3 ];
This method works fine for now, however I was wondering if this is the correct way of doing this ? is there a better way of implementing this ?
For example as it's mentioned here : Joe's answer ,NSURLQueryItem is used to access objects from dictionaries and build queries from it, however when I used NSURLQueryItem the full url was built with ? and = signs.
Are there any other methods that can be used to just get all of the objects in an NSDictionary ?
When accessing values from an NSDictionary there's no guarantee what type it will be. With full type-checking, a safer and more readable way of creating your URL might be something like:
NSDictionary *redirect = #{#"category" : #"football",
#"league" : #" League1 ",
#"section" : #"player",
#"sub_category" : #"stats"};
id category = redirect[#"category"];
id league = redirect[#"league"];
id section = redirect[#"section"];
id subCategory = redirect[#"sub_category"];
if ([category isKindOfClass:[NSString class]] &&
[league isKindOfClass:[NSString class]] &&
[section isKindOfClass:[NSString class]] &&
[subCategory isKindOfClass:[NSString class]])
{
NSString *urlString = [NSString stringWithFormat:#"https://www.thefootballapi/%#/%#/%#/%#",
[((NSString*)category).lowercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]],
[((NSString*)league).lowercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]],
[((NSString*)section).lowercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]],
[((NSString*)subCategory).lowercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
NSLog(#"%#", urlString); // https://www.thefootballapi/football/league1/player/stats
}
This also ensures the URL is generated as you wanted (lowercase "league1" without leading/trailing whitespace) given your input JSON.
Try this code.
//Your Dictionary
NSMutableDictionary *dict = [NSMutableDictionary new];
[dict setValue:#"football" forKey:#"category"];
[dict setValue:#"League1" forKey:#"league"];
[dict setValue:#"player" forKey:#"section"];
[dict setValue:#"stats" forKey:#"sub_category"];
// Get desired URL like this
NSArray *arr = [[dict allValues] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSString *strURL = [NSString stringWithFormat:#"https://www.thefootballapi/%#/%#/%#/%#", [arr objectAtIndex:0], [arr objectAtIndex:1], [arr objectAtIndex:2], [arr objectAtIndex:3]];
NSLog(#"%#", strURL);
It will return ULR same as you want : https://www.thefootballapi/football/League1/player/stats
I want to save a Json Object in a field (text) sqlite and then read it again with a select and retransform to NSDictionary or NSMutableArray to parse the key/values
This is how i save actually in the sqlite DB
As you see, is a song object from the iTunes api. I want to read that object and parse it.
This is how i make the select query and save it in a NSDictionary while i filling and NSMutableArary
globals.arrayMyPlaylists = [[NSMutableArray alloc] init];
// Form the query.
NSString *query = #"select * from myplaylists";
// Get the results.
NSArray *listas = [[NSArray alloc] initWithArray:[self.dbManager loadDataFromDB:query]];
for (int i = 0; i < listas.count; i++) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
NSLog(#"lista %d %# %#", i, listas[i][0], listas[i][1]);
[dictionary setObject:listas[i][0] forKey:#"id"];
[dictionary setObject:listas[i][1] forKey:#"nombre"];
NSString *query2 = [NSString stringWithFormat:#"select cancion from canciones where idplaylist = %#", listas[i][0]];
NSArray *canciones = [[NSArray alloc] initWithArray:[self.dbManager loadDataFromDB:query2]];
[dictionary setObject:canciones forKey:#"canciones"];
[globals.arrayMyPlaylists addObject:dictionary];
dictionary = nil;
}
When i try to read it in the cellForRowAtIndexPath method
NSArray *canciones = [[NSArray alloc] initWithArray:[[globals.arrayMyPlaylists objectAtIndex:indexPath.row] valueForKey:#"canciones"]];
and try to get the value for the key artworkUrl100
[cell.tapa1 sd_setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#", [[canciones objectAtIndex:i] valueForKey:#"artworkUrl100"]]] placeholderImage:nil];
i get the error
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x7ff575810600> valueForUndefinedKey:]: this class is not key value coding-compliant for the key artworkUrl100.'
i understand i'm messing up in some way with dictionarys/nsmutablearrays. I need some help. Thanks!
EDIT: this is how i save the data in the DB
NSString *query = [NSString stringWithFormat:#"insert into canciones (idplaylist, cancion) values ('%#', '%#')", [[globals.arrayMyPlaylists objectAtIndex:indexPath.row] valueForKey:#"id"], self.elementoSeleccionado];
[self.dbManager executeQuery:query];
self.elementoSeleccionado is the NSMutableArray with the "cancion" object and it's saved like it's shows the first image.
EDIT 2: this is what i get trying schmidt9's answer
EDIT 3: OK, i have the json string escaped. How i have to parse now?
You should parse your output first with NSJSONSerialization:
NSString *query2 = [NSString stringWithFormat:#"select cancion from canciones where idplaylist = %#", listas[i][0]];
NSArray *canciones = [[NSArray alloc] initWithArray:[self.dbManager loadDataFromDB:query2]];
// I suppose your array canciones should contain only one song object ...
NSError *error = nil;
id object = [NSJSONSerialization JSONObjectWithData:canciones[0] options:0 error:&error];
if(error) {
// handle error ...
}
if([object isKindOfClass:[NSDictionary class]])
{
NSDictionary *result = object;
[dictionary setObject:result forKey:#"canciones"];
}
Edit
Saving to database
2 options:
- if you get a prepared json string, save it directly to DB, but before you should escape quotes. See eg. here
- if you have NSDictionary:
- (NSString*)JSONStringWithDictionary:(NSDictionary*)dictionary prettyPrinted:(BOOL)prettyPrinted
{
NSJSONWritingOptions options = (prettyPrinted) ? NSJSONWritingPrettyPrinted : 0;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary options:options error:nil];
return [[NSString alloc] initWithBytes:[jsonData bytes] length:[jsonData length] encoding:NSUTF8StringEncoding];
}
After that you should escape the string too.
I get this JSON object from my web service
0: {
Username: "John"
Password: "12345"
Position: "Admin"
Job: "Developer"
ResourceJobId: 1
}
When I try to assign a value, for example, to Username JSON string, I get a semantic error:
id obj = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:&error];
for (NSDictionary *dictionary in array) {
NSString *usernameString = #"";
//this line of code gives me an error: Expression is not assignable
[dictionary objectForKey:#"Username"] = usernameString ;
I know how get the JSON object with NSJSONSerialization class, but my target is how assign the value #"" to the JSON object.
So how can I assign the value #"" to the Username JSON string?
While you are trying to fetched dictionary from array. it is not mutable dictionary so you need to created NSMutableDictionary while fetching data from Array. following code will help you to change username.
for (NSMutableDictionary *dictionary in array)
{
NSMutableDictionary* dictTemp = [NSMutableDictionary dictionaryWithDictionary:dictionary];
[dictTemp setObject:usernameString forKey#"Username"];
[_arrayList replaceObjectAtIndex:index withObject:dictTemp];
}
Well your assignment operation is written wrong, it should be indeed like this:
If you need to get value from dictionary
usernameString = [dictionary objectForKey:#"Username"];
If you want to set a value to your dictionary, first of all your dictionary should be NSMutableDictionary
[dictionary setObject:usernameString forKey:#"Username"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error;
id obj = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:&error];
if(!error && [obj isKindOfClass:[NSArray class]]){
NSArray *array = (NSArray *)obj;
Booking *booking = nil;
for (NSMutableDictionary *dictionary in array) {
NSString *usernameString = #"";
//this line of code gives me an error: Expression is not assignable
[dictionary objectForKey:#"Username"] = usernameString;
Yes, is a compiler error
You need to have a mutabledictionary inorder to change the value. Try this
for (NSDictionary *dictionary in array) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionary];
NSString *usernameString = #"";
[mutableDictionary setValue:#"" forKey:usernameString];
}
Even the answers are correct, I want to add that you do not have to do this your own. NSJSONSerialization already has a solution for that. Simply pass as options one of these:
NSJSONReadingMutableContainers = (1UL << 0),
NSJSONReadingMutableLeaves = (1UL << 1),
when reading the JSON.
This is my code.
NSURL *quoteURL = [NSURL URLWithString:#"http://qwoatzz.com/quoates.json"];
NSData *jsonData = [NSData dataWithContentsOfURL:quoteURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
// NSLog(#"dataDictionary : %#", dataDictionary);
NSDictionary *posts = [dataDictionary objectForKey:#"picture1"];
self.quoteArray = [posts objectForKey:#"quote"];
self.personArray = [posts objectForKey:#"person"];
int r = arc4random_uniform(quoteArray.count);
mainText.text = [quoteArray objectAtIndex:r];
subText.text = [personArray objectAtIndex:r];
I am getting the Thread 1: signal SIGABRT error on the line starting with "self.quoteArray". I have no broken connection in the storyboard on any View Controllers. I am also getting "libc++abi.dylib: terminating with uncaught exception of type NSException" in the console. What is the problem?
Code that was working:
SURL *quoteURL = [NSURL URLWithString:#"http://qwoatzz.com/quoates.json"];
NSData *jsonData = [NSData dataWithContentsOfURL:quoteURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
// NSLog(#"dataDictionary : %#", dataDictionary);
NSArray *posts = [dataDictionary objectForKey:#"picture3"];
int i = 0;
NSString *quote = [((NSDictionary *)(posts[i])) objectForKey:#"quote"];
NSString *person = [((NSDictionary *)(posts[i])) objectForKey:#"person"];
i++;
NSString *quote1 = [((NSDictionary *)(posts[i])) objectForKey:#"quote"];
NSString *person1 = [((NSDictionary *)(posts[i])) objectForKey:#"person"];
i++;
NSString *quote2 = [((NSDictionary *)(posts[i])) objectForKey:#"quote"];
NSString *person2 = [((NSDictionary *)(posts[i])) objectForKey:#"person"];
NSArray *quotesArray = [NSArray arrayWithObjects:quote, quote1, quote2, nil];
NSArray *personArray = [NSArray arrayWithObjects:person, person1, person2, nil];
int r = arc4random_uniform(quotesArray.count);
mainText.text = [quotesArray objectAtIndex:r];
subText.text = [personArray objectAtIndex:r];
I'm not using that code anymore as I will have an unspecified amount of objects in the JSON file.
EDIT: It is now directing me to main.m when I get the error which shows this:
int main(int argc, char * argv[]) {
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
and the error is on "return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));"
There is insufficient context in the code snippet, and only partial information in the error message, so not possible to be certain here. You should start by confirming that "picture1" actually is returning a dictionary and that it contains the array "quote" that you expect it does.
Comparing your two code sections, [dataDictionary objectForKey:#"picture1"]; returns an NSArray that contains NSDictionaries in the working version for posts but you are treating it as just returning an NSDictionary in your new code. This appears likely to be the problem.
Posting a greater portion of the error message from the SIGABRT would immediately assist in narrowing down the type of problem.
The issue is clear when you go to the URL provided and see the results below. The response is a dictionary. The object associated with the key picture1 is an array of dictionaries.
{
"picture1":
[
{
"quote":"A little nonsense now and then, is cherished by the wisest men.",
"person":"Roald Dahl"
},
{
"quote":"It's a good idea always to do something relaxing prior to making an important decision in your life.",
"person":"Paulo Coelho"
},
{
"quote":"Work. Don't Think. Relax.",
"person":"Ray Bradbury"
}
]
}
You need to get the object for key (you can use the shortcut yourDict[#"theKey"] instead of objectForKey) for picture1 - that is an array. Then each object in that array is a dictionary, so if you want to get all of the quotes/persons, this is what you'd do:
NSArray *picture1Posts = dataDictionary[#"person1"];
NSMutableArray *quotes = [NSMutableArray array];
NSMutableArray *persons = [NSMutableArray array];
for (NSDictionary *dict in picture1Posts) {
[quotes addObject:dict[#"quote"];
[persons addObject:dict[#"person"];
}
Be aware that inserting nil into an array will crash it, so you'll want to make sure that dict[#"quote"] and dict[#"person"] isn't nil before adding, this was just to show you your problem.
What I am trying to accomplish, I've one macro defined,
#define NAME_List #"a,aaa,aac,aacaaba,abbbb"
I'm converting this to NSArray using componentsSeperatedByString: its fine giving me array of NSString objects, instead I want NSDictionary objects, something like,
[0] -> "Name" = "a"
[1] -> "Name" = "aaa"
[2] -> "Name" = "aac""
& not like,
[0] - > a
[1] - > aaa
[2] - > aac
I tried this (but I don't have any idea for NSKeyedArchiver or NSPropertyListFormat or
NSPropertyListSerialization classes)
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arrayNames];
NSError *error = nil;
NSPropertyListFormat plistFormat;
NSDictionary *temp = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:&plistFormat error:&error];
Update
I can iterate through array and create dictionary object and add it to array to do this, but I don't want to do it like this!
Is there any functions available which can do this?
NSMutableArray *resultArr = [NSMutableArray array];
NSArray *arr = [NAME_LIST componentsSeperatedByString:#","];
for(NSString *str in arr) {
NSDictionary *nameDictionary = [NSDictionary dictionaryWithObject:str forKey:#"Name"];
[resultArr addObject:nameDictionary];
}
NSLog(#"%#", resultArr);