How to change JSON before mapping by RESTKIT - objective-c

I am using RESTKIT to map the JSON returned from server.
The JSON result obtained from server is as follows
{"term":"Zh","results":{"users":[{"id":{"$oid":"4ebe59970a614e0019000055"},"term":"some text","score":1}]}
How can I convert the above JSON result to the below:
{"results":{"users":[{"uid":"4ebe59970a614e0019000055","text":"some text"}]}
Also, where can I do this so that the RESTKIT mapping will use the converted JSON instead of the initial one?
Below is the loader class that I am using to manage the JSON and mappings
-(void)getObjects
{
RKObjectManager *sharedManager = [RKObjectManager sharedManager];
[sharedManager loadObjectsAtResourcePath:self.resourcePath delegate:self];
}
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {
NSLog(#"Loaded PAYLOAD successfully for %#, with response %#", self.resourcePath , [response bodyAsString] );
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects
{
}
+ (void)setManagerAndMappings
{
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:SERVER_URL];
RKObjectMappingProvider* provider = [[RKObjectMappingProvider new] autorelease];
//User Object Mapping
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[User class]];
[userMapping mapKeyPath:#"_id" toAttribute:#"uid"];
[userMapping mapAttributes:#"avatar_url",#"created_at",#"email",#"name",#"nickname",#"follower_count",#"following_count",#"answer_count",#"new_notification_count",#"new_questions_count",#"is_following",#"facebook_configured",#"twitter_configured",#"description",#"approved",#"type", nil];
[provider setMapping:userMapping forKeyPath:#"user"];
}

#Shocks answer is appealing, unfortunately it is valid a for version before 0.20.
Anyway, a similar solution is available for 0.20 too, using an implementation of RKSerialization:
#interface ORRKJsonSerialization : NSObject <RKSerialization>
#end
and implementing
#implementation ORRKJsonSerialization
+ (id)objectFromData:(NSData *)data error:(NSError **)error
{
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:error];
// change your data before mapping
return result;
}
+ (NSData *)dataFromObject:(id)object error:(NSError **)error
{
return [NSJSONSerialization dataWithJSONObject:object options:0 error:error];
}
#end
then during the setup:
[RKMIMETypeSerialization registerClass:[ORRKJsonSerialization class] forMIMEType:#"application/json"];
HTH

There is a willMapData: selector in the RKObkectLoaderDelegate that is invoked just after parsing has completed. The mappableData argumet is mutable, so i guess you can change the data just before the object mapping will take place.

use RKObjectRequestOperation.setWillMapDeserializedResponseBlock:.
In swift:
let request = RKObjectManager.sharedManager().requestWith...
let operation = RKObjectManager.sharedManager().managedObjectRequestOperationWithRequest(request, managedObjectContext: context, success: { operation, result in
// success
}, failure: { operation, error in
// failure
})
operation.setWillMapDeserializedResponseBlock { deserializedResponse -> AnyObject! in
// Here to transform response
return transformResponse(deserializedResponse)
}
RKObjectManager.sharedManager().enqueueObjectRequestOperation(operation)

For me the only solution is to modify the returned object at the server level.
If you can't,just map that returns the server.

You can register your own parser. I had to do this to modify and clean-up some JSON from a legacy system.
[[RKParserRegistry sharedRegistry] setParserClass:[MyJSONParser class]
forMIMEType:#"application/json"];
And then create your own:
#import <RestKit/RestKit.h>
#import <RestKit/RKJSONParserJSONKit.h>
#import <RestKit/JSONKit.h>
#import <RestKit/RKLog.h>
#interface EFJSONParser : RKJSONParserJSONKit
- (NSDictionary *)objectFromString:(NSString *)string error:(NSError **)error;
#end

It's very difficult to editing the response of JSOn. the better way is only to do changes in server side as said by #beber

Related

CoreBluetooth: unable to write and retrieve a static characteristic

I am building an application where two iOS devices both transmit and scan (peripheral and central) for each other. Due to Apple's implementation, when the app is backgrounded, all identifiable information is removed from the advertising packet, meaning I need to connect to the discovered peripherals to find out who and what they are if they are transmitting in the background.
All I really need to do is identify the peripheral. (Connect and disconnect). Currently, the only way I can find to do this is to set a static characteristic attached to a common service that allows each device to uniquely identify itself, even when backgrounded. This value will not change or get updated. If I could simply look at peripheral.UUID after connecting, this would do the trick. But I can't anymore with iOS8. So, I create a characteristic to contain the unique identifier.
(Not sure if this is the best way, but its the only way I can think of.)
Everything is working great (discovering characteristic) but I am unable to retrieve anything other than nil for the characteristic, even though I have specifically set it when I started transmitting.
Here is my (Peripheral code):
-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
// Opt out from any other state
if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
return;
}
NSLog(#"BT Transmitter Powered On");
NSString* uniqueString = #“foobar";
NSData* characteristicValue = [uniqueString dataUsingEncoding:NSUTF8StringEncoding];
self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:#"08590F7E-DB05-467E-8757-72F6FAEB13D4"]
properties:CBCharacteristicPropertyRead
value:characteristicValue
permissions:CBAttributePermissionsReadable];
CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:#"E20A39F4-73F5-4BC4-A12F-17D1AD07A961"] primary:YES];
transferService.characteristics = #[self.transferCharacteristic];
[self.peripheralManager addService:transferService];
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataServiceUUIDsKey: #[[CBUUID UUIDWithString:#"E20A39F4-73F5-4BC4-A12F-17D1AD07A961"]] }];
}
And here is my Central Code:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
// print out value of discovered characteristic
NSLog (#"Characteristic discovered: %#", characteristic); // this outputs all all the properties of the characteristic, including a value of "null".
NSString *value = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"Value: %#",value); // this prints out nothing
}
}
What am I doing wrong? I would expect to see the value of the characteristic as "foobar" when transformed back into an NSString. Instead it is null.
Having discovered the characteristic you need to perform a read request to actually get its value -
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
// print out value of discovered characteristic
NSLog (#"Characteristic discovered: %#", characteristic); // this outputs all all the properties of the characteristic, including a value of "null".
if ([characteristic.UUID.UUIDString isEqualToString:#"08590F7E-DB05-467E-8757-72F6FAEB13D4"]) {
[peripheral readValueForCharacteristic:characteristic];
NSString *value = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"Value: %#",value); // this prints out nothing
}
}
You will subsequently get a call to didUpdateValueForCharacteristic: -
-(void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error == nil) {
NSString *valueString=[[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"The value=%#",valueString
}
}
Update: to find the RSSI of the peripheral once you have connected to it and read its service, use the readRSSI method.
Then, strangely enough, even though its not in the documentation, this is the only delegate callback method (with RSSI) that works for me running 8.1.1.
-(void) peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error {
NSLog(#"Got RSSI update in didReadRSSI : %4.1f", [RSSI doubleValue]);
}
Now I just have to figure out how to link this RSSI signal with the specific peripheral I connected to and identified in the previous call.

Show error messages in UIAlertView by UIAlerView+AFNetworking

There is a categroy UIAlertView+AFNetworking.h ship with AFNetworking 2.0. And I use it to show error messages in my App like this:
[UIAlertView showAlertViewForTaskWithErrorOnCompletion:task delegate:nil]
But I want to add some other messages in the message of UIAlertView.
i.e.:
StatusCode: 422
JSON Data: {"errors":{"detail":"Wrong password"}}
How to get "Wrong password" and show in a UIAlertView by the functions in UIAlertView+AFNetworking?
UIAlertView+AFNetworking category uses error messages from NSError – localizedDescription, localizedRecoverySuggestion and localizedFailureReason. AFNetworking generates NSError on failure, however, you must write your own responseSerializer to set your custom error messages there.
This topic has been thoroughly discussed on GitHub and I use solution based #camdez's code snippet.
My error response messages from API looks like this:
{ error: { message: "This username already exists" } }
Now, I've subclassed AFJSONResponseSerializer and when an error occurs, I save the error message to the NSError instance.
CustomResponseSerializer.h
#interface CustomResponseSerializer : AFJSONResponseSerializer
#end
CustomResponseSerializer.m
#implementation CustomResponseSerializer
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
id JSONObject = [super responseObjectForResponse:response data:data error:error]; // may mutate `error`
if (*error && [JSONObject objectForKey:#"error"]) {
NSMutableDictionary *mutableUserInfo = [(*error).userInfo mutableCopy];
mutableUserInfo[NSLocalizedFailureReasonErrorKey] = [[JSONObject objectForKey:#"error"] objectForKey:#"message"];
NSError *newError = [NSError errorWithDomain:(*error).domain code:(*error).code userInfo:[mutableUserInfo copy]];
(*error) = newError;
}
return JSONObject;
}
#end
Finally, you need to set appropriate responseSerializer on AFHTTPSessionManager (or AFHTTPRequestOperationManager if you use NSURLConnection API) instance. If you subclass this class as I do, you can simply do it in your init method by following:
self.responseSerializer = [CustomResponseSerializer serializer];

RestKit Post method with JSON

I'm developing an iOS App for school.
I'm using a database so i can run some statistics later.
I created a Restful Web Service to handle all functions I need and using RestKit to access the Web Service.
When I need to retrieve data from WS i dont have any problem to do it, but when i need to post info I am getting some errors that I would like some help if you can.
The POST method I created in WS is for adding a new Collection to DB, it has no return, just add it. I'm using the GSON library to convert from JSON. I tested with the "tester" from netbeans and worked well so I guess the problem is not on the Web Service. I'll put the code for the classes involved and the method that is trying to POST de object.
I have this class: Collection
#import <Foundation/Foundation.h>
#import "User.h"
#interface Collection : NSObject
#property NSNumber *idCollection;
#property NSString *name;
#property User *user;
#property NSArray *collectionItens;
#end
And this class: User
#import <Foundation/Foundation.h>
#interface User : NSObject
#property NSNumber *idUser;
#property NSString *login;
#property NSString *password;
#end
Both class only have the #syntezise
Here is the method im trying to post the object:
- (IBAction)createNewCollection:(id)sender
{
NSLog(#"..");
Collection *collection = [[Collection alloc] init];
collection.name = collectionNameTextField.text;
collection.user = [AppDefauts defaultUser];
RKObjectMapping *userMapping = [RKObjectMapping requestMapping];
[userMapping addAttributeMappingsFromArray:#[ #"idUser", #"login", #"password"]];
RKObjectMapping *collectionMapping = [RKObjectMapping requestMapping];
[collectionMapping addAttributeMappingsFromArray:#[ #"idCollection", #"name"]];
[collectionMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"user" toKeyPath:#"user" withMapping:userMapping]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:collectionMapping objectClass:[Collection class] rootKeyPath:#"collection" method:RKRequestMethodAny];
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://localhost:8080/MTGAppWS/webresources"]];
[objectManager addRequestDescriptor:requestDescriptor];
[objectManager postObject:collection path:#"/Collection" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error");
}];
}
Log display on Xcode with error:
http://pastebin.com/JXCYzF8B
Web Service code for include a new Collection
#Path("Collection")
public class CollectionService
{
}
#Context
private UriInfo context;
/**
* Creates a new instance of CollectionService
*/
public CollectionService()
{
}
#PUT
#Consumes("application/json")
public void createNewCollection(String content)
{
CollectionController c= new CollectionController(new SQLController());
Gson gson = new Gson();
JsonParser parser = new JsonParser();
parser.parse(content);
Collection collection = gson.fromJson(content, Collection.class);
c.criarNovaCollection(collection);
}
}
EDIT
Found out that the request wasn't "going" to right address.
already fixed that but now its happening another error. (Method not allowed).
Error message: http://pastebin.com/99FiNmQZ
The server you are communicating with is giving you an HTML error page instead of the JSON you were expecting: The requested resource () is not available. The error isn't in your app (unless you are asking for the wrong URL); it's on the server.

Restkit NSString includes metadata

I have a JSON file that i'm downloading from the web and parsing using RestKit. My problem is, I have a property with an array of strings that i'm trying to map into their own object. For some reason, when parsing that string, it ends up including the metadata object's description in the string. Please see my code snippets below for what i'm currently using, and the errors i'm experiencing. Note: I removed any core data integration and made my test as bare-bones as I could to attempt to track down the issue.
Object interfaces
#interface BLAuthor : NSObject
#property (nonatomic, retain) NSString *name;
#end
#interface BLVolume : NSObject
#property (nonatomic, retain) NSString *id;
#property (nonatomic, retain) NSSet *authors;
#end
Mapping and request operation
RKObjectMapping *volumeMapping = [RKObjectMapping mappingForClass:[BLVolume class]];
[volumeMapping addAttributeMappingsFromDictionary:#{ #"id": #"id" }];
RKObjectMapping *authorMapping = [RKObjectMapping mappingForClass:[BLAuthor class]];
[authorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil
toKeyPath:#"name"]];
[volumeMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"volumeInfo.authors"
toKeyPath:#"authors"
withMapping:authorMapping]];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:volumeMapping
pathPattern:nil
keyPath:#"items"
statusCodes:statusCodes];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://example.com/?q=123"]];
RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[responseDescriptor]];
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *result) {
NSLog(#"Success! %#", result);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed with error: %#", [error localizedDescription]);
}];
NSOperationQueue *operationQueue = [NSOperationQueue new];
[operationQueue addOperation:operation];
JSON snippet
{
"items": [
{
"id": "abc",
"volumeInfo": {
"authors": [
"123"
]
}
}
Resulting data - please note, everything after <BLAuthor = 0x08466250 | name = is actually part of the NSString property on BLAuthor:
Success! <RKMappingResult: 0x8468560, results={
items = (
"<BLVolume = 0x08463F60 | id = mikPQFhIPogC | authors = {(
<BLAuthor = 0x08466250 | name = 123 ({
HTTP = {
request = {
URL = \"https://example.com/?q=123";
headers = {
};
method = GET;
};
response = {
URL = \"https://example.com/?q=123\";
headers = {
\"Cache-Control\" = \"private, max-age=0, must-revalidate, no-transform\";
\"Content-Type\" = \"application/json; charset=UTF-8\";
Date = \"Sun, 23 Jun 2013 00:41:01 GMT\";
Etag = \"\\\"I09ELXbrmOlE-RFCkDsRbIJj278/gPh8_OxpfA9YHXz_P_25F8A4orw\\\"\";
Expires = \"Sun, 23 Jun 2013 00:41:01 GMT\";
Server = GSE;
\"Transfer-Encoding\" = Identity;
\"X-Content-Type-Options\" = nosniff;
\"X-Frame-Options\" = SAMEORIGIN;
Thanks in advance to anyone who can help me resolve this! I'm at wits end - tried to remove as many variables from my testing as I can, and have searched both the web and RestKit's source looking for the cause.
The problem is with this mapping
[authorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil
toKeyPath:#"name"]];
You're basically saying: "map whatever you find onto the name property".
This can only work in case you're matching the path as opposed to the key in the response descriptor, as suggested here. But in order to do so, you would need the response to return the string array standalone, which is not your case.
As per the same post, you can try with
[authorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:#""
toKeyPath:#"name"]];
but I am skeptical it could work.
If you have control over the APIs, I'd suggest to change the format to a more RESTful one, by returning full-fledged resources instead of plain strings inside the array.
So, i've come to a sad conclusion - nothing is broken, my code works perfectly.
With relationships, apparently RestKit sets your objects to an NSProxy subclass. The subclass overrides -description and returns #"%# (%#)", originalObject, metadata. So when logging the description of the object, I get that, instead of the ACTUAL objects' value. Very irritating and difficult to track down. I'm going to be opening an issue on RestKit to remove this confusing description, as when building my app without a UI yet, logging the description of the actual object is important.

Objective-C: No objects in array after adding them. Out of scope!

I have a NSMutableArray in an object.
In an object-method, I do something like this:
/* ... */
[[LRResty client] get:connectURL withBlock:^(LRRestyResponse *r) {
SBJsonParser *jsonParser = [SBJsonParser new];
NSDictionary *jsonResponse = [jsonParser objectWithString:[r asString]];
NSDictionary *permittedBases= [jsonResponse objectForKey:#"permittedBases"];
Database *database = [[Database alloc] init];
for (id key in permittedBases) {
/* ... */
[workingDatabases addObject:database];
}
}];
return workingDatabases;
At the return line, there are no objects in my array (anymore). I am aware of the fact, that the 'database'-objects are going out of scope. But I am saving them in the array.
Am I overseeing something?
If it is of any help, here is the header file:
#class Database;
#interface CommunicationHelper : NSObject {
NSMutableArray *workingDatabases;
}
// The function where the problem appears:
- (NSMutableArray *)getDatabasesForWebsite:(Website *)websiteIn;
#property(nonatomic,copy) NSMutableArray *workingDatabases;
#end
just allocate your workingDatabases (Mutable array) somewhere before using that array.
Once you allocate it,It will work fine.
I assume it's because [LRResty client] get: is asynchronous. The block is called when the connection is finished, i.e. after the call to return.
//Called first
[[LRResty client] get:connectURL
//Called second
return workingDatabases;
//Called later when the connection is finished
SBJsonParser *jsonParser = [SBJsonParser new];
NSDictionary *jsonResponse = [jsonParser objectWithString:[r asString]];
NSDictionary *permittedBases= [jsonResponse objectForKey:#"permittedBases"];
Database *database = [[Database alloc] init];
for (id key in permittedBases) {
/* ... */
[workingDatabases addObject:database];
}
Edit
Ajeet has a valid point too, ensure your array is initialized.
I used the LRResty framework for accessing a RESTful webservice. It was an odd thing anyways, so I switched to a way more rich-featured framework, called "ASIHTTP". I would recommend that to anyone who wants to use RESTful services (and more) on iOS