Reputation: 1443
I have a special need to send a pre-formatted JSON string to the server. Due to the server using older Microsoft technology the JSON elements MUST be in a certain order. If I use the standard JSON processing of RestKit the JSON elements come from a dictionary and are added in hash order. Sadly this will not work for this one special case.
How can I send a pre-formatted JSON string instead of an NSDictionary that is converted to a JSON string with RestKit 0.2x?
Here is the code for the request using NSDictionary
RKObjectManager *objectManager = self.createObjectManager;
RKObjectMapping *requestMapping = [EssenceRequest.objectMapping inverseMapping];
[objectManager addRequestDescriptor:[RKRequestDescriptor requestDescriptorWithMapping:requestMapping
objectClass:EssenceRequest.class
rootKeyPath:nil
method:RKRequestMethodPOST]];
RKObjectMapping *responseMapping = EssenceRoot.objectMapping;
RKResponseDescriptor* essenceResponse = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping
method:RKRequestMethodPOST
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:essenceResponse];
EssenceRequest *dataObject = [[EssenceRequest alloc] initWithContextAndHandle:uniqueHandle essenceHandle:essenceHandle];
[objectManager postObject:dataObject
path:[NSString stringWithFormat:@"%@%@%@GetEssences", Connection.apiPrefix, Connection.svcMedia, Connection.jsonSecure]
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[serverResponseDelegate serverResponseSuccess:operation mappingResult:mappingResult ];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
[serverResponseDelegate serverResponseFailure:operation error:error];
}];
The EssenceRequest
- (id)initWithContextAndHandle:(NSString *)uniqueHandle essenceHandle:(NSString *)essenceUH;
{
self = [super init];
if (self != nil) {
_request = @{
@"__type" : @"SpecificEssenceLocationRequest:#Messaging.Media",
@"Action" : @"1",
@"ContextUH" : uniqueHandle,
@"EssenceUH" : essenceUH
};
}
return self;
}
+ (RKObjectMapping*)objectMapping
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:EssenceRequest.class];
[mapping addAttributeMappingsFromDictionary:@{
@"request": @"request"
}];
return mapping;
}
The "__type" item must be the first time in the JSON request body. Right now with it being in a dictionary it shows up later in the body when the dictionary is converted to a JSON string.
I know this is poor JSON handling on the server. They will fix it at some point and not require the __type any more but for now I need to send it as needed. I was able to do this in my Android code so I know the request will work once I have the NSString formatted.
Upvotes: 1
Views: 300
Reputation: 4507
Taking Petro's answer a step further. This solution will maintain the functionality of all other requests.
After implementation you can wrap any JSON string in a SPRawJSON
to send it as raw JSON for any request.
SPJSONSerialization.h
#import <RestKit/RestKit.h>
@interface SPRawJSON : NSObject
@property (nonatomic, readonly) NSString *json;
-(instancetype)initWithJSON:(NSString*)json;
+(RKObjectMapping*)mapping;
@end
@interface SPJSONSerialization : NSObject <RKSerialization>
@end
SPJSONSerialization.m
#import "SPJSONSerialization.h"
@implementation SPRawJSON
-(instancetype)initWithJSON:(NSString*)json
{
self = [super init];
if (self) {
_json = json;
}
return self;
}
+(RKObjectMapping*)mapping {
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[SPRawJSON class]];
[mapping addAttributeMappingsFromDictionary:@{ @"rawJSON": @"self" }];
return mapping;
}
@end
@implementation SPJSONSerialization
+ (id)objectFromData:(NSData *)data error:(NSError **)error {
return [RKNSJSONSerialization objectFromData:data error:error];
}
+ (NSData *)dataFromObject:(id)object error:(NSError **)error {
if ([object isKindOfClass:NSDictionary.class]) {
NSDictionary *dict = object;
id rawJSONObj = dict[@"rawJSON"];
if (rawJSONObj && [rawJSONObj isKindOfClass:SPRawJSON.class]) {
return [[(SPRawJSON*)rawJSONObj json] dataUsingEncoding:NSUTF8StringEncoding];
}
}
return [RKNSJSONSerialization dataFromObject:object error:error];
}
@end
Registering the mapping
RKObjectManager *objectManager = [RKObjectManager sharedManager];
// Make requests bodies be sent as JSON
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
// Add inverse mapping for the request
RKRequestDescriptor *descriptor = [RKRequestDescriptor requestDescriptorWithMapping:[SPRawJSON mapping].inverseMapping objectClass:[SPRawJSON class] rootKeyPath:nil method:RKRequestMethodPOST];
[objectManager addRequestDescriptor:descriptor];
Registering the JSON Serializer
// Replace standard JSON Serializer with our custom one that accepts raw json strings as well (SPRawJSON)
let currentJSONSerializer = RKMIMETypeSerialization.serializationClass(forMIMEType: RKMIMETypeJSON)
RKMIMETypeSerialization.unregisterClass(currentJSONSerializer)
RKMIMETypeSerialization.registerClass(SPJSONSerialization.self, forMIMEType: RKMIMETypeJSON)
Example code for sending request
NSString *myJSON = @"{\"exampleKey\": \"Example value\"}";
SPRawJSON *rawJSON = [[SPRawJSON alloc] initWithJSON:myJSON];
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager postObject:rawJSON path:@"foo/bar" parameters:nil success: ... failure: ...]
Notice that the mapping only maps POST
requests, so if you want it to work for PUT
, etc, you need to map that as well.
Upvotes: 0
Reputation: 4027
Disclaimer: following answer is just my own opinion / suggestion.
Use +[RKMimeTypeSerialization unregisterClass:[RKNSJSONSerialization class]]
to unregister default RestKit json serialization class. Then write your own class with "hacked" keys order. Register it through +[RKMimeTypeSerialization registerClass:[RKMYJSONSerialization class] forMINEType:RKMIMETypeJSON]
This way your won't change any API's - just "inject" your code into serialization/deserialization mechanism (and this is what you actually need).
The default implementation of RKNSJSONSerialization is quite simple:
+ (id)objectFromData:(NSData *)data error:(NSError **)error
{
return [NSJSONSerialization JSONObjectWithData:data options:0 error:error];
}
+ (NSData *)dataFromObject:(id)object error:(NSError **)error
{
return [NSJSONSerialization dataWithJSONObject:object options:0 error:error];
}
I think, you can go further yourself and write your own, based, of course on NSJSONSerialization or some another JSON serialization mechanism.
Upvotes: 1