Frustration on the Top !!!
I am getting some JSON Response from the Service and I want to store it in the .plist file for Future Reference.
I am unable to save my JSON Response to .plist File. I think it's due to some null values into the Response.
Note : I confirmed that the Response is in JSON Format using jsonparser.
My Code :
NSError *error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSDictionary *dictResult = [(NSDictionary*)json objectForKey:#"myKey"];
NSLog(#"Result Dictionary :: %#",dictResult);
NSURL *cacheDir = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *path = [cacheDir URLByAppendingPathComponent:#"FinalResult.plist"];
NSLog(#"Path :: %#",path);
BOOL success = [dictResult writeToURL:path atomically:YES];
NSLog(#"success? %d", success);
Note : I got all the NSLog Values (means the Response Dictionary and File Path but 0 for success).
Problem : There are almost 70-80 key-value pairs in the Response and I don't want to remove/replace all the null values. Because What I want is ...
GET the Response From the Server.
Fill all the UITextFields with the Response.
POST the Same Response to the Server with some Edited Values from the UITextFields.
So, I just want to change the Edited UITextField values in the Object and let it POST to the Server.
What is the Optimum Way to Fix this ?
I bet that your JSON contains at least one null value.
When you have JSON that contains null and you convert it using NSJSONSerialization, the null is replaced by an instance of NSNull. If your dictionary contains NSNull, then writeToURL:atomically: will fail.
This is because the convenience methods for reading and writing dictionaries (and arrays) only work if the data in the collection is restricted to property list types. These are:
NSString
NSNumber
NSData
NSDate
NSArray
NSDictionary. And for dictionaries, the keys must be NSStrings.
You can also use mutable subclasses (like NSMutableString) when writing.
If you have anything not on that list, you can't use writeToURL:atomically or any of the similar convenience methods.
The problem is that some valid JSON can't be converted to property lists. Also, some valid property lists can't be converted to JSON (because NSDate won't automatically convert to valid JSON).
If it was me, I'd just write the data to a JSON file. Leave it in its original format. You can convert to/from JSON easily, so leave it that way.
If your dictionary contains NSNull, then writeToURL:atomically: will fail.
For dictionaries, the keys must be NSStrings.
The problem is that some valid JSON can't be converted to property lists. Also, some valid property lists can't be converted to JSON.
Don't Forget, If you must use a property list, you will need to scan the entire dictionary and convert the nulls into something that can be saved in a property list file.
Only Solution is that you have to check all the NULL Values and Replace it with #" ".
Happy Coding...
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryPath = [paths objectAtIndex:0];
NSString *filename = #"FinalResult.plist";
NSString *pathFilename = [libraryPath stringByAppendingPathComponent:filename];
NSDictionary *dictResult = [json objectForKey:#"myKey"];
[dictResult writeToFile:pathFilename atomically:YES];
I build my file urls this way:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSString *filename = #"FinalResult.plist";
NSString *pathFilename = [path stringByAppendingPathComponent:filename];
Then see if this writes:
BOOL success = [dictResult writeToFile:pathFilename atomically:YES];
NSLog(#"success? %d", success);
Edit - Funny, I just recently confronted this problem and then forgot all about it. Some JSON parsers will use [NSNull null] as placeholders for nil values. I wrote this (seriously, just about two weeks ago and then spaced on it) to clean up the parse result...
- (NSDictionary *)compact:(NSDictionary *)aDictionary {
NSDictionary *answer = [NSMutableDictionary dictionary];
for (NSString *key in [aDictionary allKeys]) {
id value = [self.dictionary valueForKey:key];
if (value && value != [NSNull null]) {
[answer setValue:value forKey:key];
}
}
return [NSDictionary dictionaryWithDictionary:answer];
}
This could be made into a NSDictionary category addition if you wanted.
Check if the return value of the writeToURL:atomically: method returns YES.
If it doesn't, check if you have write permissions to that URL.
Try it with another URL to find out if its the path or the contents of the dictionary that's causing this error.
Try this, it will work :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"country.plist"];
[jsonData writeToFile:path atomically:YES];
Related
I'm currently trying to save a NSMutabledictionary keys and objects in a plist file. My keys are integers, so I'm using NSNumber to put them into the writeToFile function.
Even with that change, I cant find any of my saved data in the plist find. I suppose there is a problem with the NSNumber pointer because when I use a string it works.
do you have an idea of what is missing in my code?
NSMutableDictionary *dictionnaireNoms = [[NSMutableDictionary alloc] initWithCapacity:40];
NSNumber *nombre = [[NSNumber alloc] initWithInteger:dictionnaireNoms.count];
NSString *nomCommerce = text.text;
[dictionnaireNoms setObject:nomCommerce forKey:nombre];
//2. Sauvegarde du nom dans un fichier
[saveDicoCommerce enregisterNom:dictionnaireNoms];
- (void)enregisterNom:(NSMutableDictionary*)nom
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSLog(#"%#", documentsDirectory);
NSString *pathNom = [documentsDirectory stringByAppendingPathComponent:#"NomsDeCommerces.plist"];
if (!documentsDirectory) {
NSLog(#"Documents directory not found!");
return;
}
[nom writeToFile:pathNom atomically:YES];
if(![[NSFileManager defaultManager] fileExistsAtPath:pathNom])
{
NSLog(#"file not found");
return;
}
}
NSDictionary can only write itself directly if it only contains string keys. It confirms this you try to write using
[[NSPropertyListSerialization dataWithPropertyList:nom format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil] writeToFile:pathNom atomically:NO];`
The output is:
Property list invalid for format: 200 (property list dictionaries may only have keys which are CFStrings, not 'CFNumber')
However, you can store NSDictionary objects containing NSNumber keys if you serialize it using NSCoding. Replace this:
[nom writeToFile:pathNom atomically:YES];
with:
[NSKeyedArchiver archiveRootObject:nom toFile:pathNom];
To read the file created, use:
NSDictionary *nom2 = [NSKeyedUnarchiver unarchiveObjectWithFile:pathNom];
For more information about archives, see the Archives and Serializations Programming Guide.
Why do u want to allocate NSNumber? Try this [your_dictionary setValue:[NSNumber numberWithInt:your_int]];
I am currently creating a controller class for my plist.in this plist I have a root dictionary that has several types in it (Number, String and Dictionary), In my controller class I check for a plist then add it to the documents so I can read and write to it.
From here I read whats in my current plist and pass those values over to tempvars I have set up in this class.
This is what my read method looks like in my plist controller class.
-(void) readPlistData
{
// Data.plist code
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"EngineProperties.plist"];
// check to see if Data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle
plistPath = [[NSBundle mainBundle] pathForResource:#"EngineProperties" ofType:#"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property liost into dictionary object
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!temp)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
// assign values
self.protocolSignature = [temp objectForKey:#"Protocol"];
self.requestNumber = [temp objectForKey:#"RequestNumber"];
//How do I add the dictionary values here?
}
The reason I put the data into variables is because latter I am going to use these values to test against checks I want to perform against my db.. making sure of things like i am receiving the correct request number etc.
UPDATE:: my idea to add them to the dictionary inside the root dictionary would be something like this. which i think is not even close but it might give you a better clue to what I am trying to do.
self.cacheValue = [temp objectForKey:#"Cache Value"];
self.manufacturers = [cacheValue objectForKey:#"Manufacturers"];
self.models = [cacheValue objectForKey:#"Model"];
self.subModels = [cacheValue objectForKey:#"SubModels"];
any help would be greatly appreciated.
I believe you want to do the following:
Define your cacheValue property in the .h as a mutable dictionary.
NSMutableDictionary *cacheValue;
Serialize the plistXml as a NSMutableDictionary:
// This is the root Dictionary
NSMutableDictionary *temp = (NSMutableDictionary *)[NSPropertyListSerialization propertyListWithData:plistXML options:NSPropertyListMutableContainersAndLeaves format:NSPropertyListXMLFormat_v1_0 error:&error];
Since everything is mutable, you can now read, update, insert, delete any part of the dictionary or its subcontents. For instance, grabbing the Mutable Dictionary "Cache Value" is just:
self.cacheValue = [temp objectForKey:#"Cache Value"];
Remember to check that the object is not nil in case there isn't a value for the key. The key needs to be exactly as it appears in the plist.
Updating a value in the Mutable Dictionary is easy:
[self.cache setValue:#"New Value" forKey:#"Sub"];
And finally, to save the changes in the root Mutable Dictionary back to the plist:
/*
The flag "atomically" specifies whether the file should be written atomically or not.
If flag is YES, the receiver is written to an auxiliary file, and then the auxiliary file is renamed to path.
If flag is NO, the dictionary is written directly to path.
The YES option guarantees that path will not be corrupted even if the system crashes during writing.
*/
[self.temp writeToFile:plistPath atomically:YES];
Hope this helps, cheers!
I have been following this Tutorial on how to read from a plist. However i cannot read the string with key name. i tried putting NSLog("%#", [temp objectForKey:#"Name"]); but it returns null. i had a similar issue before which involved opening the xml file and manually changing dict to array. How can i fix this?
the hierarchy in the xml file is what confuses me. the dictionary i created named root with string John Doe is part of a dictionary itself? Clarification appreciated!
From the tutorial linked above:
// check to see if Data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle
plistPath = [[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property liost into dictionary object
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!temp)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
NSLog(#"%#", [temp objectForKey:#"Name"];
Most likely this is because there is no object with key #"Name" (remember that Xcode is case sensitive!) in the (presumably) NSDictionary named temp.
For a more specific answer, you'll need to post the code where you define temp.
I had a look around, trying to find a straightforward method for first saving a MutableArray (which will contain different text arrays from UITextViews with returns etc.) into a txt-file and then loading the txt-file back into my MutableArray.
I didn't manage to come up with the reverse method (loading the text-file) and was wondering how I should go about this. I'm sure txt files and mutable arrays are not really compatible, especially if I want the MutableArray to hold various text strings from UITextViews.
Is there a way to mark the beginning of one section in a mutable array and the beginning of the next in a txt file? The aim would be to be able to edit the txt file both in the program and in a simple text editor without messing up the structure of the mutable array.
Can I use a certain special character (not \n obviously) in my text file so as to separate different objects?
Here is what I've come up with so far. Sorry, I'm a beginner and it's very basic. The first problem is that I get the error message 'NSMutableArray' may not respond to '-writeToFile:atomically:encoding:error:'. Next, I have no idea how to load the txt back into my Array. Finally, I'd like to come up with a way to separate the arrays in the txt so that it remains editable, but that would be the absolute icing. Perhaps a solution would be to save each Object in an Array in a separate txt file and then load each txt into the array?
// GENERATE ARRAY
NoteBook = [[NSMutableArray alloc] init];
for (int temp = 0; temp < 3; temp++) {
[NoteBook insertObject:#"Title\n\n Line1\nLine2..." atIndex:temp];
}
// SAVING MY MUTABLE ARRAY
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
NSError *error;
BOOL succeed = [NoteBook writeToFile:[documentsDirectory stringByAppendingPathComponent:#"myfile.txt"]
atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (!succeed){
// Handle error here
}
// LOADING TEXTFILE AND PUT IT INTO A MUTABLE ARRAY
// NO IDEA... how to do this
Convert your arrays into strings, and vice versa, using, e.g.,
NSString* arrayText = [NoteBook componentsJoinedByString: #"<your-favourite-separator-string>"];
the write to file using [arrayText writeToFile...]
After reading a string back from a file, use
Notebook = [arrayText componentsSeparatedByString: #"<your-favourite-separator-string>"];
Lastly, don't do this. Save your array directly to a property list (read up on those) or JSON or some other structured data format.
Why not just turn the mutable array into JSON and write that string to a file? The inverse is to read the string from file and turn back into an array using the JSON parser. json-framework is very easy to use.
A benefit would be that you could create or modify your array by editing text files as long as you write valid JSON.
make NSMutableArray to NSArray .because NSMutableArray does not have writeToFile .
retriev array from file
NSArray *theCatalogInfo=nil;
NSString *theCatalogFilePath = [NSString stringWithFormat:#"%#/Documents/",NSHomeDirectory()];
theCatalogFilePath = [theCatalogFilePath stringByAppendingString:kCatalogCachePath];
if(nil!=theCatalogFilePath)
{
theCatalogInfo=[[NSArray alloc]initWithContentsOfFile:theCatalogFilePath];
}
Save array To file
NSString *theCatalogFilePath = [NSString stringWithFormat:#"%#/Documents/",NSHomeDirectory()];
theCatalogFilePath = [theCatalogFilePath stringByAppendingString:kCatalogCachePath];
[**YourArray** writeToFile:theCatalogFilePath atomically:YES];
Have a look at following three methods to create a text file, write to it and read the data from it.
The key is to store the different objects separated by space. And you should get it very simple.
-(void)createFile
{
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"Sample.txt"];
NSFileManager * file_manager = [NSFileManager defaultManager];
if(![file_manager fileExistsAtPath:filePath])
{
[file_manager createFileAtPath:filePath contents:nil attributes:nil];
NSString *content = #"NULL NULL NULL";
[content writeToFile:filePath
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
}
}
-(void)writeToFile
{
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"Sample.txt"];
NSString *content = [NSString stringWithFormat:#"%# %# %#", obj1, obj2, obj3];
[content writeToFile:filePath
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
}
-(void)readFromFile
{
objects = [[NSArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"Sample.txt"];
if (filePath) {
NSString *myText = [NSString stringWithContentsOfFile:filePath encoding:NSASCIIStringEncoding error:nil];
if (myText) {
objects = [myText componentsSeparatedByString:#" "];
}
}
}
if your nsarray contains nsdictionary, nsarray, nsstring, nsnumber, nsdata or nsdate objects (no custom objects, int's, etc) you can simply write the contents of your mutable array to a plist file.
this will maintain the data structure you have and you can simply read that data right into an array. How I do it in a couple of my data classes is
NSArray *tempArray = [NSArray arrayWithContentsOfFile:[Utils getFileLocation]];
if (tempArray == nil) {
yourArray = [[NSMutableArray alloc] init];
} else {
yourArray = [[NSArray deepMutableCopy:tempArray] retain];
}
I have one dictionary I need to save into a plist. The paletteDictionary always returns nil:
- (void)saveUserPalette:(id) sender
{
[paletteDictionary setObject:matchedPaletteColor1Array forKey:#"1"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"UserPaletteData.plist"];
// write plist to disk
[paletteDictionary writeToFile:path atomically:YES];
}
I'm reading the data back in a different view like:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"UserPaletteData.plist"];
NSMutableDictionary *plistDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:path];
if(plistDictionary==nil ){
NSLog(#"failed to retrieve dictionary from disk");
}
Can you mention what object is "matchedPaletteColor1Array". I believe, its some custom class object. With my knowledge the writable objects must conform to NSCoding, which NSString,NSDate etc are already, So in given code, setting "matchedPaletteColor1Array" as some string will work.
You can look at following post to make your object NSCoding enabled. objects conforming to nscoding will not writetofile
Run your application in the simulator and browse to the following path:
~/Library/Application Support/iPhone Simulator/User/Applications/{A GUID}/Documents
Where A GUID will be a string like this: 06430A38-AFAC-4C68-8F39-DBD6C81A5AA6 (it's probably the Last Modified folder).
Verify that UserPaletteData.plist is present and load it up in Property List Editor.app to verify that it contains some data.
Also make sure that the you use only the following data types in your dictionary otherwise it will fail to write to a plist: (NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary).
To verify that the dictionary you are attempting to save to disk is valid for a plist, try the following:
for (id key in paletteDictionary)
{
NSLog(#"key: %#, value: %#, class: %#", key, [paletteDictionary objectForKey:key], NSStringFromClass([[paletteDictionary objectForKey:key] class]));
}
That should tell you if any of your objects are the wrong data type for a plist.
If that is still not helping, then you should make sure that paletteDictionary has been alloc/init'ed before saveUserPalette is called.
Check that you are allocating (alloc/init) paletteDictionary before you use it.