2

I'm using the a WebScriptObject to call JavaScript methods in a WebView in Objective-C (OS X). I want to send and receive object like arrays and dictionaries.

I can receive them like this:

// JWJSBridge
+ (BOOL)isSelectorExpludedFromWebScript:(SEL)selector {
    if (selector == @selector(log:)) {
        return NO;
    }
    return YES;
}
+ (NSString *)webScriptNameForSelector:(SEL)selector {
    if (selector == @selector(log:)) {
        return @"log";
    }
    return nil;
}
- (void)log:(WebScriptObject *)object {
    NSLog(@"object: %@", [[object JSValue] toObject]);
}

This object is set to the JavaScript environment as follows:

WebScriptObject *windowObject = [[self webView] windowScriptObject];
[windowObject setValue:[[JWJSBridge alloc] init] forKey:@"external"];

When I then make a JavaScript call like window.external.log({key: "value"}); my JWJSBridge logs the object as NSDictionary.

Now I also want to implement it the other way. For that I created a JavaScript object like this:

window.internal = {log = function(a) { console.log(a); }};

It works perfectly with arrays:

WebScriptObject *internal = [[self webView] valueForKey:@"internal"];
[internal callWebScriptMethod:@"log" withArguments:@[@[@"value", @"value"]]];

But when I want to send a dictionary, it is not possible:

[internal callWebScriptMethod:@"log" withArgument:@[@{@"key": @"value"}]];

Know I unfortunately end up with a console message with an empty object ObjCRuntimeObject. Apparently Objective C does not / cannot serialize dictionaries to JavaScript objects. The little chunk of documentation I could find (I didn't find it again for reference here) tells me that it just works with arrays.

I filed a bug report to apple: 19464522

Why does the Objective-C API provides methods for turning everything into object form JavaScript but not vice-versa?!

There must be a possibility, so how do I achieve this?

Julian F. Weinert
  • 7,474
  • 7
  • 59
  • 107

1 Answers1

0

Been scratching my head over the best way to work around this limitation as well, tried a few class based implementations without much success, and ended up with a workaround that boils down to this:

id dict = [internal evaluateWebScript:@"(function() { return { key: { key: 'VALUE' } }; })()"];
[internal callWebScriptMethod:@"log" withArguments:@[dict]];

Which can be wrapped up like this (leaving out error handling for brevity):

static id jsDictionary(WebScriptObject *const webScript, NSDictionary *const dictionary)
{
  NSData *const data = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:nil];
  NSString *const json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  return [webScript evaluateWebScript:[NSString stringWithFormat:@"(function() { return %@; })()", json]];
}

And then used like this:

[internal callWebScriptMethod:@"log" withArguments:@[jsDictionary(internal, @{ @"key": @"VALUE" })]];
Chris
  • 1,825
  • 1
  • 12
  • 12