Kevin
Kevin

Reputation: 1443

iOS RestKit POST unmodified JSON string

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

Answers (2)

Vegard
Vegard

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

Petro Korienev
Petro Korienev

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

Related Questions