Statistics
| Branch: | Tag: | Revision:

root / Classes / SBJsonWriter.m @ 72744ed1

History | View | Annotate | Download (7.8 kB)

1 700184fb Miltiadis Vasilakis
/*
2 700184fb Miltiadis Vasilakis
 Copyright (C) 2009 Stig Brautaset. All rights reserved.
3 700184fb Miltiadis Vasilakis
 
4 700184fb Miltiadis Vasilakis
 Redistribution and use in source and binary forms, with or without
5 700184fb Miltiadis Vasilakis
 modification, are permitted provided that the following conditions are met:
6 700184fb Miltiadis Vasilakis
 
7 700184fb Miltiadis Vasilakis
 * Redistributions of source code must retain the above copyright notice, this
8 700184fb Miltiadis Vasilakis
   list of conditions and the following disclaimer.
9 700184fb Miltiadis Vasilakis
 
10 700184fb Miltiadis Vasilakis
 * Redistributions in binary form must reproduce the above copyright notice,
11 700184fb Miltiadis Vasilakis
   this list of conditions and the following disclaimer in the documentation
12 700184fb Miltiadis Vasilakis
   and/or other materials provided with the distribution.
13 700184fb Miltiadis Vasilakis
 
14 700184fb Miltiadis Vasilakis
 * Neither the name of the author nor the names of its contributors may be used
15 700184fb Miltiadis Vasilakis
   to endorse or promote products derived from this software without specific
16 700184fb Miltiadis Vasilakis
   prior written permission.
17 700184fb Miltiadis Vasilakis
 
18 700184fb Miltiadis Vasilakis
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 700184fb Miltiadis Vasilakis
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 700184fb Miltiadis Vasilakis
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 700184fb Miltiadis Vasilakis
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22 700184fb Miltiadis Vasilakis
 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 700184fb Miltiadis Vasilakis
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 700184fb Miltiadis Vasilakis
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 700184fb Miltiadis Vasilakis
 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 700184fb Miltiadis Vasilakis
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 700184fb Miltiadis Vasilakis
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 700184fb Miltiadis Vasilakis
 */
29 700184fb Miltiadis Vasilakis
30 700184fb Miltiadis Vasilakis
#import "SBJsonWriter.h"
31 700184fb Miltiadis Vasilakis
32 700184fb Miltiadis Vasilakis
@interface SBJsonWriter ()
33 700184fb Miltiadis Vasilakis
34 700184fb Miltiadis Vasilakis
- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json;
35 700184fb Miltiadis Vasilakis
- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json;
36 700184fb Miltiadis Vasilakis
- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json;
37 700184fb Miltiadis Vasilakis
- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json;
38 700184fb Miltiadis Vasilakis
39 700184fb Miltiadis Vasilakis
- (NSString*)indent;
40 700184fb Miltiadis Vasilakis
41 700184fb Miltiadis Vasilakis
@end
42 700184fb Miltiadis Vasilakis
43 700184fb Miltiadis Vasilakis
@implementation SBJsonWriter
44 700184fb Miltiadis Vasilakis
45 700184fb Miltiadis Vasilakis
static NSMutableCharacterSet *kEscapeChars;
46 700184fb Miltiadis Vasilakis
47 700184fb Miltiadis Vasilakis
+ (void)initialize {
48 700184fb Miltiadis Vasilakis
	kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain];
49 700184fb Miltiadis Vasilakis
	[kEscapeChars addCharactersInString: @"\"\\"];
50 700184fb Miltiadis Vasilakis
}
51 700184fb Miltiadis Vasilakis
52 700184fb Miltiadis Vasilakis
53 700184fb Miltiadis Vasilakis
@synthesize sortKeys;
54 700184fb Miltiadis Vasilakis
@synthesize humanReadable;
55 700184fb Miltiadis Vasilakis
56 700184fb Miltiadis Vasilakis
/**
57 700184fb Miltiadis Vasilakis
 @deprecated This exists in order to provide fragment support in older APIs in one more version.
58 700184fb Miltiadis Vasilakis
 It should be removed in the next major version.
59 700184fb Miltiadis Vasilakis
 */
60 700184fb Miltiadis Vasilakis
- (NSString*)stringWithFragment:(id)value {
61 700184fb Miltiadis Vasilakis
    [self clearErrorTrace];
62 700184fb Miltiadis Vasilakis
    depth = 0;
63 700184fb Miltiadis Vasilakis
    NSMutableString *json = [NSMutableString stringWithCapacity:128];
64 700184fb Miltiadis Vasilakis
    
65 700184fb Miltiadis Vasilakis
    if ([self appendValue:value into:json])
66 700184fb Miltiadis Vasilakis
        return json;
67 700184fb Miltiadis Vasilakis
    
68 700184fb Miltiadis Vasilakis
    return nil;
69 700184fb Miltiadis Vasilakis
}
70 700184fb Miltiadis Vasilakis
71 700184fb Miltiadis Vasilakis
72 700184fb Miltiadis Vasilakis
- (NSString*)stringWithObject:(id)value {
73 700184fb Miltiadis Vasilakis
    
74 700184fb Miltiadis Vasilakis
    if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) {
75 700184fb Miltiadis Vasilakis
        return [self stringWithFragment:value];
76 700184fb Miltiadis Vasilakis
    }
77 700184fb Miltiadis Vasilakis
    
78 700184fb Miltiadis Vasilakis
    if ([value respondsToSelector:@selector(proxyForJson)]) {
79 700184fb Miltiadis Vasilakis
        NSString *tmp = [self stringWithObject:[value proxyForJson]];
80 700184fb Miltiadis Vasilakis
        if (tmp)
81 700184fb Miltiadis Vasilakis
            return tmp;
82 700184fb Miltiadis Vasilakis
    }
83 700184fb Miltiadis Vasilakis
        
84 700184fb Miltiadis Vasilakis
85 700184fb Miltiadis Vasilakis
    [self clearErrorTrace];
86 700184fb Miltiadis Vasilakis
    [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"];
87 700184fb Miltiadis Vasilakis
    return nil;
88 700184fb Miltiadis Vasilakis
}
89 700184fb Miltiadis Vasilakis
90 700184fb Miltiadis Vasilakis
91 700184fb Miltiadis Vasilakis
- (NSString*)indent {
92 700184fb Miltiadis Vasilakis
    return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0];
93 700184fb Miltiadis Vasilakis
}
94 700184fb Miltiadis Vasilakis
95 700184fb Miltiadis Vasilakis
- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json {
96 700184fb Miltiadis Vasilakis
    if ([fragment isKindOfClass:[NSDictionary class]]) {
97 700184fb Miltiadis Vasilakis
        if (![self appendDictionary:fragment into:json])
98 700184fb Miltiadis Vasilakis
            return NO;
99 700184fb Miltiadis Vasilakis
        
100 700184fb Miltiadis Vasilakis
    } else if ([fragment isKindOfClass:[NSArray class]]) {
101 700184fb Miltiadis Vasilakis
        if (![self appendArray:fragment into:json])
102 700184fb Miltiadis Vasilakis
            return NO;
103 700184fb Miltiadis Vasilakis
        
104 700184fb Miltiadis Vasilakis
    } else if ([fragment isKindOfClass:[NSString class]]) {
105 700184fb Miltiadis Vasilakis
        if (![self appendString:fragment into:json])
106 700184fb Miltiadis Vasilakis
            return NO;
107 700184fb Miltiadis Vasilakis
        
108 700184fb Miltiadis Vasilakis
    } else if ([fragment isKindOfClass:[NSNumber class]]) {
109 700184fb Miltiadis Vasilakis
        if ('c' == *[fragment objCType])
110 700184fb Miltiadis Vasilakis
            [json appendString:[fragment boolValue] ? @"true" : @"false"];
111 700184fb Miltiadis Vasilakis
        else
112 700184fb Miltiadis Vasilakis
            [json appendString:[fragment stringValue]];
113 700184fb Miltiadis Vasilakis
        
114 700184fb Miltiadis Vasilakis
    } else if ([fragment isKindOfClass:[NSNull class]]) {
115 700184fb Miltiadis Vasilakis
        [json appendString:@"null"];
116 700184fb Miltiadis Vasilakis
    } else if ([fragment respondsToSelector:@selector(proxyForJson)]) {
117 700184fb Miltiadis Vasilakis
        [self appendValue:[fragment proxyForJson] into:json];
118 700184fb Miltiadis Vasilakis
        
119 700184fb Miltiadis Vasilakis
    } else {
120 700184fb Miltiadis Vasilakis
        [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]];
121 700184fb Miltiadis Vasilakis
        return NO;
122 700184fb Miltiadis Vasilakis
    }
123 700184fb Miltiadis Vasilakis
    return YES;
124 700184fb Miltiadis Vasilakis
}
125 700184fb Miltiadis Vasilakis
126 700184fb Miltiadis Vasilakis
- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json {
127 700184fb Miltiadis Vasilakis
    if (maxDepth && ++depth > maxDepth) {
128 700184fb Miltiadis Vasilakis
        [self addErrorWithCode:EDEPTH description: @"Nested too deep"];
129 700184fb Miltiadis Vasilakis
        return NO;
130 700184fb Miltiadis Vasilakis
    }
131 700184fb Miltiadis Vasilakis
    [json appendString:@"["];
132 700184fb Miltiadis Vasilakis
    
133 700184fb Miltiadis Vasilakis
    BOOL addComma = NO;    
134 700184fb Miltiadis Vasilakis
    for (id value in fragment) {
135 700184fb Miltiadis Vasilakis
        if (addComma)
136 700184fb Miltiadis Vasilakis
            [json appendString:@","];
137 700184fb Miltiadis Vasilakis
        else
138 700184fb Miltiadis Vasilakis
            addComma = YES;
139 700184fb Miltiadis Vasilakis
        
140 700184fb Miltiadis Vasilakis
        if ([self humanReadable])
141 700184fb Miltiadis Vasilakis
            [json appendString:[self indent]];
142 700184fb Miltiadis Vasilakis
        
143 700184fb Miltiadis Vasilakis
        if (![self appendValue:value into:json]) {
144 700184fb Miltiadis Vasilakis
            return NO;
145 700184fb Miltiadis Vasilakis
        }
146 700184fb Miltiadis Vasilakis
    }
147 700184fb Miltiadis Vasilakis
    
148 700184fb Miltiadis Vasilakis
    depth--;
149 700184fb Miltiadis Vasilakis
    if ([self humanReadable] && [fragment count])
150 700184fb Miltiadis Vasilakis
        [json appendString:[self indent]];
151 700184fb Miltiadis Vasilakis
    [json appendString:@"]"];
152 700184fb Miltiadis Vasilakis
    return YES;
153 700184fb Miltiadis Vasilakis
}
154 700184fb Miltiadis Vasilakis
155 700184fb Miltiadis Vasilakis
- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json {
156 700184fb Miltiadis Vasilakis
    if (maxDepth && ++depth > maxDepth) {
157 700184fb Miltiadis Vasilakis
        [self addErrorWithCode:EDEPTH description: @"Nested too deep"];
158 700184fb Miltiadis Vasilakis
        return NO;
159 700184fb Miltiadis Vasilakis
    }
160 700184fb Miltiadis Vasilakis
    [json appendString:@"{"];
161 700184fb Miltiadis Vasilakis
    
162 700184fb Miltiadis Vasilakis
    NSString *colon = [self humanReadable] ? @" : " : @":";
163 700184fb Miltiadis Vasilakis
    BOOL addComma = NO;
164 700184fb Miltiadis Vasilakis
    NSArray *keys = [fragment allKeys];
165 700184fb Miltiadis Vasilakis
    if (self.sortKeys)
166 700184fb Miltiadis Vasilakis
        keys = [keys sortedArrayUsingSelector:@selector(compare:)];
167 700184fb Miltiadis Vasilakis
    
168 700184fb Miltiadis Vasilakis
    for (id value in keys) {
169 700184fb Miltiadis Vasilakis
        if (addComma)
170 700184fb Miltiadis Vasilakis
            [json appendString:@","];
171 700184fb Miltiadis Vasilakis
        else
172 700184fb Miltiadis Vasilakis
            addComma = YES;
173 700184fb Miltiadis Vasilakis
        
174 700184fb Miltiadis Vasilakis
        if ([self humanReadable])
175 700184fb Miltiadis Vasilakis
            [json appendString:[self indent]];
176 700184fb Miltiadis Vasilakis
        
177 700184fb Miltiadis Vasilakis
        if (![value isKindOfClass:[NSString class]]) {
178 700184fb Miltiadis Vasilakis
            [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"];
179 700184fb Miltiadis Vasilakis
            return NO;
180 700184fb Miltiadis Vasilakis
        }
181 700184fb Miltiadis Vasilakis
        
182 700184fb Miltiadis Vasilakis
        if (![self appendString:value into:json])
183 700184fb Miltiadis Vasilakis
            return NO;
184 700184fb Miltiadis Vasilakis
        
185 700184fb Miltiadis Vasilakis
        [json appendString:colon];
186 700184fb Miltiadis Vasilakis
        if (![self appendValue:[fragment objectForKey:value] into:json]) {
187 700184fb Miltiadis Vasilakis
            [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]];
188 700184fb Miltiadis Vasilakis
            return NO;
189 700184fb Miltiadis Vasilakis
        }
190 700184fb Miltiadis Vasilakis
    }
191 700184fb Miltiadis Vasilakis
    
192 700184fb Miltiadis Vasilakis
    depth--;
193 700184fb Miltiadis Vasilakis
    if ([self humanReadable] && [fragment count])
194 700184fb Miltiadis Vasilakis
        [json appendString:[self indent]];
195 700184fb Miltiadis Vasilakis
    [json appendString:@"}"];
196 700184fb Miltiadis Vasilakis
    return YES;    
197 700184fb Miltiadis Vasilakis
}
198 700184fb Miltiadis Vasilakis
199 700184fb Miltiadis Vasilakis
- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json {
200 700184fb Miltiadis Vasilakis
    
201 700184fb Miltiadis Vasilakis
    [json appendString:@"\""];
202 700184fb Miltiadis Vasilakis
    
203 700184fb Miltiadis Vasilakis
    NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars];
204 700184fb Miltiadis Vasilakis
    if ( !esc.length ) {
205 700184fb Miltiadis Vasilakis
        // No special chars -- can just add the raw string:
206 700184fb Miltiadis Vasilakis
        [json appendString:fragment];
207 700184fb Miltiadis Vasilakis
        
208 700184fb Miltiadis Vasilakis
    } else {
209 700184fb Miltiadis Vasilakis
        NSUInteger length = [fragment length];
210 700184fb Miltiadis Vasilakis
        for (NSUInteger i = 0; i < length; i++) {
211 700184fb Miltiadis Vasilakis
            unichar uc = [fragment characterAtIndex:i];
212 700184fb Miltiadis Vasilakis
            switch (uc) {
213 700184fb Miltiadis Vasilakis
                case '"':   [json appendString:@"\\\""];       break;
214 700184fb Miltiadis Vasilakis
                case '\\':  [json appendString:@"\\\\"];       break;
215 700184fb Miltiadis Vasilakis
                case '\t':  [json appendString:@"\\t"];        break;
216 700184fb Miltiadis Vasilakis
                case '\n':  [json appendString:@"\\n"];        break;
217 700184fb Miltiadis Vasilakis
                case '\r':  [json appendString:@"\\r"];        break;
218 700184fb Miltiadis Vasilakis
                case '\b':  [json appendString:@"\\b"];        break;
219 700184fb Miltiadis Vasilakis
                case '\f':  [json appendString:@"\\f"];        break;
220 700184fb Miltiadis Vasilakis
                default:    
221 700184fb Miltiadis Vasilakis
                    if (uc < 0x20) {
222 700184fb Miltiadis Vasilakis
                        [json appendFormat:@"\\u%04x", uc];
223 700184fb Miltiadis Vasilakis
                    } else {
224 700184fb Miltiadis Vasilakis
                        CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1);
225 700184fb Miltiadis Vasilakis
                    }
226 700184fb Miltiadis Vasilakis
                    break;
227 700184fb Miltiadis Vasilakis
                    
228 700184fb Miltiadis Vasilakis
            }
229 700184fb Miltiadis Vasilakis
        }
230 700184fb Miltiadis Vasilakis
    }
231 700184fb Miltiadis Vasilakis
    
232 700184fb Miltiadis Vasilakis
    [json appendString:@"\""];
233 700184fb Miltiadis Vasilakis
    return YES;
234 700184fb Miltiadis Vasilakis
}
235 700184fb Miltiadis Vasilakis
236 700184fb Miltiadis Vasilakis
237 700184fb Miltiadis Vasilakis
@end