17

I am retrieving JSON data from a URL that is formatted like so:

{"zoneresponse":
{"tasks":
 [{"datafield1":"datafor1", 
   "datafield2":"datafor2", 
   "datafield3":"datafor3",...
 }]
}}

I have no control over the structure as it is from a private API.

How do I insert data in a selected data field of an existing object?

I have tried this:

self.responseData = [NSMutableData data];

//testingURL is the api address to the specific object in tasks
NSURL *url = [NSURL URLWithString:testingURL];

NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[[[params objectForKey:@"zoneresponse"] objectForKey:@"tasks"] setValue:@"HelloWorld" forKey:@"datafield1"];
//HAVE TRIED setObject: @"" objectForKey: @"" as well

//*****PARAMS IS EMPTY WHEN PRINTED IN NSLog WHICH IS PART OF THE ISSUE - SETTING VALUE DOES NOT WORK

NSError * error = nil;

NSLog(@"Params is %@", params);

NSData *requestdata = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];

NSMutableURLRequest *request;
request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setValue:[NSString stringWithFormat:@"%d", [requestdata length]] forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:requestdata];

NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];


if(conn) {
    NSLog(@"Connection Successful, connection is: %@", conn);

} else {
    NSLog(@"Connection could not be made");
}

The connection is being made but the dictionary params is empty when printed (the setValue is not displaying) and is not entering any data into the field I select.

I have checked these links but nothing explains whether it will insert into the right field and implies it will create a new object rather than update the existing one.

How to update data on server db through json api?

How to send json data in the Http request using NSURLRequest

Delegate methods

//any time a piece of data is received we will append it to the responseData object
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [self.responseData appendData:data];

    NSError *jsonError;

    id responseDict =
    [NSJSONSerialization JSONObjectWithData:self.responseData
                                options:NSJSONReadingAllowFragments
                                  error:&jsonError];

    NSLog(@"Did Receive data %@", responseDict);
}

 //if there is some sort of error, you can print the error or put in some other handling here, possibly even try again but you will risk an infinite loop then unless you impose some sort of limit
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    // Clear the activeDownload property to allow later attempts
    self.responseData = nil;

    NSLog(@"Did NOT receive data ");

}

//connection has finished, the requestData object should contain the entirety of the response at this point
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSError *jsonError;
    id responseDict =
    [NSJSONSerialization JSONObjectWithData:self.responseData
                                options:NSJSONReadingAllowFragments
                                  error:&jsonError];
    if(responseDict)
    {
        NSLog(@"%@", responseDict);
    }
    else
    {
        NSLog(@"%@", [jsonError description]);
    }

    //clear out our response buffer for future requests
    self.responseData = nil;
}

The first method here states that data was received with "Did Receive data (null)", there is no error with connection however the final method prints the error message "JSON text did not start with array or object and option to allow fragments not set.", which is understandable because there is no data or object being sent.

How do I insert data into a selected field of an existing object?

Community
  • 1
  • 1
App Dev Guy
  • 5,396
  • 4
  • 31
  • 54
  • where and how do you start the **conn**? I mean [conn start] – Vivek Molkar Apr 16 '15 at 09:29
  • @VivekMolkar I have this inside of a (IBAction) method and I assume it connects autonomously based on my return methods `- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data`, `- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error` and `- (void)connectionDidFinishLoading:(NSURLConnection *)connection` – App Dev Guy Apr 16 '15 at 10:02
  • No, you have to put a line of code `[conn start]` to initiate the connection. **conn** by itself not initiate. – Vivek Molkar Apr 16 '15 at 10:07
  • your delegate methods, do they get hit? – Vivek Molkar Apr 16 '15 at 10:09
  • @VivekMolkar yes the delegate for receivingData gets hit and displays ASCII in the log, as well as the `- (void)connectionDidFinishLoading:(NSURLConnection *)connection` except the NSLog message I have that creating a dictionary of results throws an error because it's not receiving a JSON array back. `[conn start];` has now been added however is not doing anything additional. – App Dev Guy Apr 16 '15 at 10:43
  • the issue is that the "params" is empty. NSLog displays {} when printed. – App Dev Guy Apr 16 '15 at 10:49
  • can you show me the code from delegate methods also? – Vivek Molkar Apr 16 '15 at 10:53
  • 1
    Boom!! I am out of options. Everything I thought was wrong. Sorry dude have no answer now :( – Vivek Molkar Apr 16 '15 at 11:12
  • @VivekMolkar I really appreciate you're effort. If you pop a favorite on the question you can keep track of it should i or anyone else work it out. The issue is primarily that nothing is being inserted from dictionary "param" – App Dev Guy Apr 16 '15 at 11:14

2 Answers2

13

You do this wrong:

NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[[[params objectForKey:@"zoneresponse"] objectForKey:@"tasks"] setValue:@"HelloWorld" forKey:@"datafield1"];

Your params is empty dictionary and no object will be returned with [params objectForKey:@"zoneresponse"].

Try this:

NSMutableDictionary *params = [NSMutableDictionary new];
params[@"zoneresponse"] = @{@"tasks": @{@"datafield1": @"HelloWorld"}};

This will work but object for key @"tasks" will be immutable. To add another objects to tasks dictionary we need to make it mutable:

NSMutableDictionary *params = [NSMutableDictionary new];
NSMutableDictionary *tasks = [NSMutableDictionary new];
params[@"zoneresponse"] = @{@"tasks": tasks};
params[@"zoneresponse"][@"tasks"][@"datafield1"] = @"HelloWorld";

or

NSMutableDictionary *params = [NSMutableDictionary new];    
params[@"zoneresponse"] = @{@"tasks": [@{@"datafield1": @"HelloWorld"} mutableCopy]};

Then you can add another objects to tasks:

params[@"zoneresponse"][@"tasks"][@"datafield2"] = @"HelloWorld2";
params[@"zoneresponse"][@"tasks"][@"datafield3"] = @"HelloWorld3";

I think my answer will bring some clarity to operations with dictionaries for you.

Vlad
  • 7,199
  • 2
  • 25
  • 32
2

Architectural context of your approach to sending JSON data to a servers database:

You are using an old method (since 03') of making a HTTP POST request to get your lovely JSON data in to the servers database. It is still functional and not deprecated and ultimately an acceptable approach. Typically how this approach works is you set up and fire a NSURLRequest with a NSURLConnection, the ViewController or object that fired the request typically implements the NSURLConnection protocol so you have a callback method that receives the NSURLRequests associated response as you've done. Theres also some NSURLCaching and NSHTTPCookStorage available to avoid redundancy and speed up the whole thing.

There is a new way (since 13'):

NSURLConnections successor is NSURLSession. Since Vladimir Kravchenko's answer focusses on forming the NSDictionary to be sent as a parameter who showed that you need to use a NSMutableDictionary instead of a static NSDictionary which can't be edited after it's init'ed. I'll focus on the networking approach surrounding your question though in order to be helpful.

/*
The advantage of this code is it doesn't require implementing a
protocol or multiple callbacks.
It's self contained, uses a more modern framework, less code
and can be just thrown in to the viewDidAppear

Basically - theres less faffing about while being a little easier to understand.
*/
NSError *requestError;
// session config
NSURLSessionConfiguration *configuration = 
[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = 
[NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
// setup request
NSURL *url = [NSURL URLWithString:testingURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:60.0];

[request addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request addValue:@"application/json" forHTTPHeaderField:@"Accept"];
[request setHTTPMethod:@"POST"];
// setup request parameters, Vladimir Kravchenko's use of literals seems fine and I've taken his code
NSDictionary *params = @{@"zoneresponse" : @{@"tasks" : [@{@"datafield1" : @"HelloWorld"} mutableCopy]}};
params[@"zoneresponse"][@"tasks"][@"datafield2"] = @"HelloWorld2";
params[@"zoneresponse"][@"tasks"][@"datafield3"] = @"HelloWorld3";
// encode parameters
NSData *postData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&requestError];
[request setHTTPBody:postData];
// fire request and handle response
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:
    ^(NSData *data, NSURLResponse *response, NSError *error) 
{
    NSError *responseError;
    // parse response
    NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:data
                                                                 options:NSJSONReadingAllowFragments
                                                                   error:&responseError];
    // handle response data
    if (responseError){
        NSLog(@"Error %@", responseError)
    }else{
        if (responseDict){
            NSLog(@"Response %@", responseDict);
        }
    }
}];

[postDataTask resume];

Further Reading

The always wonderful Mattt Thompson has written about the transition from NSURLConnection to NSURLSession here:objc.io

You can find another similar Stack Overflow Questions here:Stack Overflow

If you would like a networking library that makes these issues a little easier check out Mattt Thompsons AFNetworking which can be found here: GitHub

Further analysis on NSURLConnection versus NSURLSession can be found here:Ray Wenderlich

Community
  • 1
  • 1
Sean Dev
  • 1,239
  • 1
  • 17
  • 31
  • 1
    Really thorough answer. I will have to try and implement things tomorrow as I am currently away from the desk. I have had a look at AFNetworking however for all intensive purposes it didn't fit what I needed in this specific case, hence asking the question. The stack question doesn't really cover the question specifically however does provide some good points regarding connection. Thanks for the answer. I will be sure to try and implement tomorrow. – App Dev Guy Apr 27 '15 at 11:28