Skip to content

Commit 02eab22

Browse files
committed
Add initial encode / writer support.
1 parent 8f6ca7e commit 02eab22

2 files changed

Lines changed: 326 additions & 1 deletion

File tree

JSONKit.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
#include <AvailabilityMacros.h>
4444

4545
#ifdef __OBJC__
46+
#import <Foundation/NSArray.h>
47+
#import <Foundation/NSData.h>
48+
#import <Foundation/NSDictionary.h>
4649
#import <Foundation/NSError.h>
4750
#import <Foundation/NSObjCRuntime.h>
4851
#import <Foundation/NSString.h>
@@ -203,6 +206,16 @@ typedef struct {
203206
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
204207
@end
205208

209+
@interface NSArray (JSONKit)
210+
- (NSData *)JSONData;
211+
- (NSString *)JSONString;
212+
@end
213+
214+
@interface NSDictionary (JSONKit)
215+
- (NSData *)JSONData;
216+
- (NSString *)JSONString;
217+
@end
218+
206219
#endif // __OBJC__
207220

208221
#endif // _JSONKIT_H_

JSONKit.m

Lines changed: 313 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ The code in isValidCodePoint() is derived from the ICU code in
8686
#include <string.h>
8787
#include <assert.h>
8888
#include <sys/errno.h>
89-
89+
#include <math.h>
9090

9191
#import "JSONKit.h"
9292

@@ -97,8 +97,12 @@ The code in isValidCodePoint() is derived from the ICU code in
9797
#include <CoreFoundation/CFNumber.h>
9898

9999
//#import <Foundation/Foundation.h>
100+
#import <Foundation/NSArray.h>
101+
#import <Foundation/NSData.h>
100102
#import <Foundation/NSDictionary.h>
101103
#import <Foundation/NSException.h>
104+
#import <Foundation/NSNull.h>
105+
#import <Foundation/NSScriptClassDescription.h>
102106

103107
// For DJB hash.
104108
#define JK_HASH_INIT (5381UL)
@@ -120,6 +124,9 @@ The code in isValidCodePoint() is derived from the ICU code in
120124
// JK_STACK_OBJS is the default number of spaces reserved on the stack for temporarily storing pointers to Obj-C objects before they can be transfered to a NSArray / NSDictionary.
121125
#define JK_STACK_OBJS (1024UL * 1UL)
122126

127+
#define JK_JSONBUFFER_SIZE (1024UL * 64UL)
128+
#define JK_UTF8BUFFER_SIZE (1024UL * 16UL)
129+
123130

124131

125132
#if defined (__GNUC__) && (__GNUC__ >= 4)
@@ -1388,3 +1395,308 @@ - (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags
13881395
}
13891396

13901397
@end
1398+
1399+
typedef struct {
1400+
void *stringClass;
1401+
void *numberClass;
1402+
void *arrayClass;
1403+
void *dictionaryClass;
1404+
void *nullClass;
1405+
} JKFastClassLookup;
1406+
1407+
enum {
1408+
JKClassUnknown = 0,
1409+
JKClassString = 1,
1410+
JKClassNumber = 2,
1411+
JKClassArray = 3,
1412+
JKClassDictionary= 4,
1413+
JKClassNull = 5,
1414+
};
1415+
1416+
typedef struct {
1417+
JKManagedBuffer utf8ConversionBuffer;
1418+
JKManagedBuffer stringBuffer;
1419+
size_t atIndex;
1420+
JKFastClassLookup fastClassLookup;
1421+
} JKEncodeState;
1422+
1423+
static int jk_printf(JKEncodeState *encodeState, const char *format, ...) {
1424+
va_list varArgsList;
1425+
va_start(varArgsList, format);
1426+
va_end(varArgsList);
1427+
1428+
if(encodeState->stringBuffer.bytes.length < encodeState->atIndex) { return(1); }
1429+
if((encodeState->stringBuffer.bytes.length - encodeState->atIndex) < 1024L) { if(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + 4096UL) == NULL) { return(1); } }
1430+
1431+
char *atPtr = (char *)encodeState->stringBuffer.bytes.ptr + encodeState->atIndex;
1432+
ssize_t remaining = encodeState->stringBuffer.bytes.length - encodeState->atIndex;
1433+
1434+
int printfAdded = vsnprintf(atPtr, remaining, format, varArgsList);
1435+
1436+
if(printfAdded > remaining) {
1437+
if(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + printfAdded + 1024UL) == NULL) { return(1); }
1438+
vsnprintf(atPtr, remaining, format, varArgsList);
1439+
}
1440+
1441+
encodeState->atIndex += printfAdded;
1442+
return(0);
1443+
}
1444+
1445+
static int jk_write(JKEncodeState *encodeState, const char *format) {
1446+
if(encodeState->stringBuffer.bytes.length < encodeState->atIndex) { return(1); }
1447+
if((encodeState->stringBuffer.bytes.length - encodeState->atIndex) < 1024L) { if(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + 4096UL) == NULL) { return(1); } }
1448+
1449+
char *atPtr = (char *)encodeState->stringBuffer.bytes.ptr + encodeState->atIndex;
1450+
ssize_t remaining = encodeState->stringBuffer.bytes.length - encodeState->atIndex;
1451+
1452+
ssize_t idx = 0L, added = 0L;
1453+
for(added = 0L, idx = 0L; format[added] != 0; added++) { if(idx < remaining) { atPtr[idx++] = format[added]; } }
1454+
1455+
if(added > remaining) {
1456+
if(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + added + 1024UL) == NULL) { return(1); }
1457+
for(added = 0L, idx = 0L; format[added] != 0; added++) { if(idx < remaining) { atPtr[idx++] = format[added]; } }
1458+
}
1459+
1460+
atPtr[idx] = 0;
1461+
encodeState->atIndex += added;
1462+
return(0);
1463+
}
1464+
1465+
1466+
1467+
static int jk_add_atom_to_buffer(JKEncodeState *encodeState, void *objectPtr) {
1468+
NSCParameterAssert((encodeState != NULL) && (objectPtr != NULL));
1469+
NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length);
1470+
1471+
id object = objectPtr;
1472+
1473+
if(((encodeState->atIndex + 256UL) > encodeState->stringBuffer.bytes.length) && (jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + 256UL) == NULL)) { return(1); }
1474+
1475+
int isClass = JKClassUnknown;
1476+
1477+
if(object->isa == encodeState->fastClassLookup.stringClass) { isClass = JKClassString; }
1478+
else if(object->isa == encodeState->fastClassLookup.numberClass) { isClass = JKClassNumber; }
1479+
else if(object->isa == encodeState->fastClassLookup.arrayClass) { isClass = JKClassArray; }
1480+
else if(object->isa == encodeState->fastClassLookup.dictionaryClass) { isClass = JKClassDictionary; }
1481+
else if(object->isa == encodeState->fastClassLookup.nullClass) { isClass = JKClassNull; }
1482+
else {
1483+
if([object isKindOfClass:[NSString class]]) { encodeState->fastClassLookup.stringClass = object->isa; isClass = JKClassString; }
1484+
else if([object isKindOfClass:[NSNumber class]]) { encodeState->fastClassLookup.numberClass = object->isa; isClass = JKClassNumber; }
1485+
else if([object isKindOfClass:[NSArray class]]) { encodeState->fastClassLookup.arrayClass = object->isa; isClass = JKClassArray; }
1486+
else if([object isKindOfClass:[NSDictionary class]]) { encodeState->fastClassLookup.dictionaryClass = object->isa; isClass = JKClassDictionary; }
1487+
else if([object isKindOfClass:[NSNull class]]) { encodeState->fastClassLookup.nullClass = object->isa; isClass = JKClassNull; }
1488+
}
1489+
1490+
switch(isClass) {
1491+
case JKClassString:
1492+
{
1493+
{
1494+
const unsigned char *cStringPtr = (const unsigned char *)CFStringGetCStringPtr((CFStringRef)object, kCFStringEncodingMacRoman);
1495+
if(cStringPtr != NULL) {
1496+
unsigned char *atPtr = encodeState->stringBuffer.bytes.ptr + encodeState->atIndex;
1497+
const unsigned char *utf8String = cStringPtr;
1498+
1499+
size_t utf8Idx = 0UL, added = 0UL;
1500+
atPtr[added++] = '\"';
1501+
for(utf8Idx = 0UL; utf8String[utf8Idx] != 0; utf8Idx++) {
1502+
NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex + added]) - encodeState->stringBuffer.bytes.ptr) < encodeState->stringBuffer.bytes.length);
1503+
NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length);
1504+
if(((encodeState->atIndex + added + 256UL) > encodeState->stringBuffer.bytes.length)) {
1505+
if((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + added + 1024UL) == NULL)) { return(1); }
1506+
atPtr = encodeState->stringBuffer.bytes.ptr + encodeState->atIndex;
1507+
}
1508+
if(utf8String[utf8Idx] >= 0x80) { goto slowUTF8Path; }
1509+
if(utf8String[utf8Idx] < 0x20) {
1510+
switch(utf8String[utf8Idx]) {
1511+
case '\b': atPtr[added++] = '\\'; atPtr[added++] = 'b'; break;
1512+
case '\f': atPtr[added++] = '\\'; atPtr[added++] = 'f'; break;
1513+
case '\n': atPtr[added++] = '\\'; atPtr[added++] = 'n'; break;
1514+
case '\r': atPtr[added++] = '\\'; atPtr[added++] = 'r'; break;
1515+
case '\t': atPtr[added++] = '\\'; atPtr[added++] = 't'; break;
1516+
default: jk_printf(encodeState, "\\u%4.4x", utf8String[utf8Idx]); break;
1517+
}
1518+
} else {
1519+
if((utf8String[utf8Idx] == '\"') || (utf8String[utf8Idx] == '\\')) { atPtr[added++] = '\\'; }
1520+
atPtr[added++] = utf8String[utf8Idx];
1521+
}
1522+
}
1523+
atPtr[added++] = '\"';
1524+
atPtr[added] = 0;
1525+
encodeState->atIndex += added;
1526+
return(0);
1527+
}
1528+
}
1529+
1530+
slowUTF8Path:
1531+
{
1532+
CFIndex stringLength = CFStringGetLength((CFStringRef)object);
1533+
CFIndex maxStringUTF8Length = CFStringGetMaximumSizeForEncoding(stringLength, kCFStringEncodingUTF8) + 32L;
1534+
1535+
if(((size_t)maxStringUTF8Length > encodeState->utf8ConversionBuffer.bytes.length) && (jk_managedBuffer_resize(&encodeState->utf8ConversionBuffer, maxStringUTF8Length + 1024UL) == NULL)) { return(1); }
1536+
1537+
CFIndex usedBytes = 0L, convertedCount = 0L;
1538+
convertedCount = CFStringGetBytes((CFStringRef)object, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, encodeState->utf8ConversionBuffer.bytes.ptr, encodeState->utf8ConversionBuffer.bytes.length - 16L, &usedBytes);
1539+
encodeState->utf8ConversionBuffer.bytes.ptr[usedBytes] = 0;
1540+
1541+
if(((encodeState->atIndex + maxStringUTF8Length) > encodeState->stringBuffer.bytes.length) && (jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + maxStringUTF8Length + 1024UL) == NULL)) { return(1); }
1542+
1543+
unsigned char *atPtr = encodeState->stringBuffer.bytes.ptr + encodeState->atIndex;
1544+
const unsigned char *utf8String = encodeState->utf8ConversionBuffer.bytes.ptr;
1545+
1546+
size_t utf8Idx = 0UL, added = 0UL;
1547+
atPtr[added++] = '\"';
1548+
for(utf8Idx = 0UL; utf8String[utf8Idx] != 0; utf8Idx++) {
1549+
NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex + added]) - encodeState->stringBuffer.bytes.ptr) < encodeState->stringBuffer.bytes.length);
1550+
NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length);
1551+
NSCParameterAssert((CFIndex)utf8Idx < usedBytes);
1552+
if(((encodeState->atIndex + added + 256UL) > encodeState->stringBuffer.bytes.length)) {
1553+
if((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + added + 1024UL) == NULL)) { return(1); }
1554+
atPtr = encodeState->stringBuffer.bytes.ptr + encodeState->atIndex;
1555+
}
1556+
if(utf8String[utf8Idx] < 0x20) {
1557+
switch(utf8String[utf8Idx]) {
1558+
case '\b': atPtr[added++] = '\\'; atPtr[added++] = 'b'; break;
1559+
case '\f': atPtr[added++] = '\\'; atPtr[added++] = 'f'; break;
1560+
case '\n': atPtr[added++] = '\\'; atPtr[added++] = 'n'; break;
1561+
case '\r': atPtr[added++] = '\\'; atPtr[added++] = 'r'; break;
1562+
case '\t': atPtr[added++] = '\\'; atPtr[added++] = 't'; break;
1563+
default: jk_printf(encodeState, "\\u%4.4x", utf8String[utf8Idx]); break;
1564+
}
1565+
} else {
1566+
if((utf8String[utf8Idx] == '\"') || (utf8String[utf8Idx] == '\\')) { atPtr[added++] = '\\'; }
1567+
atPtr[added++] = utf8String[utf8Idx];
1568+
}
1569+
}
1570+
atPtr[added++] = '\"';
1571+
atPtr[added] = 0;
1572+
encodeState->atIndex += added;
1573+
return(0);
1574+
}
1575+
}
1576+
break;
1577+
1578+
case JKClassNumber:
1579+
{
1580+
if(object == (id)kCFBooleanTrue) { return(jk_write(encodeState, "true")); break; } else if(object == (id)kCFBooleanFalse) { return(jk_write(encodeState, "false")); break; }
1581+
1582+
long long llv;
1583+
unsigned long long ullv;
1584+
1585+
const char *objCType = [object objCType];
1586+
switch(objCType[0]) {
1587+
case 'B':
1588+
case 'c':
1589+
if(object == (id)kCFBooleanTrue) { return(jk_write(encodeState, "true")); }
1590+
else if(object == (id)kCFBooleanFalse) { return(jk_write(encodeState, "false")); }
1591+
else { if(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &llv)) { return(jk_printf(encodeState, "%lld", llv)); } else { return(1); } }
1592+
break;
1593+
case 'i': case 's': case 'l': case 'q': if(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &llv)) { return(jk_printf(encodeState, "%lld", llv)); } else { return(1); } break;
1594+
case 'C': case 'I': case 'S': case 'L': case 'Q': if(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &ullv)) { return(jk_printf(encodeState, "%llu", ullv)); } else { return(1); } break;
1595+
1596+
case 'f': case 'd':
1597+
{
1598+
double dv;
1599+
if(CFNumberGetValue((CFNumberRef)object, kCFNumberDoubleType, &dv)) {
1600+
if(!isfinite(dv)) { return(1); }
1601+
return(jk_printf(encodeState, "%.16g", dv));
1602+
}
1603+
}
1604+
break;
1605+
default: jk_printf(encodeState, "/* NSNumber conversion error. Type: '%c' / 0x%2.2x */", objCType[0], objCType[0]); return(1); break;
1606+
}
1607+
}
1608+
break;
1609+
1610+
case JKClassArray:
1611+
{
1612+
int printComma = 0;
1613+
CFIndex arrayCount = CFArrayGetCount((CFArrayRef)object), idx = 0L;
1614+
if(jk_write(encodeState, "[")) { return(1); }
1615+
if(arrayCount > 256L) {
1616+
for(id arrayObject in object) { if(printComma) { if(jk_write(encodeState, ",")) { return(1); } } printComma = 1; if(jk_add_atom_to_buffer(encodeState, arrayObject)) { return(1); } }
1617+
} else {
1618+
void *objects[256];
1619+
CFArrayGetValues((CFArrayRef)object, CFRangeMake(0L, arrayCount), (const void **)objects);
1620+
for(idx = 0L; idx < arrayCount; idx++) { if(printComma) { if(jk_write(encodeState, ",")) { return(1); } } printComma = 1; if(jk_add_atom_to_buffer(encodeState, objects[idx])) { return(1); } }
1621+
}
1622+
if(jk_write(encodeState, "]")) { return(1); }
1623+
}
1624+
break;
1625+
case JKClassDictionary:
1626+
{
1627+
int printComma = 0;
1628+
CFIndex dictionaryCount = CFDictionaryGetCount((CFDictionaryRef)object), idx = 0L;
1629+
1630+
if(jk_write(encodeState, "{")) { return(1); }
1631+
if(dictionaryCount > 256L) {
1632+
for(id keyObject in object) { if(printComma) { if(jk_write(encodeState, ",")) { return(1); } } printComma = 1; if(jk_add_atom_to_buffer(encodeState, keyObject)) { return(1); } if(jk_write(encodeState, ":")) { return(1); } if(jk_add_atom_to_buffer(encodeState, [object objectForKey:keyObject])) { return(1); } }
1633+
} else {
1634+
void *keys[256], *objects[256];
1635+
CFDictionaryGetKeysAndValues((CFDictionaryRef)object, (const void **)keys, (const void **)objects);
1636+
for(idx = 0L; idx < dictionaryCount; idx++) { if(printComma) { if(jk_write(encodeState, ",")) { return(1); } } printComma = 1; if(jk_add_atom_to_buffer(encodeState, keys[idx])) { return(1); } if(jk_write(encodeState, ":")) { return(1); } if(jk_add_atom_to_buffer(encodeState, objects[idx])) { return(1); } }
1637+
}
1638+
if(jk_write(encodeState, "}")) { return(1); }
1639+
}
1640+
break;
1641+
1642+
case JKClassNull: if(jk_write(encodeState, "null")) { return(1); } break;
1643+
1644+
default: jk_printf(encodeState, "/* Unable to convert class type of '%s' */", [[object className] UTF8String]); return(1); break;
1645+
}
1646+
1647+
1648+
return(0);
1649+
}
1650+
1651+
1652+
static NSData *jk_encode(void *object) {
1653+
JKEncodeState encodeState;
1654+
memset(&encodeState, 0, sizeof(JKEncodeState));
1655+
1656+
encodeState.stringBuffer.roundSizeUpToMultipleOf = (1024UL * 32UL);
1657+
encodeState.utf8ConversionBuffer.roundSizeUpToMultipleOf = 4096UL;
1658+
1659+
unsigned char stackJSONBuffer[JK_JSONBUFFER_SIZE] JK_ALIGNED(64);
1660+
jk_managedBuffer_setToStackBuffer(&encodeState.stringBuffer, stackJSONBuffer, sizeof(stackJSONBuffer));
1661+
1662+
unsigned char stackUTF8Buffer[JK_UTF8BUFFER_SIZE] JK_ALIGNED(64);
1663+
jk_managedBuffer_setToStackBuffer(&encodeState.utf8ConversionBuffer, stackUTF8Buffer, sizeof(stackUTF8Buffer));
1664+
1665+
NSData *jsonData = NULL;
1666+
if(jk_add_atom_to_buffer(&encodeState, object) == 0) { jsonData = [NSData dataWithBytes:encodeState.stringBuffer.bytes.ptr length:encodeState.atIndex]; }
1667+
1668+
jk_managedBuffer_release(&encodeState.stringBuffer);
1669+
jk_managedBuffer_release(&encodeState.utf8ConversionBuffer);
1670+
1671+
return(jsonData);
1672+
}
1673+
1674+
1675+
@implementation NSArray (JSONKit)
1676+
1677+
- (NSData *)JSONData
1678+
{
1679+
return(jk_encode(self));
1680+
}
1681+
1682+
- (NSString *)JSONString
1683+
{
1684+
return([[[NSString alloc] initWithData:[self JSONData] encoding:NSUTF8StringEncoding] autorelease]);
1685+
}
1686+
1687+
@end
1688+
1689+
@implementation NSDictionary (JSONKit)
1690+
1691+
1692+
- (NSData *)JSONData
1693+
{
1694+
return(jk_encode(self));
1695+
}
1696+
1697+
- (NSString *)JSONString
1698+
{
1699+
return([[[NSString alloc] initWithData:[self JSONData] encoding:NSUTF8StringEncoding] autorelease]);
1700+
}
1701+
1702+
@end

0 commit comments

Comments
 (0)