Cocoa: How to save NSAttributedString to JSON Cocoa: How to save NSAttributedString to JSON json json

Cocoa: How to save NSAttributedString to JSON


NSAttributedString has two properties:

  • the string
  • an array of attribute "runs"

Each "run" has:

  • an integer range that it applies to
  • a dictionary of key/value attributes

It would be very easy to represent that as JSON, using enumerateAttributesInRange:options:usingBlock:.

Something like:

{  "string" : "Hello World",  "runs" : [    {      "range" : [0,3],      "attributes" : {        "font" : {          "name" : "Arial",          "size" : 12        }      }    },    {      "range" : [3,6],      "attributes" : {        "font" : {          "name" : "Arial",          "size" : 12        },        "color" : [255,0,0]      }    },    {      "range" : [9,2],      "attributes" : {        "font" : {          "name" : "Arial",          "size" : 12        }      }    }  ]}

EDIT: here's an example implementation:

// create a basic attributed stringNSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:@"Hello World" attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Arial" size:12]}];[attStr addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:NSMakeRange(3, 6)];// build array of attribute runsNSMutableArray *attributeRuns = [NSMutableArray array];[attStr enumerateAttributesInRange:NSMakeRange(0, attStr.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {  NSArray *rangeArray = @[[NSNumber numberWithUnsignedInteger:range.location],                          [NSNumber numberWithUnsignedInteger:range.length]];  NSMutableDictionary *runAttributes = [NSMutableDictionary dictionary];  [attrs enumerateKeysAndObjectsUsingBlock:^(id attributeName, id attributeValue, BOOL *stop) {    if ([attributeName isEqual:NSFontAttributeName]) { // convert font values into a dictionary with the name and size      attributeName = @"font";      attributeValue = @{@"name": [(NSFont *)attributeValue displayName],                         @"size": [NSNumber numberWithFloat:[(NSFont *)attributeValue pointSize]]};    } else if ([attributeName isEqualToString:NSForegroundColorAttributeName]) { // convert foreground colour values into an array with red/green/blue as a number from 0 to 255      attributeName = @"color";      attributeValue = @[[NSNumber numberWithInteger:([(NSColor *)attributeValue redComponent] * 255)],                         [NSNumber numberWithInteger:([(NSColor *)attributeValue greenComponent] * 255)],                         [NSNumber numberWithInteger:([(NSColor *)attributeValue blueComponent] * 255)]];    } else { // skip unknown attributes      NSLog(@"skipping unknown attribute %@", attributeName);      return;    }    [runAttributes setObject:attributeValue forKey:attributeName];  }];  // save the attributes (if there are any)  if (runAttributes.count == 0)    return;  [attributeRuns addObject:@{@"range": rangeArray,                             @"attributes": runAttributes}];}];// build JSON outputNSDictionary *jsonOutput = @{@"string": attStr.string,                             @"runs": attributeRuns};NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonOutput options:NSJSONWritingPrettyPrinted error:NULL];NSLog(@"%@", [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]);exit(0);


You could try starting with RTFFromRange:

From the documentation: For information about the OS X methods supporting RTF, ..., see NSAttributedString Application Kit Additions Reference.

RTF should be self contained. RTFFromRange: returns NSData; I would think its probably character data in some encoding so should be easy to convert to NSString.

(Sorry, just read that method is MacOS X only).


You could use this simple code snippet to convert NSAttributedString to XML without actually parsing NSAttributedString. This can be a human-readable alternative to JSON if you can afford verbose text output.

It can be also used for decoding back to NSAttributedString.

    let data = NSMutableData()    let archiver = NSKeyedArchiver(forWritingWithMutableData: data)    archiver.outputFormat = .XMLFormat_v1_0    textView.attributedText.encodeWithCoder(archiver)    archiver.finishEncoding()    let textAsString = NSString(data: data, encoding: NSUTF8StringEncoding)'