I've got an app where I use a JSON based API. As part of JSON, often values are set to "null". This may be common:
{"data":["one","two","three"],"name":null,otherstuff:10}
Recently I've tried to store a misc NSDictionary hierarchy, converted from a JSON object, in NSUserDefaults. Unfortunately it causes an exception if there is null data, converted in IOS to [NSNull null]. Apparently that can't be saved in prefs.
I was wondering if anyone has worked around this before? I tried to add some logic to remove all null values from the JSON first, with limited success, but it seems inappropriate to have to modify the data before storing it. Is there a better way to handle this?
You can first convert your NSDictionary to NSData, then safely store in NSUserDefaults (since NSNull conforms to NSCoding).
//archive
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"key"];
//unarchive
NSData *newData = [[NSUserDefaults standardUserDefaults] objectForKey:#"key"];
NSDictionary *newDict = [NSKeyedUnarchiver unarchiveObjectWithData:newData];
Edit: Original data object was being referenced instead of newData object.
I've tried some recursive solutions but they tend to be complicated and don't handle mixed type content well. At the simplest level here is a flat example that works well if you have a predictable, flat response to clean.
NSMutableDictionary *dictMutable = [dict mutableCopy];
[dictMutable removeObjectsForKeys:[dict allKeysForObject:[NSNull null]]];
Related
I am trying to save an array of objects into an NSUserDefault without success. When I log out the array before the attempt it is full of object. However, when I try to log out the NSUserDefault it is NULL. Can anyone see what I might be doing wrong? Thanks for any suggestions:
Items *myItems = [mutableFetchedObjects mutableCopy];
NSLog(#"my Items%#",myItems);//LOGS OUT LONG LIST OF ITEMS
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:myItems];
[currentDefaults setObject:data forKey:#"myItems"];
[currentDefaults synchronize];
Items *myRetrievedItems = [[[NSUserDefaults standardUserDefaults] arrayForKey:#"myItems"] mutableCopy];
NSLog(#"my Retrieved Items%#",myRetrievedItems); //LOGS OUT AS NULL
As the other answers mentioned, it is because your array is not complying to the NSDictionary types (string, binary, bool, etc). Your members of array is of custom types therefore it cannot be saved. What you need to do is convert your array to binary first and then save it.
You have to unarchive your data first at the time of retrieving back. You are directly accessing the data. This won't work. You can do it the similar way you are archiving the data
NSData *dataObj = [currentDefaults objectForKey:#"myItems"];
Items *myRetrievedItems = [NSKeyedUnarchiver unarchiveObjectWithData:dataObj];
For more reference, you can consider this answer.
Hope this helps.
Thanks!
Your access value method is wrong.
You can get the array in following code:
Items *myRetrievedItems = [[[NSUserDefaults standardUserDefaults] objectForKey:#"myItems"] mutableCopy];
I have a string that downloads from a server. It is in JSON format and is well-formed. It is an array of JSON objects. My objective is to convert this to an NSArray and then store it in NSUserDefaults for subsequent use. My attempt generally works, but sometimes I crash with this error:
Attempt to set a non-property-list object
Here is the code that I am using. I think that it should be this straightforward. I don't believe have to actually convert the objects to NSDictionaries and iterate over this, do I?
NSString *message = [dict objectForKey:#"message"];
NSLog(#"message: %#", message);
data = [message dataUsingEncoding:NSUTF8StringEncoding];
NSArray *arr = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
[[NSUserDefaults standardUserDefaults] setObject:arr forKey:RESOURCES_LIST];
For example, this is one of the strings that causes this to crash (as output by the NSLog command):
[{"folder":"Documents","files":[{"sort_order":"120","filename":"pdf.pdf","filetype":"pdf","display_name":"Instructions","upload_date":"2015-08-11","md5":"ea9f839f91941b5ea7f5a316e3ce95ca","bool_external":"0","url":"http://www.somesite.com/pdf.pdf"}]},{"folder":"Images","files":[{"sort_order":"100","filename":"space.jpg","filetype":"image","display_name":"example","upload_date":"2015-10-14","md5":"bc63b896949cbf87c54678fee8ed833b","bool_external":"0","url":"http://www.somesite.com/space.jpg"},{"sort_order":"110","filename":"profile.png","filetype":"image","display_name":"Profile","upload_date":"2015-10-14","md5":"740d61911560e1c84869563b83f3bbf8","bool_external":"0","url":"http://www.somesite.com/profile.jpeg"}]},{"folder":"Info","files":[{"sort_order":"130","filename":"info.pdf","filetype":"pdf","display_name":"info","upload_date":"2015-11-17","md5":"926a7941cc9c7f58e43c3eb2de661c27","bool_external":"0","url":null}]},{"folder":"Videos","files":[{"sort_order":"130","filename":"sample_video","filetype":"video","display_name":"Instructional
Video","upload_date":"2015-08-11","md5":"-1","bool_external":"0","url":"https://www.youtube.com/embed/PuNIwSsz7PI"}]}]
"url": null
(in [2][#"files"][0])
This is likely being parsed into a value of [NSNull null], which cannot be stored in NSUserDefaults. You will need to either change the server to send as an empty string or not at all, or (recursively) iterate and check all values, removing (or replacing with #"") all keys with value [NSNull null].
For reference, the acceptable classes in plist files and NSUserDefaults are:
Array (NSArray)
Dictionary (NSDictionary)
String (NSString)
Data (NSData)
Date (NSDate)
Numerics [int32/64, float/double] (NSNumber)
Boolean (NSNumber)
And keys must be strings.
I've got an app where I use a JSON based API. As part of JSON, often values are set to "null". This may be common:
{"data":["one","two","three"],"name":null,otherstuff:10}
Recently I've tried to store a misc NSDictionary hierarchy, converted from a JSON object, in NSUserDefaults. Unfortunately it causes an exception if there is null data, converted in IOS to [NSNull null]. Apparently that can't be saved in prefs.
I was wondering if anyone has worked around this before? I tried to add some logic to remove all null values from the JSON first, with limited success, but it seems inappropriate to have to modify the data before storing it. Is there a better way to handle this?
You can first convert your NSDictionary to NSData, then safely store in NSUserDefaults (since NSNull conforms to NSCoding).
//archive
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"key"];
//unarchive
NSData *newData = [[NSUserDefaults standardUserDefaults] objectForKey:#"key"];
NSDictionary *newDict = [NSKeyedUnarchiver unarchiveObjectWithData:newData];
Edit: Original data object was being referenced instead of newData object.
I've tried some recursive solutions but they tend to be complicated and don't handle mixed type content well. At the simplest level here is a flat example that works well if you have a predictable, flat response to clean.
NSMutableDictionary *dictMutable = [dict mutableCopy];
[dictMutable removeObjectsForKeys:[dict allKeysForObject:[NSNull null]]];
I have NSString representation of NSDictionary
I created the string like that:
NSString * insertionStr = [dictionary description];
Now I want to convert it back to NSDictionary
Is it possible ?
To save a dictionary to a file (or other medium) and be able to restore it later, you should use either JSON or the plist format.
For JSON your data must be limited to a combination of dictionaries, arrays, strings, and NSNumbers. For a plist it must be one of those or NSDate, or NSData.
For JSON you'd use the methods of NSJSONSerialization to convert to NSData, then save the data to a file. To restore, load the file into NSData and run back through NSJSONSerialization.
For plist format use the NSDictionary writeToFile and dictionaryWithContentsOfFile methods.
Do note that by default the objects you get back are immutable. If you want mutable objects from JSON there is an option on JSONObjectWithData called NSJSONReadingMutableContainers. With plists I believe you need to use the more complex plist interfaces (vs using the simple NSDictionary interfaces) that allow you to specify a similar option.
From the NSUserDefaults documentation for setObject:forKey
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
If your dictionary contains contains non-property list objects you can archive and store it to file if all objects in the dictionary implement NSCoding.
Based on the Question and comments i will say ...NO ...Don't do it like that.
NSUserDefaults has the capability to store object values and its perfectly fine to store NSDictionary
[[NSUserDefaults standardUserDefaults] setObject: dictionary forKey:#"DetailDict"];
[[NSUserDefaults standardUserDefaults] synchronize];
and retrive it use
NSDictionary *retrievedDictionary = [[NSUserDefaults standardUserDefaults] dictionaryForKey:#"DetailDict"];
OR
If you think you think of saving it as a string have a look at this question and when retrieving the value just convert back the string to Object
A dictionary is represented as key/value pairs. You can create a dictionary with a single value like this:
NSDictionary *dictionary = #{#"name": #"Mike"};
You can also store multiple key/value pairs:
NSDictionary *newDictionary = #{
#"firstName": #"Mike",
#"lastName": #"Smith"
};
Later if you want to get the value back out of the dictionary you use the key to get the value back out of it:
NSString *first = newDictionary[#"firstName"];
NSString *last = newDictionary[#"lastName"];
// first will now be #"Mike" and last will be #"Smith"
Think of a dictionary like an array, but instead of using indexes (numbers) to reference the value you use keys (strings).
Based on a comment made, if you also want to store an array of dictionaries you can do that no problem like this:
NSDictionary *firstDictionary = #{#"key": #"value"};
NSDictionary *secondDictionary = #{#"anotherKey": #"anotherValue"};
NSArray *array = #[firstDictionary, secondDictionary];
I am trying to save a NSDictionary with array values to NSUserDefaults but am having some strange trouble.
My NSDictionary has NSStrings for keys and each value is a NSArray of NSNumbers. When I print the dictionary out, everything is fine. I write this dictionary to NSUserDefaults and if I read it back out right away, everything seams fine. Using this everything seams just fine:
[[NSUserDefaults standardUserDefaults] setObject:self.selectedOptionPositions
forKey:PREF_OPTIONS_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
//THIS PRINT EVERYTHING OUT EXACTLY AS IT SHOULD!
NSLog(#"read after write: %#", [[NSUserDefaults standardUserDefaults]
objectForKey:PREF_OPTIONS_KEY]);
The problem comes when I create a new instance of the class that handles this. When I make a new instance of the class and in the init method check the NSDictionary like so:
NSLog(#"read initial: %#", [[NSUserDefaults standardUserDefaults]
objectForKey:PREF_OPTIONS_KEY]);
When I print that logging, the NSDictionary contains all of the keys but all of the values are now empty! All newly added keys exist after recreating the class, but no values persist.
What could be wrong here? There are no warnings or errors in the console.
Try this:
You can use NSKeyedArchiver to write out your dictionary to an NSData, which you can store among the preferences.
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.selectedOptionPositions];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:PREF_OPTIONS_KEY];
For retrieving data:
NSData *dictionaryData = [defaults objectForKey:PREF_OPTIONS_KEY];
NSDictionary *dictionary = [NSKeyedUnarchiver unarchiveObjectWithData:dictionaryData];
As in the iOS Developer Documentation for NSKeyedArchiver it says that:
NSKeyedArchiver, a concrete subclass of NSCoder, provides a way to
encode objects (and scalar values) into an architecture-independent
format that can be stored in a file. When you archive a set of
objects, the class information and instance variables for each object
are written to the archive. NSKeyedArchiver’s companion class,
NSKeyedUnarchiver, decodes the data in an archive and creates a set of
objects equivalent to the original set.