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.
Related
Dict is coming from notification, taking out the NSData from dict and adding it to NSMutableArray is crashing the application.
Once in a while this crash is happening not always.
NSData *data=[dict objectForKey:#"obj"];
[self.RFTagData addObject:data];
You can directly add data object by doing this.Instead of converting to string.
Don't type cast NSData to NSString when adding objects into array.You should first convert NSData into NSString then add it to array.So
better way to use this NSData into NSString and add NSString into array.
NSData *data=[dict objectForKey:#"obj"];
NSString *strData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if(data != nil self.RFTagData != nil)
{
[self.RFTagData addObject:strData];
.....
}
Example for Converting Data into String
You can directly get the data to array there is no need to cast.
if(self.RFTagData != nil){
self.RFTagData = [dict objectForKey:#"obj"];
}
NSLog(#"array %#", RFTagData);
This will add all data to array under the obj key.
Update:
As user rmaddy & danh suggested, so here needs to take concern over this point regarding use of valueForKey and objectForKey methods and nil check on the array.
objectForKey: This is an NSDictionary method. An NSDictionary is a collection class similar to an NSArray (collections), except instead of using indexes like NSArray, it uses keys to differentiate between items. A key is an arbitrary string you provide. No two objects can have the same key (just as no two objects in an NSArray can have the same index).
valueForKey: This is a KVC method. It works with ANY class. valueForKey: allows you to access a property using a string for its name.
Here both returns the value associated with a given key, so here using valueForKey method provides workaround solution to you. But using objectForKey is the more preferred way to use in such cases.
To check for the null values inside array which are identically appears like literals #"<null>" rather then NSNull objects typically used to represent nils in Cocoa collections. You can filter them out by using NSArray's filteredArrayUsingPredicate method:
NSPredicate *pred = [NSPredicate predicateWithBlock:^BOOL(id value, NSDictionary *unused) {
return ![str isEqualToString:#"<null>"];
}];
NSArray *filteredAry = [self.RFTagData filteredArrayUsingPredicate:pred];
NSLog(#"array with non null vals %#", filteredAry);
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 am putting two NSMutableArray objects into an NSDictionary and trying to serialize, but the method call is returning nil. One array, addresses, is an array of NSString objects. The other, engines is an array of objects that each contain several data types. I am attempting to serialize using the following code:
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:engAddr forKey:#"engAddr"];
[dictionary setObject:trainList forKey:#"engines"];
NSData *data = [NSPropertyListSerialization dataFromPropertyList:dictionary
format:NSPropertyListXMLFormat_v1_0
errorDescription:&error];
Stepping through, the debugger shows the arrays are properly added to the dictionary, but after the line that should serialize the dictionary it shows data = (NSData *) nil.
Where am I going wrong? Thank you for your help!
What kind of objects does engines contain?
Plist supports only specific objects below.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html#//apple_ref/doc/uid/10000048i-CH3-54303
If you want to serialize a custom object, convert it to NSData by NSKeyedArchiver.
To do that, objects must conform NSCoding protocol.
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];