Index: src/OFArray.m ================================================================== --- src/OFArray.m +++ src/OFArray.m @@ -36,10 +36,15 @@ #import "macros.h" static struct { Class isa; } placeholder; + +@interface OFArray (OF_PRIVATE_CATEGORY) +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth; +@end @interface OFArray_placeholder: OFArray @end @implementation OFArray_placeholder @@ -542,25 +547,78 @@ return [element autorelease]; } - (OFString*)JSONRepresentation { + return [self OF_JSONRepresentationWithOptions: 0 + depth: 0]; +} + +- (OFString*)JSONRepresentationWithOptions: (int)options +{ + return [self OF_JSONRepresentationWithOptions: options + depth: 0]; +} + +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth +{ + OFMutableString *JSON = [OFMutableString stringWithString: @"["]; void *pool = objc_autoreleasePoolPush(); - OFMutableString *JSON; - - JSON = [[self componentsJoinedByString: @"," - usingSelector: @selector(JSONRepresentation)] - mutableCopy]; - - [JSON prependString: @"["]; - [JSON appendString: @"]"]; - + OFEnumerator *enumerator = [self objectEnumerator]; + id object; + size_t i, count = [self count]; + + if (options & OF_JSON_REPRESENTATION_PRETTY) { + OFMutableString *indentation = [OFMutableString string]; + + for (i = 0; i < depth; i++) + [indentation appendString: @"\t"]; + + [JSON appendString: @"\n"]; + + i = 0; + while ((object = [enumerator nextObject]) != nil) { + void *pool2 = objc_autoreleasePoolPush(); + + [JSON appendString: indentation]; + [JSON appendString: @"\t"]; + [JSON appendString: [object + OF_JSONRepresentationWithOptions: options + depth: depth + 1]]; + + if (++i < count) + [JSON appendString: @",\n"]; + else + [JSON appendString: @"\n"]; + + objc_autoreleasePoolPop(pool2); + } + + [JSON appendString: indentation]; + } else { + i = 0; + while ((object = [enumerator nextObject]) != nil) { + void *pool2 = objc_autoreleasePoolPush(); + + [JSON appendString: [object + OF_JSONRepresentationWithOptions: options + depth: depth + 1]]; + + if (++i < count) + [JSON appendString: @","]; + + objc_autoreleasePoolPop(pool2); + } + } + + [JSON appendString: @"]"]; [JSON makeImmutable]; objc_autoreleasePoolPop(pool); - return [JSON autorelease]; + return JSON; } - (OFDataArray*)messagePackRepresentation { OFDataArray *data; Index: src/OFDictionary.m ================================================================== --- src/OFDictionary.m +++ src/OFDictionary.m @@ -34,10 +34,15 @@ #import "macros.h" static struct { Class isa; } placeholder; + +@interface OFDictionary (OF_PRIVATE_CATEGORY) +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth; +@end @interface OFDictionary_placeholder: OFDictionary @end @implementation OFDictionary_placeholder @@ -577,31 +582,82 @@ return [element autorelease]; } - (OFString*)JSONRepresentation +{ + return [self OF_JSONRepresentationWithOptions: 0 + depth: 0]; +} + +- (OFString*)JSONRepresentationWithOptions: (int)options +{ + return [self OF_JSONRepresentationWithOptions: options + depth: 0]; +} + +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth { OFMutableString *JSON = [OFMutableString stringWithString: @"{"]; void *pool = objc_autoreleasePoolPush(); OFEnumerator *keyEnumerator = [self keyEnumerator]; OFEnumerator *objectEnumerator = [self objectEnumerator]; - size_t i = 0, count = [self count]; - OFString *key; - OFString *object; - - while ((key = [keyEnumerator nextObject]) != nil && - (object = [objectEnumerator nextObject]) != nil) { - void *pool2 = objc_autoreleasePoolPush(); - - [JSON appendString: [key JSONRepresentation]]; - [JSON appendString: @":"]; - [JSON appendString: [object JSONRepresentation]]; - - if (++i < count) - [JSON appendString: @","]; - - objc_autoreleasePoolPop(pool2); + size_t i, count = [self count]; + id key, object; + + if (options & OF_JSON_REPRESENTATION_PRETTY) { + OFMutableString *indentation = [OFMutableString string]; + + for (i = 0; i < depth; i++) + [indentation appendString: @"\t"]; + + [JSON appendString: @"\n"]; + + i = 0; + while ((key = [keyEnumerator nextObject]) != nil && + (object = [objectEnumerator nextObject]) != nil) { + void *pool2 = objc_autoreleasePoolPush(); + + [JSON appendString: indentation]; + [JSON appendString: @"\t"]; + [JSON appendString: [key + OF_JSONRepresentationWithOptions: options + depth: depth + 1]]; + [JSON appendString: @": "]; + [JSON appendString: [object + OF_JSONRepresentationWithOptions: options + depth: depth + 1]]; + + if (++i < count) + [JSON appendString: @",\n"]; + else + [JSON appendString: @"\n"]; + + objc_autoreleasePoolPop(pool2); + } + + [JSON appendString: indentation]; + } else { + i = 0; + while ((key = [keyEnumerator nextObject]) != nil && + (object = [objectEnumerator nextObject]) != nil) { + void *pool2 = objc_autoreleasePoolPush(); + + [JSON appendString: [key + OF_JSONRepresentationWithOptions: options + depth: depth + 1]]; + [JSON appendString: @":"]; + [JSON appendString: [object + OF_JSONRepresentationWithOptions: options + depth: depth + 1]]; + + if (++i < count) + [JSON appendString: @","]; + + objc_autoreleasePoolPop(pool2); + } } [JSON appendString: @"}"]; [JSON makeImmutable]; Index: src/OFJSONRepresentation.h ================================================================== --- src/OFJSONRepresentation.h +++ src/OFJSONRepresentation.h @@ -13,10 +13,14 @@ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ @class OFString; + +enum { + OF_JSON_REPRESENTATION_PRETTY = 1 +}; /*! * @brief A protocol implemented by classes that support encoding to a JSON * representation. * @@ -26,9 +30,22 @@ */ @protocol OFJSONRepresentation /*! * @brief Returns the JSON representation of the object as a string. * - * @return The JSON representation of the object as a string. + * @return The JSON representation of the object as a string */ - (OFString*)JSONRepresentation; + +/*! + * @brief Returns the JSON representation of the object as a string. + * + * @param options The options to use when creating a JSON representation.@n + * Possible values are: + * Value | Description + * --------------------------------|------------------------- + * `OF_JSON_REPRESENTATION_PRETTY` | Optimize for readability + * + * @return The JSON representation of the object as a string + */ +- (OFString*)JSONRepresentationWithOptions: (int)options; @end Index: src/OFNull.m ================================================================== --- src/OFNull.m +++ src/OFNull.m @@ -23,10 +23,15 @@ #import "OFInvalidArgumentException.h" #import "autorelease.h" #import "macros.h" + +@interface OFNull (OF_PRIVATE_CATEGORY) +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth; +@end static OFNull *null = nil; @implementation OFNull + (void)initialize @@ -80,10 +85,23 @@ return [element autorelease]; } - (OFString*)JSONRepresentation +{ + return [self OF_JSONRepresentationWithOptions: 0 + depth: 0]; +} + +- (OFString*)JSONRepresentationWithOptions: (int)options +{ + return [self OF_JSONRepresentationWithOptions: options + depth: 0]; +} + +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth { return @"null"; } - (OFDataArray*)messagePackRepresentation Index: src/OFNumber.m ================================================================== --- src/OFNumber.m +++ src/OFNumber.m @@ -331,10 +331,15 @@ case OF_NUMBER_DOUBLE: \ return [OFNumber numberWithDouble: _value.double_ o]; \ default: \ @throw [OFInvalidFormatException exception]; \ } + +@interface OFNumber (OF_PRIVATE_CATEGORY) +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth; +@end @implementation OFNumber + (instancetype)numberWithBool: (bool)bool_ { return [[[self alloc] initWithBool: bool_] autorelease]; @@ -1396,10 +1401,23 @@ return [element autorelease]; } - (OFString*)JSONRepresentation +{ + return [self OF_JSONRepresentationWithOptions: 0 + depth: 0]; +} + +- (OFString*)JSONRepresentationWithOptions: (int)options +{ + return [self OF_JSONRepresentationWithOptions: options + depth: 0]; +} + +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth { double doubleValue; if (_type == OF_NUMBER_BOOL) return (_value.bool_ ? @"true" : @"false"); Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -71,10 +71,12 @@ maxLength: (size_t)maxLength encoding: (of_string_encoding_t)encoding lossy: (bool)lossy; - (const char*)OF_cStringWithEncoding: (of_string_encoding_t)encoding lossy: (bool)lossy; +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth; @end extern bool of_unicode_to_iso_8859_15(const of_unichar_t*, char*, size_t, bool); extern bool of_unicode_to_windows_1252(const of_unichar_t*, char*, size_t, bool); @@ -1431,10 +1433,23 @@ return [element autorelease]; } - (OFString*)JSONRepresentation +{ + return [self OF_JSONRepresentationWithOptions: 0 + depth: 0]; +} + +- (OFString*)JSONRepresentationWithOptions: (int)options +{ + return [self OF_JSONRepresentationWithOptions: options + depth: 0]; +} + +- (OFString*)OF_JSONRepresentationWithOptions: (int)options + depth: (size_t)depth { OFMutableString *JSON = [[self mutableCopy] autorelease]; /* FIXME: This is slow! Write it in pure C! */ [JSON replaceOccurrencesOfString: @"\\" Index: tests/OFJSONTests.m ================================================================== --- tests/OFJSONTests.m +++ tests/OFJSONTests.m @@ -49,10 +49,15 @@ TEST(@"-[JSONValue #1]", [[s JSONValue] isEqual: d]) TEST(@"-[JSONRepresentation]", [[d JSONRepresentation] isEqual: @"{\"x\":[0.5,15,null,\"foo\",false],\"foo\":\"ba\\r\"}"]) + TEST(@"OF_JSON_REPRESENTATION_PRETTY", + [[d JSONRepresentationWithOptions: OF_JSON_REPRESENTATION_PRETTY] + isEqual: @"{\n\t\"x\": [\n\t\t0.5,\n\t\t15,\n\t\tnull,\n\t\t" + @"\"foo\",\n\t\tfalse\n\t],\n\t\"foo\": \"ba\\r\"\n}"]) + EXPECT_EXCEPTION(@"-[JSONValue #2]", OFInvalidJSONException, [@"{" JSONValue]) EXPECT_EXCEPTION(@"-[JSONValue #3]", OFInvalidJSONException, [@"]" JSONValue]) EXPECT_EXCEPTION(@"-[JSONValue #4]", OFInvalidJSONException,