Quick and dirty JSON validation in Cocoa

I revamped the UI for advanced MathJax configuration for the upcoming Marked 2.5 update I mentioned recently. The configuration is simply a field where you can input additional inline configuration options using the JSON syntax you’d find in a script tag in an HTML document.

When writing JSON in a text field without the benefit of syntax highlighting and linters, though, it’s easy to make a mistake, and Marked would only let you know that when refreshing a document and attempting to implement the configuration during load. I wanted it to be able to provide immediate feedback.

Cocoa provides some decent JSON parsing libraries, but all I needed to know was whether it was valid syntax for a JavaScript engine to parse. I ended up using JavaScriptCore to quickly allocate and validate the string. It works quite well, though I haven’t benchmarked it very thoroughly.

This below code is from an NSTextView subclass. It takes the field’s current string, trims it and removes newlines, then attempts to run a JSON.stringify() in a JavaScriptCore context.

- (void)didChangeText
{
  [super didChangeText];

  NSColor *errColor = [NSColor colorWithCalibratedRed:0.445
                                                green:0.009
                                                 blue:0.027
                                                alpha:1.000];
  NSColor *okColor = [NSColor blackColor];

  NSString *input = [[[self string] straightenQuotes] trim];
  input = [input replace:RX(@"[\\n\\r]+") with:@""];
  if (![input isMatch:RX(@"^\\{.*?\\}$")])
  {
    input = [NSString stringWithFormat:@"{%@}",
                                       input];
  }

  NSString *scriptString = [NSString stringWithFormat:@"JSON.stringify(%@)",
                                                      input];
  JSGlobalContextRef jsx = JSGlobalContextCreate(NULL);
  JSStringRef scriptJS = JSStringCreateWithCFString(
                                      (CFStringRef)CFStringCreateWithCString(
                                        kCFAllocatorDefault,
                                        scriptString.UTF8String,
                                        kCFStringEncodingUTF8
                                      )
                                    );

  BOOL valid = JSCheckScriptSyntax(jsx, scriptJS, NULL, 0, NULL);

  JSStringRelease(scriptJS);

  if (!valid)
      [self setTextColor:errColor];
  else
      [self setTextColor:okColor];
}

For reference, the RX() methods come from Objective-C Regex Categories by Josh Wright. They are very handy shortcuts for the NSRegularExpression syntax1. Also note that it calls a method called “straightenQuotes”, which is from an NSString category and simply dumbs down smart quotes:

- (NSString *)straightenQuotes
{
	NSString *input = [self replace:RX(@"[“”]") with:@"\""];
	input = [input replace:RX(@"[‘’]") with:@"'"];
	return input;
}

This is because even though the text field disables smart quotes, system settings can override it, and users can paste them into the field, which can be a hard bug to track down in customer support.

If this is useful to you, great. If you see any major issues with the choice or in the implementation, I welcome the input.

Here’s a good (though aging) intro to JavaScriptCore.

  1. Yes, I should be working toward Swift, but this codebase is quite well grounded in Obj-C, and I’m easily intimidated by large undertakings that will break things that are working.

Brett Terpstra

Brett is a writer and developer living in Minnesota, USA. You can follow him as ttscoff on Twitter, GitHub, and Mastodon. Keep up with this blog by subscribing in your favorite news reader.

This content is supported by readers like you.

Join the conversation