Permalink
Browse files

make json serialization more reliable

  • Loading branch information...
1 parent ae53f33 commit 530950382b44000fb425eb5cc0afc0937ea76d85 @neilrahilly neilrahilly committed Dec 6, 2012
@@ -525,10 +525,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Neil Rahilly (JLY92MT8BF)";
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "HelloMixpanel/HelloMixpanel-Prefix.pch";
- GCC_PREPROCESSOR_DEFINITIONS = (
- "MIXPANEL_LOG=1",
- "MIXPANEL_DEBUG=1",
- );
+ GCC_PREPROCESSOR_DEFINITIONS = "";
INFOPLIST_FILE = "HelloMixpanel/HelloMixpanel-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -30,6 +30,7 @@ @interface Mixpanel (Test)
@property(nonatomic,retain) NSMutableArray *peopleQueue;
@property(nonatomic,retain) NSTimer *timer;
++ (NSData *)JSONSerializeObject:(id)obj;
- (NSString *)defaultDistinctId;
- (void)archive;
- (NSString *)eventsFilePath;
@@ -84,20 +85,39 @@ - (NSDictionary *)allPropertyTypes
NSArray *array = [NSArray arrayWithObject:@"1"];
NSNull *null = [NSNull null];
+ NSDictionary *nested = [NSDictionary dictionaryWithObject:
+ [NSDictionary dictionaryWithObject:
+ [NSArray arrayWithObject:
+ [NSDictionary dictionaryWithObject:
+ [NSArray arrayWithObject:@"bottom"]
+ forKey:@"p3"]]
+ forKey:@"p2"]
+ forKey:@"p1"];
+ NSURL *url = [NSURL URLWithString:@"https://mixpanel.com/"];
+
return [NSDictionary dictionaryWithObjectsAndKeys:
@"yello", @"string",
number, @"number",
date, @"date",
dictionary, @"dictionary",
array, @"array",
null, @"null",
+ nested, @"nested",
+ url, @"url",
+ @1.3, @"float",
nil];
}
-- (void)testJSONSerializer {
+- (void)testJSONSerializeObject {
NSDictionary *test = [self allPropertyTypes];
- NSString *json = [[MPCJSONSerializer serializer] serializeArray:[NSArray arrayWithObject:test] error:nil];
- STAssertEqualObjects(json, @"[{\"date\":\"2012-09-29T02:14:36\",\"array\":[\"1\"],\"null\":null,\"dictionary\":{\"k\":\"v\"},\"number\":3,\"string\":\"yello\"}]", @"json serializer failed");
+ NSData *data = [Mixpanel JSONSerializeObject:[NSArray arrayWithObject:test]];
+ NSString *json = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
+ STAssertEqualObjects(json, @"[{\"float\":1.3,\"string\":\"yello\",\"url\":\"https:\\/\\/mixpanel.com\\/\",\"nested\":{\"p1\":{\"p2\":[{\"p3\":[\"bottom\"]}]}},\"array\":[\"1\"],\"date\":\"2012-09-29T02:14:36\",\"dictionary\":{\"k\":\"v\"},\"null\":null,\"number\":3}]", @"json serialization failed");
+
+ test = [NSDictionary dictionaryWithObject:@"non-string key" forKey:@3];
+ data = [Mixpanel JSONSerializeObject:[NSArray arrayWithObject:test]];
+ json = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
+ STAssertEqualObjects(json, @"[{\"3\":\"non-string key\"}]", @"json serialization failed");
}
- (void)testIdentify
@@ -202,7 +222,7 @@ - (void)testSuperProperties
- (void)testAssertPropertyTypes
{
- NSDictionary *p = [NSDictionary dictionaryWithObject:[NSURL URLWithString:@"http://www.google.com/"] forKey:@"URL"];
+ NSDictionary *p = [NSDictionary dictionaryWithObject:[NSData data] forKey:@"data"];
STAssertThrows([self.mixpanel track:@"e1" properties:p], @"property type should not be allowed");
STAssertThrows([self.mixpanel registerSuperProperties:p], @"property type should not be allowed");
STAssertThrows([self.mixpanel registerSuperPropertiesOnce:p], @"property type should not be allowed");
@@ -465,10 +485,10 @@ - (void)testMixpanelDelegate
- (void)testPeopleAssertPropertyTypes
{
- NSURL *u = [NSURL URLWithString:@"http://www.google.com/"];
- NSDictionary *p = [NSDictionary dictionaryWithObject:u forKey:@"URL"];
+ NSURL *d = [NSData data];
+ NSDictionary *p = [NSDictionary dictionaryWithObject:d forKey:@"URL"];
STAssertThrows([self.mixpanel.people set:p], @"unsupported property allowed");
- STAssertThrows([self.mixpanel.people set:@"p1" to:u], @"unsupported property allowed");
+ STAssertThrows([self.mixpanel.people set:@"p1" to:d], @"unsupported property allowed");
p = [NSDictionary dictionaryWithObject:@"a" forKey:@"p1"]; // increment should require a number
STAssertThrows([self.mixpanel.people increment:p], @"unsupported property allowed");
}
@@ -40,7 +40,6 @@
- (NSData *)serializeNull:(NSNull *)inNull error:(NSError **)outError;
- (NSData *)serializeNumber:(NSNumber *)inNumber error:(NSError **)outError;
- (NSData *)serializeString:(NSString *)inString error:(NSError **)outError;
-- (NSData *)serializeDate:(NSDate *)inDate error:(NSError **)outError;
- (NSData *)serializeArray:(NSArray *)inArray error:(NSError **)outError;
- (NSData *)serializeDictionary:(NSDictionary *)inDictionary error:(NSError **)outError;
@@ -74,10 +74,6 @@ - (NSData *)serializeObject:(id)inObject error:(NSError **)outError {
theResult = [self serializeString:theString error:outError];
} else if ([inObject isKindOfClass:[MPCSerializedJSONData class]]) {
theResult = [inObject data];
- } else if ([inObject isKindOfClass:[NSDate class]]) {
- theResult = [self serializeDate:inObject error:outError];
- } else if ([inObject isKindOfClass:[NSURL class]]) {
- theResult = [self serializeString:[(NSURL*)inObject absoluteString] error:outError];
} else {
theResult = [self serializeString:[inObject description] error:outError];
}
@@ -86,7 +82,7 @@ - (NSData *)serializeObject:(id)inObject error:(NSError **)outError {
if (outError) {
NSDictionary *theUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithFormat:
- @"Cannot serialize data of type '%@'. The following types are supported: NSNull, NSNumber, NSString, NSArray, NSDictionary, NSData, NSDate, NSURL.",
+ @"Cannot serialize data of type '%@'. The following types are supported: NSNull, NSNumber, NSString, NSArray, NSDictionary, NSData.",
NSStringFromClass([inObject class])], NSLocalizedDescriptionKey,
nil];
*outError = [NSError errorWithDomain:@"MixpanelLib" code:-1 userInfo:theUserInfo];
@@ -154,15 +150,6 @@ - (NSData *)serializeString:(NSString *)inString error:(NSError **)outError {
return [[NSString stringWithFormat:@"\"%@\"", theMutableCopy] dataUsingEncoding:NSUTF8StringEncoding];
}
-- (NSData *)serializeDate:(NSDate *)inDate error:(NSError **)outError {
- NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
- [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"];
- [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
- NSData *output = [self serializeString:[formatter stringFromDate:inDate] error:outError];
- [formatter release];
- return output;
-}
-
- (NSData *)serializeArray:(NSArray *)inArray error:(NSError **)outError {
NSMutableData *theData = [NSMutableData data];
View
@@ -30,7 +30,7 @@
#import "Mixpanel.h"
#import "NSData+MPBase64.h"
-#define VERSION @"1.0.1"
+#define VERSION @"1.0.3"
#ifndef IFT_ETHER
#define IFT_ETHER 0x6 // ethernet CSMACD
@@ -243,35 +243,104 @@ + (NSString *)calculateHMACSHA1withString:(NSString *)str andKey:(NSString *)key
];
}
-+ (NSString *)encodeAPIData:(NSArray *)array
++ (NSData *)JSONSerializeObject:(id)obj
{
+ id coercedObj = [Mixpanel JSONSerializableObjectForObject:obj];
+
MPCJSONDataSerializer *serializer = [MPCJSONDataSerializer serializer];
NSError *error = nil;
- NSData *data = [serializer serializeArray:array error:&error];
+ NSData *data = nil;
+ @try {
+ data = [serializer serializeObject:coercedObj error:&error];
+ }
+ @catch (NSException *exception) {
+ NSLog(@"%@ exception encoding api data: %@", self, exception);
+ }
if (error) {
NSLog(@"%@ error encoding api data: %@", self, error);
- return @"";
- } else {
- NSString *b64String = [data mp_base64EncodedString];
+ }
+ return data;
+}
+
++ (id)JSONSerializableObjectForObject:(id)obj
+{
+ // valid json types
+ if ([obj isKindOfClass:[NSString class]] ||
+ [obj isKindOfClass:[NSNumber class]] ||
+ [obj isKindOfClass:[NSNull class]]) {
+ return obj;
+ }
+
+ // recurse on containers
+ if ([obj isKindOfClass:[NSArray class]]) {
+ NSMutableArray *a = [NSMutableArray array];
+ for (id i in obj) {
+ [a addObject:[Mixpanel JSONSerializableObjectForObject:i]];
+ }
+ return [NSArray arrayWithArray:a];
+ }
+ if ([obj isKindOfClass:[NSDictionary class]]) {
+ NSMutableDictionary *d = [NSMutableDictionary dictionary];
+ for (id key in obj) {
+ NSString *stringKey;
+ if (![key isKindOfClass:[NSString class]]) {
+ stringKey = [key description];
+ NSLog(@"%@ warning: property keys should be strings. got: %@. coercing to: %@", self, [key class], stringKey);
+ } else {
+ stringKey = [NSString stringWithString:key];
+ }
+ id v = [Mixpanel JSONSerializableObjectForObject:[obj objectForKey:key]];
+ [d setObject:v forKey:stringKey];
+ }
+ return [NSDictionary dictionaryWithDictionary:d];
+ }
+
+ // some common cases
+ if ([obj isKindOfClass:[NSDate class]]) {
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+ [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"];
+ [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
+ NSString *s = [formatter stringFromDate:obj];
+ [formatter release];
+ return s;
+ } else if ([obj isKindOfClass:[NSURL class]]) {
+ return [obj absoluteString];
+ }
+
+ // default to sending the object's description
+ NSString *s = [obj description];
+ NSLog(@"%@ warning: property values should be valid json types. got: %@. coercing to: %@", self, [obj class], s);
+ return s;
+}
+
++ (NSString *)encodeAPIData:(NSArray *)array
+{
+ NSString *b64String = @"";
+ NSData *data = [Mixpanel JSONSerializeObject:array];
+ if (data) {
+ b64String = [data mp_base64EncodedString];
b64String = (id)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)b64String,
NULL,
CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8);
- return [b64String autorelease];
}
+ return [b64String autorelease];
}
+ (void)assertPropertyTypes:(NSDictionary *)properties
{
- for (id v in [properties allValues]) {
+ for (id k in properties) {
+ NSAssert([k isKindOfClass: [NSString class]], @"%@ property keys must be NSString. got: %@ %@", self, [k class], k);
+ id v = [properties objectForKey:k];
NSAssert([v isKindOfClass:[NSString class]] ||
[v isKindOfClass:[NSNumber class]] ||
[v isKindOfClass:[NSNull class]] ||
- [v isKindOfClass:[NSDate class]] ||
[v isKindOfClass:[NSArray class]] ||
- [v isKindOfClass:[NSDictionary class]],
- @"%@ property values must be NSString, NSNumber, NSNull, NSDate, NSArray or NSDictionary. found: %@", self, v);
+ [v isKindOfClass:[NSDictionary class]] ||
+ [v isKindOfClass:[NSDate class]] ||
+ [v isKindOfClass:[NSURL class]],
+ @"%@ property values must be NSString, NSNumber, NSNull, NSArray, NSDictionary, NSDate or NSURL. got: %@ %@", self, [v class], v);
}
}

0 comments on commit 5309503

Please sign in to comment.