Is there a way to loop through nsdictionary in objective C where key values are not known and the dictionary is deeply nested. Can anyone please help? i searched a lot couldn't find a definitive solution for this.
I will get a JSON string which i am moving into a NSDictinary variable, here some values are encrypted some are not. I need to decrypt only certain parts of the json. I know which key value pair i need to decrypt. (which also is a part of the json string, i.e is available in the outer nest). But for each JSON response the string will change so i don't know where the encrypted key value pair will be. I just know the key name. So i need to loop through the entire json and find those key pairs to decrypt. Also the json is deeply nested.
Hi thanks for responding here is the sample json
This is a sample json (not the exact string)
{
"DTLS": [
{
“VALUE1”: “ASsddcFF12223fdvfvfvffrefcdcssss”
},
{
"VALUE2”: “sdsdd2323edffvfvb4ddcdccvvvrfdc”
},
{
"VALUE3”: "sdsdd2323edffvfvb4ddcdccvvvrfdc"
},
],
"Decryptkeys":"VALUE1|VALUE2|VALUE3"
"isSuccessful": true,
"responseTime": 2014,
"totalTime": 2014,
"responseCode": "S",
"statusCode": 200
}
The above response may change in the another server call where DTLS could be nested inside another json key like MAINDTLS, so the only key that i can blindly take is Decryptkeys. I know where this will be. But the ones inside Decryptkeys i.e VALUE1,VALUE2,VALUE3 maybe nested somewhere deep inside the json.
Check this answer:
how to iterate nested dictionaries in objective-c iphone sdk
https://stackoverflow.com/a/40942013/2432053
EDIT
Here is modified version:
- (id)findflat:(NSString *)keyToFind {
if([self objectForKey:keyToFind]) {
return self[keyToFind];
}
for(id key in self.allKeys) {
if ([self[key] isKindOfClass:[NSArray class]]) {
for (id obj in self[key]) {
if ([obj isKindOfClass:[NSDictionary class]]) {
id res = [obj findflat:keyToFind];
if (res) {
return res;
}
}
}
}
}
return nil;
}
First two if conditions check if value is kind of dictionary or array if so it has to be parsed.
Last if condition if ([self respondsToSelector:NSSelectorFromString(key)])
This is where you get value.
In my case I have a custom NSObject, what I'm doing here is checking if this object respond to particular key then I'm setting the value...
In your case i believe last if condition should check if key is DTLS...then do your stuff
Hope this helps...
-(void)parseDictionary:(NSDictionary*)dict{
for (NSString *key in dict) {
if([[dict valueForKey:key] isKindOfClass:[NSDictionary class]]){
[self parseDictionary:[dict valueForKey:key]];
}else if([[dict valueForKey:key] isKindOfClass:[NSArray class]]){
for (NSDictionary* object in [dict valueForKey:key]) {
[self parseDictionary:object];
}
}else if ([self respondsToSelector:NSSelectorFromString(key)]) {
[self setValue:[dict valueForKey:key] forKey:key];
}
}
}
I am trying to fetch 'taggable_friends' list from Facebook, where there may be more than 1000 taggable friends, so Facebook paginates the results. Here is the method.
-(void)getsFbTaggableFriends:(NSString *)nextCursor dicFBFriends:(NSMutableArray *) dicFriends failure:(void (^) (NSError *error))failureHandler
{
NSString *qry = #"/me/taggable_friends";
NSMutableDictionary *parameters;
if (nextCursor == nil) {
parameters = nil;
}
else {
parameters = [[NSMutableDictionary alloc] init];
[parameters setValue:nextCursor forKey:#"next"];
}
[FBRequestConnection startWithGraphPath:qry
parameters:parameters
HTTPMethod:#"GET"
completionHandler:^(
FBRequestConnection *connection,
id result,
NSError *error
) {
if (error) {
NSLog(#"%#", [error localizedDescription]);
}else {
/* handle the result */
NSMutableDictionary *mDicResult = [[NSMutableDictionary alloc]initWithDictionary:result];
for (NSDictionary * fbItem in [mDicResult valueForKey:#"data"])
{
[dicFriends addObject:fbItem];
}
// if 'next' value is found, then call recursively
if ([[mDicResult valueForKey:#"paging"] objectForKey:#"next"] != nil) {
NSString *nextCursor = mDicResult[#"paging"][#"next"];
NSLog(#"next:%#", [nextCursor substringFromIndex:27]);
[self getsFbTaggableFriends:nextCursor dicFBFriends:dicFriends failure:^(NSError *error) {
failureHandler(error);
}];
}
}
}];
}
Problem:
I get first 1000 records in the 'result' object and the value of the 'next' key is passed as the "parameters" parameter for the recursive call. However, the second iteration doesn't paginate & keeps returning the same 1000 records.
I also tried using the nextCursor value as the startWithGraphPath parameter for the second call instead. It resulted in a different response object with keys like og_object, share, id instead of data & paging.
Please help to properly obtain the taggable friends page by page, as long as 'next' value is present in the response object. Thank you.
Use the returned next endpoint (Graph path portion, including the cursor) as the new Graph path for the subsequent request, instead putting it as the parameter.
I have come across all the answers. Most of the answers are suggesting either URL based pagination or recursively calling function. We can do this from Facebook SDK itself.
var friendsParams = "taggable_friends"
// Save the after cursor in your data model
if let nextPageCursor = user?.friendsNextPages?.after {
friendsParams += ".limit(10)" + ".after(" + nextPageCursor + ")"
} else {
self.user?.friends.removeAll()
}
let requiredParams = friendsParams + "{id, name, first_name, last_name, picture.width(200).height(200)}"
let params = ["fields": requiredParams]
let _ = FBSDKGraphRequest(graphPath: "me", parameters: params).start { connection, response, error in
if connection?.urlResponse.statusCode == 200 {
print("\(response)")
// Update the UI and next page (after) cursor
} else {
print("Not able to fetch \(error)")
}
}
You can also find the example project here
I have my coredata set up and saving and fetchRequests work well. In this case I am fetching the first object saved in 'Item' but if the fetchResult is empty I do not want to call my method 'displayDataInLabel' as if there is nothing in it this method breaks (i'm guessing because the is nothing at 'objectAtIndex:0'
-(void)viewDidAppear:(BOOL)animated
{
if (_fetchResultsController.fetchedObjects == nil) {
return;
}
else
[self displayDataInLabel];
}
-(void)displayDataInLabel
{
Item *thisItem = [_fetchResultsController.fetchedObjects objectAtIndex:0];
NSLog(#"test item %#", thisItem.name);
}
It is not sufficient to check for nil because the result, even if nothing is found, will still be a valid (empty) array, which is not nil.
Instead, check if the count property of the result is zero.
I'm reading through some source code and I noticed the following API, where the developer passes in nil if they want an object to be removed.
- (void)setSomeStatus:(SomeStatusObject *)status {
if (status != nil) {
[store setObject:status forKey:SOME_STATUS_KEY];
} else {
[store removeObjectForKey:SOME_STATUS_KEY];
}
}
My specific question is if the above can be reduced to
- (void)setSomeStatus:(SomeStatusObject *)status {
[store setObject:status forKey:SOME_STATUS_KEY];
}
Or alternatively
- (void)setSomeStatus:(SomeStatusObject *)status {
store[SOME_STATUS_KEY] = status;
}
No, they are not equivalent. In fact, passing nil to setObject:forKey: (either the value or the key) will result in a runtime exception.
You can't set a value to nil using setObject:forKey.
NSDictionary wants an object, even if that object represents NULL, and the value alone can't be set to simply nil. (You could set it to NSNULL though).
So to answer your question, your code can't replace the existing.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
In my iOS app that uses Parse, some users will need to save thousand of objects in one action. I've tried iterating through the array of data and creating/saving the objects one by one, but this results in the objects saving to my data browser pretty slowly. Each object only needs to contain a few strings, so I don't understand why its taking so long to save these objects.
Is there a faster way to save thousands of objects to Parse?
Edited:
I've always used [PFObject saveAllInBackground:array block:^(BOOL succeeded, NSError *error) {}]; but....another method I have just attempted semi-successfully, was to upload a Json string as a PFFile(no 128k limit), and then use cloud code to parse it and create the necessary PFObjects. I was able to get this to work with small quantities, but unfortunately the cloud code timed out when using a large quantity. I instead opted to utilize a background job to perform the parsing. This takes a considerable amount of time before the data is completely available, but can handle the large amount of data. The upload time itself was much quicker. When using 1000 objects with 3 strings each upload was roughly .8 seconds, vs 23 seconds doing a save all in background, 5000 objects 3 strings each was only 2.5 seconds upload time. In addition to the quicker time you also get progress updates. Depending on the use-case, utilizing this alternative may work best if immediate and quick upload is important, vs making the data immediately available.
IOS Code:
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i<5; i++) {
//subclass of PFObject
Employee *employee = [Employee object];
employee.firstName = #"FName";
employee.lastName = #"LName";
employee.employeeID = #"fid54";
[array addObject:[employee dictionaryWithValuesForKeys:employee.allKeys]];
}
//Seperate class only to store the PFFiles
PFObject *testObject = [PFObject objectWithClassName:#"fileTestSave"];
testObject[#"testFile"] = [PFFile fileWithData:[NSJSONSerialization dataWithJSONObject:array options:0 error:nil]];
NSLog(#"started");
//**notice I am only saving the test object with the NSData from the JSONString**
[testObject saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error && succeeded) NSLog(#"succeeded");
else NSLog(#"error");
}];
Edited: Instead of saving in the beforeSave or afterSave Cloud Code which can cause timeout issues, the background job below can be run anytime. It grab all rows in the "fileTestSave" table, parses the JSON strings in those rows, and adds them to the "Person" table. Once completed it will rows from the table. All asynchronously!
var _ = require('underscore.js');
Parse.Cloud.job("userMigration", function(request, status)
{
// Set up to modify user data
Parse.Cloud.useMasterKey();
//Table called fileTestSave stores a PFFile called "testFile" which we will use an HTTPRequest to get the data. Is there a better way to get the data?
//This PFFile stores a json string which contains relavent data to add to the "Person" table
var testFileSave = Parse.Object.extend("fileTestSave");
var query = new Parse.Query(testFileSave);
query.find().then(function(results)
{
//Generate an array of promises
var promises = [];
_.each(results, function(testFileSaveInstance){
//add promise to array
promises.push(saveJsonPerson(testFileSaveInstance));
});
//only continue when all promises are complete
return Parse.Promise.when(promises);
}).then(function()
{
// Set the job's success status
console.log("Migration Completed NOW");
status.success("Migration completed");
}, function(error) {
// Set the job's error status
status.error("Uh oh, something went wrong.");
});
});
function saveJsonPerson(fileTestSave)
{
//Get the pffile testfile
var testFile = fileTestSave.get("testFile");
//get the fileURL from the PFFile to generate the http request
var fileURL = testFile["url"]();
//return the promise from the httpRequest
return Parse.Cloud.httpRequest({
method:"GET",
url: fileURL
}).then(function(httpResponse){
//return the promise from the parsing
return parsehttpResponse(httpResponse,fileTestSave);
},
function(error){
console.log("http response error");
}
);
}
function parsehttpResponse(httpResponse,fileTestSave)
{
var jsonArray = eval( '(' + httpResponse.text + ')' );
var saveArray =[];
//parse each person in the json string, and add them to the saveArray for bulk saving later.
for (i in jsonArray)
{
var personExtend = Parse.Object.extend("Person");
var person = new personExtend();
person.set("classDiscriminator",jsonArray[i]["classDiscriminator"]);
person.set("lastName",jsonArray[i]["lastName"]);
person.set("firstName",jsonArray[i]["firstName"]);
person.set("employeeID",jsonArray[i]["employeeID"]);
saveArray.push(person);
};
//return the promise from the saveAll(bulk save)
return Parse.Object.saveAll(
saveArray
).then(function(){
//return the promise from the destory
return fileTestSave.destroy(
).then(function(){
},function(error){
console.log("error destroying");
}
);
},function(error){
console.log("Error Saving");
}
);
}
Old Cloud Code that timed out as reference:
Parse.Cloud.afterSave("fileTestSave", function(request) {
//When accessing PFFiles you don't get the actual data, there may be an easier way, but I just utitlized an HTTPRequest to get the data, and then continued parsing.
var file = request.object.get("testFile");
var fileURL = file["url"]();
console.log("URL:"+fileURL);
Parse.Cloud.httpRequest({
method:"GET",
url: fileURL,
success: function(httpResponse)
{
var jsonArray = eval( '(' + httpResponse.text + ')' );
var saveArray =[];
for (i in jsonArray)
{
var personExtend = Parse.Object.extend("Person");
var person = new personExtend();
//May be a better way to parse JSON by using each key automatically, but I'm still new to JS, and Parse so I set each individually.
person.set("classDiscriminator",array[i]["classDiscriminator"]);
person.set("lastName",array[i]["lastName"]);
person.set("firstName",array[i]["firstName"]);
person.set("employeeID",array[i]["employeeID"]);
saveArray.push(person);
};
Parse.Object.saveAll(saveArray,
{
success: function(list) {
// All the objects were saved.
},
error: function(error) {
// An error occurred while saving one of the objects.
},
});
},
error: function(httpResponse) {
console.log("http response error");
}
});
});
Another method for uploading thousands of objects in the background, again this takes some time but can be sized to avoid timing out, as arrays are saved in chunks recursively. I've had no problem saving 10k+ items. Implemented as a category, just enter how many objects at a time you want save at a time, it will save them in the background serially, and recursively until all objects are saved, it also features progress updating via a separate block.
// PFObject+addOns.h
#import <Parse/Parse.h>
#interface PFObject (addOns)
+(void)saveAllInBackground:(NSArray *)array chunkSize:(int)chunkSize block:(PFBooleanResultBlock)block progressBlock:(PFProgressBlock)progressBlock;
#end
#import "PFObject+addOns.h"
#interface PFObject (addOns_internal)
+(void)saveAllInBackground:(NSArray *)array chunkSize:(int)chunkSize block:(PFBooleanResultBlock)block trigger:(void(^)())trigger;
#end
#implementation PFObject (addOns)
+(void)saveAllInBackground:(NSArray *)array chunkSize:(int)chunkSize block:(PFBooleanResultBlock)block progressBlock:(PFProgressBlock)progressBlock
{
unsigned long numberOfCyclesRequired = array.count/chunkSize;
__block unsigned long count = 0;
[PFObject saveAllInBackground:array chunkSize:chunkSize block:block trigger:^() {
count++;
progressBlock((int)(100.0*count/numberOfCyclesRequired));
}];
}
+(void)saveAllInBackground:(NSArray *)array chunkSize:(int)chunkSize block:(PFBooleanResultBlock)block trigger:(void(^)())trigger
{
NSRange range = NSMakeRange(0, array.count <= chunkSize ? array.count:chunkSize);
NSArray *saveArray = [array subarrayWithRange:range];
NSArray *nextArray = nil;
if (range.length<array.count) nextArray = [array subarrayWithRange:NSMakeRange(range.length, array.count-range.length)];
[PFObject saveAllInBackground:saveArray block:^(BOOL succeeded, NSError *error) {
if(!error && succeeded && nextArray){
trigger(true);
[PFObject saveAllInBackground:nextArray chunkSize:chunkSize block:block trigger:trigger];
}
else
{
trigger(true);
block(succeeded,error);
}
}];
}
#end
I think you should be able to do this with sending the save process in quantities of five into the background, so to speak fork it, "thread" it, as apple would refer to it.
here is the link to apples ios threading guides.
I have not used it yet, but I will soon need it as well, as I'm working on a massive database app.
here's the link
https://developer.apple.com/library/mac/Documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html
If you have an array of objects, you can use saveAllInBackgroundWithBlock. This method takes an array of PFObjects as its argument:
https://parse.com/docs/ios/api/Classes/PFObject.html#//api/name/saveAllInBackground:block:
For the faster processing, you can use the parse cloud code, whihc is simply a javascript. You can create a function which takes the array of the data as argument and then in function, you can save the objects.
Parse cloud code has better processing speed than the native one.
For its use, you can refer :
https://parse.com/docs/cloud_code_guide
https://parse.com/docs/js_guide