In reading the documentation and sample code for posting objects, I must have missed something relating to serializing entities. It appears possible to send an entity to postObject and expect it to use the supplied mapping to produce JSON and POST it to a server.
I have been able to map and post an object, but the JSON is not coming through well formed.
I have been able to hand parameterize the object and get valid JSON. I'm about to dig into the source-code, but was wondering what I'm missing.
Here's the code and the results I'm seeing. Insight/help is appreciated!
I have the following Managed Object:
#interface TFUser : NSManagedObject
#property (nonatomic, retain) NSString * first_name;
#property (nonatomic, retain) NSString * last_name;
#end
I have the following code to map it:
+(RKEntityMapping *) mapping
{
if (_mapping == nil)
{
RKObjectManager *objectManager = [RKObjectManager sharedManager];
assert(objectManager && "Object manager not initialized!?");
RKManagedObjectStore *managedObjectStore = objectManager.managedObjectStore;
assert(objectManager && "No object store!?");
// USER Entity Map
_mapping = [RKEntityMapping mappingForEntityForName:#"TFUser"
inManagedObjectStore:managedObjectStore];
[_mapping addAttributeMappingsFromDictionary:#{
#"first_name": #"first_name",
#"last_name": #"last_name"
}];
_mapping.identificationAttributes = #[ #"first_name" ];
}
return _mapping;
}
I have the following code to test serializing one Managed Object directly:
TFUser *user = (TFUser*) [self.managedObjectContext insertNewObjectForEntityForName:#"TFUser"];
user.first_name = #"Mickey";
user.last_name = #"Mouse";
NSError *error;
RKRequestDescriptor *requestDescriptor =
[RKRequestDescriptor requestDescriptorWithMapping:[TFUser.mapping inverseMapping]
objectClass:[TFUser class]
rootKeyPath:#"user"
method:RKRequestMethodAny];
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:user
requestDescriptor:requestDescriptor
error:&error];
NSData *jsonData= [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeJSON error:&error];
NSString *message = [[NSString alloc] initWithData:jsonData encoding:NSASCIIStringEncoding];
NSLog(#"USER JSON:\r\n%#", message);
And this produces what I'd expect:
USER JSON:
{
"user" : {
"first_name" : "Mickey",
"last_name" : "Mouse"
}
}
When I postObject the Managed Object directly:
[[RKObjectManager sharedManager] postObject:user
path:#"/user"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error");
}];
The server receives:
{ user: { first_name: 'Mickey', last_name: 'Mouse' } }
And the parser complains that it can't process the JSON.
Why am I getting a different result? How can I get the post to produce the correct JSON for the server?
Thanks!
- Kevin
The code has been working the entire time. The debugging and some code on the server was broken.
By carefully inspecting the logging of the body request being sent to the server it was apparent that the JSON was well-formed leaving the client. Here it is:
request.headers={
Accept = "application/json";
"Accept-Language" = "en;q=1, fr;q=0.9, de;q=0.8, zh-Hans;q=0.7, zh-Hant;q=0.6, ja;q=0.5";
"Content-Type" = "application/json; charset=utf-8";
"User-Agent" = "Treefort/1.0 (iPhone Simulator; iOS 7.0.3; Scale/2.00)";
}
request.body={
"user" : {
"first_name" : "Mickey",
"last_name" : "Mouse"
}
}
The code on the server was doing this:
console.log req.body
request = JSON.parse req.body
user = request.user
The JSON has already been processed into objects by the time it gets here. This means that the log line is dumping the object to the log, not the text of the received body. The second problem is that there is no reason to be explicitly requesting a JSON parse of the body. It's already been converted to objects.
To fix it I just changed the code to be:
user = req.body.user
Sometimes it helps to have somebody else say... it should be working. I've been banging my head on this for days. Thanks!
Related
I am new to swift and I am trying to parse some simple JSON data I am retrieving from a private API that I have. I am using the SwiftJSON library.
No matter what I do I cannot assign the variable "videoUploadId" with the value of "video_upload_id" coming from the JSON response. I hope i provided enough info to get some help. Thanks
Here is a segment of the code
let task = session.dataTaskWithRequest(request, completionHandler: { (data : NSData!, response, error : NSError!) -> Void in
if (error == nil) {
// Success
let statusCode = (response as NSHTTPURLResponse).statusCode
println("Status Code: \(statusCode)\r\n")
println("Response: \(response)\r\n")
println("Data: \(data)\r\n")
let dataContent = NSString(data: data!, encoding: NSUTF8StringEncoding)!
println("UTF8 Data: \(dataContent)\r\n")
let json = JSON(dataContent)
if let videoUploadId = json["video_upload_id"].int {
println("Video Upload ID (Dict: int): \(videoUploadId)")
}
else if let videoUploadId = json["video_upload_id"].string {
println("Video Upload ID (Dict: string): \(videoUploadId)")
}
else if let videoUploadId = json[0].int {
println("Video Upload ID (Array: int): \(videoUploadId)")
}
else if let videoUploadId = json[0].string {
println("Video Upload ID (Array: string): \(videoUploadId)")
}
else {
println(json["video_upload_id"].error)
}
}
else {
// Failure
println("URL Session Task Failed: %#", error.localizedDescription);
}
})
task.resume()
This is what I am receiving from my console:
Login Response: HTTP 200
Status Code: 201
Response: { URL: https:////videos/uploads/ } { status code: 201, headers {
Connection = "Keep-alive";
"Content-Length" = 24;
"Content-Type" = "application/json";
Date = "Sun, 25 Jan 2015 01:02:42 GMT";
Location = "https:////videos/uploads/";
Server = "Apache/2.2.15 (CentOS)";
"Set-Cookie" = "session=eyJzZXNzaW9uX3Rva2VuIjp7IiBiIjoiUzAxWGFYRnlVVGM1YjBsa1kxWkJiV2xrYVZwcFZXdDFiR0ZLYW5GQ1VqRjFjbk5GIn19.B6XSMg.HXatQ76ZFaoZEQsnNu1BgsVECKA; HttpOnly; Path=/";
} }
Data: <7b227669 64656f5f 75706c6f 61645f69 64223a20 3736307d>
UTF8 Data: {"video_upload_id": 760}
Optional(Error Domain=SwiftyJSONErrorDomain Code=901 "Dictionary["video_upload_id"] failure, It is not an dictionary" UserInfo=0x170238620 {NSLocalizedDescription=Dictionary["video_upload_id"] failure, It is not an dictionary})
As you can see from the code and console output, I am attempting to set the variable in several different ways all of which seem to fail. I am receiving the error "Dictionary["video_upload_id"] failure, It is not an dictionary" I even tried prepending "[" and appending "]" to try to see if its a formatting issue.
Any clues?
You are doing the initialization wrong. You should use:
let json = JSON(data:data) // data is NSData!
Converting NSData to NSString is not necessary, and somehow wrong for this. SwiftyJSON can only be initialized with NSData or Swift object.
So I'm trying to study up on HTTP/TCP/IP Protocols, Nodejs, MongoJS,and MongoDB all at once. So I'm trying to figure out a couple things with the Hapi Web Framework. I am able to communicate with the server with my iOS simulator however, I can't seem to parse the payload right. I understand I could use parameters in the URL to send the information but I would like to use the payload.
So I end up with this being saved into Mongo.
(
{
"_id" = 5431f161bb859872034d2456;
"{\"userLastNameKey\":\"Kwon\",\"userEmailKey\":\"email\",\"userFirstNameKey\":\"Michael\",\"userUsernameKey\":\"username\",\"userPasswordKey\":\"password\"}" = "";
},
{
"_id" = 5431fe5694ed4721046c1f8c;
"{\"userLastNameKey\":\"Kwon2\",\"userEmailKey\":\"email2\",\"userFirstNameKey\":\"Michael2\",\"userUsernameKey\":\"username2\",\"userPasswordKey\":\"password2\"}" = "";
This is my hapi code for the call.
// This will add a new user to the database
function addUser(request, response){
db.usersCollection.save(request.payload, function (err, saved){
if(err || !saved)
{
console.log("User not saved");
} else
{
console.log("User saved");
}
});
}
My end result I would like to try and get it to be like this
(
{
"_id" = 5431f161bb859872034d2456,
"userLastNameKey" = "Kwon",
"userEmailKey" = "email",
"userFirstNameKey"= "Michael",
"userUsernameKey" = "username",
"userPasswordKey" = "password",
}
)
Here's the iOS code
NSDictionary *userData = [User userToDictionary: newUserInfo];
NSData *userJSON = [NSJSONSerialization dataWithJSONObject: userData options: 0 error: nil];
NSURL *url = [NSURL URLWithString: urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: url];
[request setHTTPMethod: #"POST"];
[request addValue: #"application/json" forHTTPHeaderField: #"Content-Type"];
// This will set up the url session
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration: config];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest: request fromData: userJSON completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error)
{
} else
{
}
}];
[uploadTask resume];
}
Anyone know how I can achieve this?
{
"_id" = 5431f161bb859872034d2456;
"userLastNameKey" = "Kwon";
"userEmailKey" = "email";
"userFirstNameKey"= "Michael";
"userUsernameKey" = "username";
"userPasswordKey" = "password";
}
That is NOT a JSON object. You need to use commas instead of semicolons. and colons instead of equals. Your ID is also not a decimal number so it's probably best to make it a string.
Your object would look like :
{
"_id" : "5431f161bb859872034d2456",
"userLastNameKey" : "Kwon",
"userEmailKey" : "email",
"userFirstNameKey" : "Michael",
"userUsernameKey" : "username",
"userPasswordKey" : "password"
}
To parse a (well-formatted) JSON string, use : var JSONObject = JSON.parse(JSONstring);
I have an API that requires I post a complex JSON object. The API saves and responds with a primitive ID (or an error object). I am not able to map the primitive ID. Any ideas? The example below, for simplicity's sake, is not doing the POST object mapping, but some of my API calls will require that as well.
I saw suggestions to utilize RestKit to build the request, and pass it to AFNetworking, but this will not parse a possible error return object.
RKObjectMapping* map = [RKObjectMapping mappingForClass:[MyPrimitiveResponse class]];
[map addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"success"]];
RKResponseDescriptor *errDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MyErrorResponse objectMap] method:RKRequestMethodGET pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassServerError)];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:map method:RKRequestMethodGET pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURL *URL = [NSURL URLWithString:apiUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ errDescriptor, responseDescriptor ]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load collection of Articles: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[objectRequestOperation start];
I get the following error in the debugger:
2013-10-29 10:23:15.196 TestParser[6988:70b] E app:MyHomeViewController.m:54 Operation failed with error: Error Domain=org.restkit.RestKit.ErrorDomain Code=-1017 "Loaded an unprocessable response (200) with content type 'application/json'" UserInfo=0x8daca20 {NSErrorFailingURLKey=....., NSUnderlyingError=0x8db4cd0 "The operation couldn’t be completed. (Cocoa error 3840.)", NSLocalizedDescription=Loaded an unprocessable response (200) with content type 'application/json'}
UPDATE:
This is my final bits of code to handle this situation. It may prove useful to others...
// Manually Map Request to JSON & send to server
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:payload requestDescriptor:[payload.class requestDescriptor] error:&error];
NSMutableURLRequest* request = [self.apiManager requestWithObject:nil method:RKRequestMethodPOST path:url parameters:parameters];
RKHTTPRequestOperation *requestOperation = [[RKHTTPRequestOperation alloc] initWithRequest:request];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
unichar firstChar = [operation.responseString characterAtIndex:0];
NSData *newData;
if (firstChar != '{' && firstChar != '[') {
// Force into JSON array so it can be parsed normally
newData = [[[#"[" stringByAppendingString:operation.responseString] stringByAppendingString:#"]"] dataUsingEncoding:NSUTF8StringEncoding];
} else {
newData = operation.responseData;
}
// Parse JSON response into native object, whether primitive NSNumber (integer or boolean) response or a full fledged JSON error object.
RKResponseDescriptor *errDescriptor = [MyErrorResponse responseDescriptor];
RKObjectResponseMapperOperation* mapper = [[RKObjectResponseMapperOperation alloc] initWithRequest:request response:operation.response data:newData responseDescriptors:#[errDescriptor, [payload.class responseDescriptor]]];
[mapper setDidFinishMappingBlock:^(RKMappingResult *mappingResult, NSError *error) {
if (mappingResult) { //Success
RKLogInfo(#"Load response: %#", mappingResult.firstObject);
} else {
RKLogError(#"Operation failed with error: %#", error);
}
}];
[mapper start];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[requestOperation start];
I saw suggestions to utilize RestKit to build the request, and pass it to AFNetworking
Yes, do that.
but this will not parse a possible error return object
True, but you can use a mapping operation in RestKit to process that. Or, if the error response is relatively simple just use NSJSONSerialization (or similar) to convert the response.
The 'primitive ID' should be mappable with a nil-keypath mapping. But you may still have issues in mapping an error response if it isn't being mapped back into the source object used in the POST request.
I am using RestKit 2.0 to send a core data entity and an image to a server with the 'multipartFormRequestWithObject' method. However, when the entity data arrives it is not in json format. If I send the entity using 'postObject' without an image then the data is in json format. I use the same RKObjectMapping for both situations.
What do I have to do to make the Object serialize to json? I tried
[request setValue:#"application/json" forHTTPHeaderField:#"content-type"];
But that didn't help and I already have my object manager settings as so:
[objectManager setRequestSerializationMIMEType:RKMIMETypeJSON];
[objectManager setAcceptHeaderWithMIMEType:RKMIMETypeJSON];
My Header Content-Type is multipart/form-data but I guess that is required.
request.headers={
Accept = "application/json";
"Accept-Language" = "en;q=1, fr;q=0.9, de;q=0.8, zh-Hans;q=0.7, zh-Hant;q=0.6, ja;q=0.5";
"Accept-Version" = 1;
"Content-Length" = 19014;
"Content-Type" = "multipart/form-data; boundary=Boundary+0xAbCdEfGbOuNdArY";
"User-Agent" = "app/1.0 (iPhone Simulator; iOS 7.0; Scale/2.00)";
}
My complete code is for the mapping and operation are below. As usual any feedback would be great. Thanks. Al
- (void)loginMainUser:(NSDictionary*)paramsDict path:(NSString *)apiPath{
RKObjectManager *manager = [RKObjectManager sharedManager];
// Response Mapping
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[self class]];
[mapping addAttributeMappingsFromDictionary:#{#"token" : #"token",
#"_links" : #"links"}];
[manager addResponseDescriptorsFromArray:#[[RKResponseDescriptor responseDescriptorWithMapping:mapping
method:RKRequestMethodAny
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]];
// Request Mapping
RKObjectMapping *userRequestMapping = [RKObjectMapping requestMapping];
[userRequestMapping addAttributeMappingsFromDictionary:#{#"name" : #"first_name",
#"surname" : #"last_name",
#"email" : #"email",
#"password" : #"password"}];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:userRequestMapping
objectClass:[self class]
rootKeyPath:nil
method:RKRequestMethodAny];
[manager addRequestDescriptor:requestDescriptor];
UIImage *image = [UIImage imageNamed:#"logo.png"];
// Serialize the Article attributes then attach a file
NSMutableURLRequest *request = [manager multipartFormRequestWithObject:self
method:RKRequestMethodPOST
path:apiPath
parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:UIImagePNGRepresentation(image)
name:#"logo"
fileName:#"logo.png"
mimeType:#"image/png"];
}];
RKObjectRequestOperation *operation = [manager objectRequestOperationWithRequest:request
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed");
}];
[manager enqueueObjectRequestOperation:operation];
}
multipartFormRequestWithObject is explicitly not JSON. This is by design. It's the HTTP content type that is changed so JSON and multipart form are mutually exclusive.
So, you need to change your mind about what you're trying to achieve.
One option could be to use a mapping operation to create the JSON for your object and then supply that JSON when you call multipartFormRequestWithObject. This would give you a multipart form message being sent with a section of JSON that could be deserialised on the server.
It is not the best approach but if you really need to get the JSON from a object with RestKit, you can convert it using this code:
// The object you want to convert
YourObject *object = ...;
// The RestKit descriptor that you want to use to map.
RKRequestDescriptor *requestDescriptorObject = ...;
NSDictionary *parametersForObject;
parametersForObject = [RKObjectParameterization parametersWithObject:object
requestDescriptor:requestDescriptorObject
error:nil];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parametersForObject
options:NSJSONWritingPrettyPrinted
error:nil];
NSString *jsonString;
if(jsonData) {
jsonString = [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
}
And then with the JSON string you can post it in a multi-part post. It can be done with any method, but as you have already RestKit, you can do it with the library.
For example, at the code of below there is a post of a file and the JSON string (in fact, the JSON data).
// The RestKit manager
RKObjectManager *manager = ...;
// The server endpoint
NSString *path = ...;
NSMutableURLRequest *request = [manager multipartFormRequestWithObject:nil method:RKRequestMethodPOST path:path parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// Post of the file
[formData appendPartWithFileData:[NSData dataWithData:self.dataFile]
name:#"file"
fileName:filename
mimeType:mimetype];
// Post of the JSON data
[formData appendPartWithFormData:jsonData name:#"json_data"];
}];
I have a problem to download a file or manage an error in some cases (JSON)
The download works very fine. The problem is the server can send a 404 or 200 (with JSON) in a few cases when the user requests the download.
How to handle the JSON in this case? When we send the request we don't know if we will receive JSON error (with a 200 status oR 404) or the zipped file...
I don't see how responseObject can help me.
Here is my code:
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:zipPath append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"Successfully downloaded zip file to %#", zipPath);
// is-it impossible to handle a JSON response here ?
// responseObject can help me ? don't think !
// do what I have to do after the download is complete
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
// 404
// is-it impossible to handle a JSON response here ?
if ([error code] == -1011)
{
}
}];
[operation setDownloadProgressBlock:^(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
float progress = ((float)((int)totalBytesWritten) / (float)((int)totalBytesExpectedToWrite));
self.progressView.progress = progress;
}];
[operation start];
Does I need to make a AFJSOnRequestOperation ? But in this case, how to receive the downloaded file that is not JSON ?
Thanks for helping.
EDIT: as I wroted in my comments, I can get and catch the responseData in success block ONLY if I comment the line:
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:zipPath append:NO];
It's logical, I suppose the response goes into outputStream instead of success block. Is there a solution for that ?
The server should signal the MIME type in the header Content-Type.
If it doesn't you need to detect the data type yourself.
This line ensures to get all responses with statuscode 200-499 in the success block:
[AFHTTPRequestOperation addAcceptableStatusCodes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(300, 200)]];
In the success block you need to identify the type of response:
id jsonObject = AFJSONDecode(operation.responseData, NULL);
if (jsonObject) {
// handle JSON
} else {
// handle zip file
}
You can also inspect operation.response.statusCode and operation.response.MIMEType beforehand.
I've tried with AFJSOnRequestOperation, in .m I simply put the outputStream beneath the init:
AFJSONRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease];
requestOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:[FullyLoaded filePathForResourceAtURL:urlRequest.URL.absoluteString] append:NO];
And the same happens, responseData is nil unless commenting the outputStream line, so it's not HTTPRequest or JSOnRequest issue. The way I use to get around is to JSON sequence the object from the responseData and write it to file, rather than directly outputstream the request.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFCompoundResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/octet-stream"];
AFHTTPRequestOperation *operation = [manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (responseObject) {
}
else{
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[operation start];
// manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/octet-stream"]; : can vary based on what you expect