Index: src/OFDictionary.m ================================================================== --- src/OFDictionary.m +++ src/OFDictionary.m @@ -676,14 +676,15 @@ of_JSONRepresentationWithOptions: (OFJSONRepresentationOptions)options depth: (size_t)depth { OFMutableString *JSON = [OFMutableString stringWithString: @"{"]; void *pool = objc_autoreleasePoolPush(); - OFEnumerator *keyEnumerator = [self keyEnumerator]; - OFEnumerator *objectEnumerator = [self objectEnumerator]; + OFArray *keys = self.allKeys; size_t i, count = self.count; - id key, object; + + if (options & OFJSONRepresentationOptionSorted) + keys = keys.sortedArray; if (options & OFJSONRepresentationOptionPretty) { OFMutableString *indentation = [OFMutableString string]; for (i = 0; i < depth; i++) @@ -690,13 +691,13 @@ [indentation appendString: @"\t"]; [JSON appendString: @"\n"]; i = 0; - while ((key = [keyEnumerator nextObject]) != nil && - (object = [objectEnumerator nextObject]) != nil) { + for (id key in keys) { void *pool2 = objc_autoreleasePoolPush(); + id object = [self objectForKey: key]; int identifierOptions = options | OFJSONRepresentationOptionIsIdentifier; if (![key isKindOfClass: [OFString class]]) @throw [OFInvalidArgumentException exception]; @@ -720,13 +721,13 @@ } [JSON appendString: indentation]; } else { i = 0; - while ((key = [keyEnumerator nextObject]) != nil && - (object = [objectEnumerator nextObject]) != nil) { + for (id key in keys) { void *pool2 = objc_autoreleasePoolPush(); + id object = [self objectForKey: key]; int identifierOptions = options | OFJSONRepresentationOptionIsIdentifier; if (![key isKindOfClass: [OFString class]]) @throw [OFInvalidArgumentException exception]; Index: src/OFJSONRepresentation.h ================================================================== --- src/OFJSONRepresentation.h +++ src/OFJSONRepresentation.h @@ -29,10 +29,12 @@ typedef enum { /** Optimize for readability */ OFJSONRepresentationOptionPretty = 0x01, /** Generate JSON5 */ OFJSONRepresentationOptionJSON5 = 0x02, + /** Sort keys alphabetically */ + OFJSONRepresentationOptionSorted = 0x04, OFJSONRepresentationOptionIsIdentifier = 0x10 } OFJSONRepresentationOptions; /** * @protocol OFJSONRepresentation OFJSONRepresentation.h ObjFW/ObjFW.h Index: tests/OFJSONTests.m ================================================================== --- tests/OFJSONTests.m +++ tests/OFJSONTests.m @@ -63,10 +63,19 @@ - (void)testJSONRepresentation { OTAssert(_dictionary.JSONRepresentation, @"{\"foo\":\"b\\na\\r\",\"x\":[0.5,15,null,\"foo\",false]}"); } + +- (void)testSortedJSONRepresentation +{ + OTAssertEqualObjects( + [([OFDictionary dictionaryWithKeysAndObjects: + @"b", @"a", @"a", @"b", nil]) + JSONRepresentationWithOptions: OFJSONRepresentationOptionSorted], + @"{\"a\":\"b\",\"b\":\"a\"}"); +} - (void)testPrettyJSONRepresentation { OTAssertEqualObjects([_dictionary JSONRepresentationWithOptions: OFJSONRepresentationOptionPretty],