Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability for us and customers to make custom types serializable … #940

Merged
merged 2 commits into from
Sep 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion Analytics/Classes/SEGAnalyticsUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,23 @@

NS_ASSUME_NONNULL_BEGIN

// Logging
#pragma mark - Logging
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you


void SEGSetShowDebugLogs(BOOL showDebugLogs);
void SEGLog(NSString *format, ...);


#pragma mark - Serialization Extensions

NS_SWIFT_NAME(SegmentSerializable)
@protocol SEGSerializable
/**
Serialize objects to a type supported by NSJSONSerializable. Objects that conform to this protocol should
return values of type NSArray, NSDictionary, NSString, NSNumber. Useful for extending objects of your own
such that they can be serialized on the way to Segment and destinations.
*/
- (id)serializeToAppropriateType;
@end


NS_ASSUME_NONNULL_END
28 changes: 27 additions & 1 deletion Analytics/Classes/SEGAnalyticsUtils.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#import "SEGAnalyticsUtils.h"
#import "SEGAnalytics.h"
#import "SEGUtils.h"

static BOOL kAnalyticsLoggerShowLogs = NO;

// Logging
#pragma mark - Logging

void SEGSetShowDebugLogs(BOOL showDebugLogs)
{
Expand All @@ -21,3 +22,28 @@ void SEGLog(NSString *format, ...)
va_end(args);
}

#pragma mark - Serialization Extensions

@interface NSDate(SEGSerializable)<SEGSerializable>
- (id)serializeToAppropriateType;
@end

@implementation NSDate(SEGSerializable)
- (id)serializeToAppropriateType
{
return iso8601FormattedString(self);
}
@end

@interface NSURL(SEGSerializable)<SEGSerializable>
- (id)serializeToAppropriateType;
@end

@implementation NSURL(SEGSerializable)
- (id)serializeToAppropriateType
{
return [self absoluteString];
}
@end


66 changes: 8 additions & 58 deletions Analytics/Internal/SEGUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@
#import <Cocoa/Cocoa.h>
#endif

// BKS: This doesn't appear to be needed anymore. Will investigate.
//NSString *const SEGADClientClass = @"ADClient";

@implementation SEGUtils

+ (NSData *_Nullable)dataFromPlist:(nonnull id)plist
Expand Down Expand Up @@ -492,76 +489,26 @@ void seg_dispatch_specific_sync(dispatch_queue_t queue,
seg_dispatch_specific(queue, block, YES);
}

// JSON Utils

static id SEGCoerceJSONObject(id obj)
{
// if the object is a NSString, NSNumber
// then we're good
if ([obj isKindOfClass:[NSString class]] ||
[obj isKindOfClass:[NSNumber class]] ||
[obj isKindOfClass:[NSNull class]]) {
return obj;
}

if ([obj isKindOfClass:[NSArray class]]) {
NSMutableArray *array = [NSMutableArray array];
for (id i in obj) {
NSObject *value = i;
// Hotfix: Storage format should support NSNull instead
if ([value isKindOfClass:[NSNull class]]) {
value = [NSData data];
}
[array addObject:SEGCoerceJSONObject(value)];
}
return array;
}

if ([obj isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (NSString *key in obj) {
NSObject *value = obj[key];
if (![key isKindOfClass:[NSString class]])
SEGLog(@"warning: dictionary keys should be strings. got: %@. coercing "
@"to: %@",
[key class], [key description]);
dict[key.description] = SEGCoerceJSONObject(value);
}
return dict;
}

if ([obj isKindOfClass:[NSDate class]])
return iso8601FormattedString(obj);

if ([obj isKindOfClass:[NSURL class]])
return [obj absoluteString];

// default to sending the object's description
SEGLog(@"warning: dictionary values should be valid json types. got: %@. "
@"coercing to: %@",
[obj class], [obj description]);
return [obj description];
}

NSDictionary *SEGCoerceDictionary(NSDictionary *dict)
{
// make sure that a new dictionary exists even if the input is null
dict = dict ?: @{};
// assert that the proper types are in the dictionary
dict = [dict serializableDeepCopy];
// coerce urls, and dates to the proper format
return SEGCoerceJSONObject(dict);
return dict;
}

NSString *SEGEventNameForScreenTitle(NSString *title)
{
return [[NSString alloc] initWithFormat:@"Viewed %@ Screen", title];
}


@implementation NSJSONSerialization(Serializable)
+ (BOOL)isOfSerializableType:(id)obj
{
if ([obj conformsToProtocol:@protocol(SEGSerializable)])
return YES;

if ([obj isKindOfClass:[NSArray class]] ||
[obj isKindOfClass:[NSDictionary class]] ||
[obj isKindOfClass:[NSString class]] ||
Expand Down Expand Up @@ -598,6 +545,8 @@ - (id)serializableDeepCopy:(BOOL)mutable
theCopy = [aValue serializableDeepCopy:mutable];
} else if ([aValue conformsToProtocol:@protocol(NSCopying)]) {
theCopy = [aValue copy];
} else if ([aValue conformsToProtocol:@protocol(SEGSerializable)]) {
theCopy = [aValue serializeToAppropriateType];
} else {
theCopy = aValue;
}
Expand Down Expand Up @@ -647,6 +596,8 @@ -(id)serializableDeepCopy:(BOOL)mutable
theCopy = [aValue serializableDeepCopy:mutable];
} else if ([aValue conformsToProtocol:@protocol(NSCopying)]) {
theCopy = [aValue copy];
} else if ([aValue conformsToProtocol:@protocol(SEGSerializable)]) {
theCopy = [aValue serializeToAppropriateType];
} else {
theCopy = aValue;
}
Expand All @@ -669,5 +620,4 @@ - (NSMutableArray *)serializableMutableDeepCopy {
return [self serializableDeepCopy:YES];
}


@end
47 changes: 40 additions & 7 deletions AnalyticsTests/SerializationTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#import <XCTest/XCTest.h>
@import Analytics;

#pragma mark - Internal copy-overs for testing

JSON_DICT SEGCoerceDictionary(NSDictionary *_Nullable dict);

@interface NSJSONSerialization (Serializable)
+ (BOOL)isOfSerializableType:(id)obj;
@end
Expand All @@ -23,6 +27,18 @@ @interface NSDictionary(SerializableDeepCopy) <SEGSerializableDeepCopy>
@interface NSArray(SerializableDeepCopy) <SEGSerializableDeepCopy>
@end

@interface MyObject: NSObject <SEGSerializable>
@end

@implementation MyObject
- (id)serializeToAppropriateType
{
return @"MyObject";
}
@end

#pragma mark - Serialization Tests

@interface SerializationTests : XCTestCase

@end
Expand Down Expand Up @@ -56,20 +72,37 @@ - (void)testDeepCopyAndConformance {
XCTAssertThrows([nonserializable serializableDeepCopy]);
}

- (void)testDateIssue {
- (void)testSEGSerialization {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hype 🔥

MyObject *myObj = [[MyObject alloc] init];
NSDate *date = [NSDate date];
NSData *data = [NSData data];
NSURL *url = [NSURL URLWithString:@"http://segment.com"];
NSString *test = @"test";

XCTAssertFalse([NSJSONSerialization isOfSerializableType:date]);
XCTAssertFalse([NSJSONSerialization isOfSerializableType:data]);
XCTAssertTrue([NSJSONSerialization isOfSerializableType:date]);
XCTAssertTrue([NSJSONSerialization isOfSerializableType:url]);
XCTAssertTrue([NSJSONSerialization isOfSerializableType:test]);

NSDictionary *nonserializable = @{@"test": date};
NSDictionary *serializable = @{@"test": @1};
NSDictionary *datevalue = @{@"test": date};
NSDictionary *urlvalue = @{@"test": url};
NSDictionary *numbervalue = @{@"test": @1};
NSDictionary *myobjectvalue = @{@"test": myObj};

XCTAssertNoThrow([datevalue serializableDeepCopy]);
XCTAssertNoThrow([urlvalue serializableDeepCopy]);
XCTAssertNoThrow([numbervalue serializableDeepCopy]);
XCTAssertNoThrow([myobjectvalue serializableDeepCopy]);

NSDictionary *nonserializable = @{@"test": @[data]};
XCTAssertThrows([nonserializable serializableDeepCopy]);
XCTAssertNoThrow([serializable serializableDeepCopy]);

nonserializable = @{@"test": @[date]};
XCTAssertThrows([nonserializable serializableDeepCopy]);
NSDictionary *testCoersion1 = @{@"test1": @[date], @"test2": url, @"test3": @1};
NSDictionary *coersionResult1 = SEGCoerceDictionary(testCoersion1);
XCTAssertNotNil(coersionResult1);

NSDictionary *testCoersion2 = @{@"test1": @[date], @"test2": url, @"test3": @1, @"test4": data};
XCTAssertThrows(SEGCoerceDictionary(testCoersion2));
}

@end