BTJSON.m 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. #import "BTJSON.h"
  2. NSString * const BTJSONErrorDomain = @"com.briantreepayments.BTJSONErrorDomain";
  3. @interface BTJSON ()
  4. @property (nonatomic, strong) NSArray *subscripts;
  5. @property (nonatomic, strong) id value;
  6. @end
  7. @implementation BTJSON
  8. @synthesize value = _value;
  9. - (instancetype)init {
  10. self = [super init];
  11. if (self) {
  12. self.subscripts = [NSMutableArray array];
  13. self.value = @{};
  14. }
  15. return self;
  16. }
  17. - (instancetype)initWithData:(NSData *)data {
  18. NSError *error;
  19. id value = [NSJSONSerialization JSONObjectWithData:data
  20. options:NSJSONReadingAllowFragments
  21. error:&error];
  22. if (error != nil) {
  23. return [self initWithValue:error];
  24. }
  25. return [self initWithValue:value];
  26. }
  27. - (instancetype)initWithValue:(id)value {
  28. self = [self init];
  29. if (self) {
  30. self.value = value;
  31. }
  32. return self;
  33. }
  34. #pragma mark Subscripting
  35. - (id)objectForKeyedSubscript:(NSString *)key {
  36. BTJSON *json = [[BTJSON alloc] initWithValue:_value];
  37. json.subscripts = [self.subscripts arrayByAddingObject:key];
  38. return json;
  39. }
  40. - (id)objectAtIndexedSubscript:(NSUInteger)idx {
  41. BTJSON *json = [[BTJSON alloc] initWithValue:_value];
  42. json.subscripts = [self.subscripts arrayByAddingObject:@(idx)];
  43. return json;
  44. }
  45. - (id)value {
  46. id value = _value;
  47. for (id key in self.subscripts) {
  48. if ([value isKindOfClass:[NSArray class]]) {
  49. if (![key isKindOfClass:[NSNumber class]]) {
  50. value = [self chainedErrorOrErrorWithCode:BTJSONErrorAccessInvalid userInfo:nil];
  51. break;
  52. }
  53. NSUInteger idx = [(NSNumber *)key unsignedIntegerValue];
  54. if (idx >= [(NSArray *)value count]) {
  55. value = nil;
  56. break;
  57. }
  58. value = [value objectAtIndexedSubscript:idx];
  59. } else if ([value isKindOfClass:[NSDictionary class]]) {
  60. if (![key isKindOfClass:[NSString class]]) {
  61. value = [self chainedErrorOrErrorWithCode:BTJSONErrorAccessInvalid userInfo:nil];
  62. break;
  63. }
  64. value = [value objectForKeyedSubscript:key];
  65. } else {
  66. value = [self chainedErrorOrErrorWithCode:BTJSONErrorValueInvalid userInfo:@{ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Attempted to index into a value that is neither an object nor an array using key (%@).", key] }];
  67. break;
  68. }
  69. }
  70. return value;
  71. }
  72. #pragma mark Validity Checks
  73. - (BOOL)isError {
  74. return [self.value isKindOfClass:[NSError class]];
  75. }
  76. - (NSError *)asError {
  77. if (![self.value isKindOfClass:[NSError class]]) {
  78. return nil;
  79. }
  80. return self.value;
  81. }
  82. #pragma mark Generating JSON
  83. - (NSData *)asJSONAndReturnError:(NSError **)error {
  84. return [NSJSONSerialization dataWithJSONObject:self.value
  85. options:0
  86. error:error];
  87. }
  88. - (NSString *)asPrettyJSONAndReturnError:(NSError **)error {
  89. return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:self.value
  90. options:NSJSONWritingPrettyPrinted
  91. error:error]
  92. encoding:NSUTF8StringEncoding];
  93. }
  94. #pragma mark JSON Type Casts
  95. - (NSString *)asString {
  96. if (![self.value isKindOfClass:[NSString class]]) {
  97. return nil;
  98. }
  99. return self.value;
  100. }
  101. - (NSArray<BTJSON *> *)asArray {
  102. if (![self.value isKindOfClass:[NSArray class]]) {
  103. return nil;
  104. }
  105. return self.value;
  106. }
  107. - (NSDecimalNumber *)asNumber {
  108. if (![self.value isKindOfClass:[NSNumber class]]) {
  109. return nil;
  110. }
  111. return [NSDecimalNumber decimalNumberWithDecimal:[self.value decimalValue]];
  112. }
  113. #pragma mark JSON Extension Type Casts
  114. - (NSURL *)asURL {
  115. NSString *urlString = self.asString;
  116. if (urlString == nil) {
  117. return nil;
  118. }
  119. return [NSURL URLWithString:urlString];
  120. }
  121. - (NSArray<NSString *> *)asStringArray {
  122. NSArray <NSString *> *array = (NSArray <NSString *> *)self.asArray;
  123. for (id obj in array) {
  124. if (![obj isKindOfClass:[NSString class]]) {
  125. return nil;
  126. }
  127. }
  128. return array;
  129. }
  130. - (NSDictionary<NSString *, BTJSON *> *)asDictionary {
  131. NSDictionary *dictionary = self.value;
  132. if (![dictionary isKindOfClass:[NSDictionary class]]) {
  133. return nil;
  134. }
  135. return dictionary;
  136. }
  137. - (NSInteger)asIntegerOrZero {
  138. NSNumber *number = self.value;
  139. if (![number isKindOfClass:[NSNumber class]]) {
  140. return 0;
  141. }
  142. return number.integerValue;
  143. }
  144. - (NSInteger)asEnum:(nonnull NSDictionary *)mapping orDefault:(NSInteger)defaultValue {
  145. id key = self.value;
  146. NSNumber *value = mapping[key];
  147. if (value == nil || ![value isKindOfClass:[NSNumber class]]) {
  148. return defaultValue;
  149. }
  150. return value.integerValue;
  151. }
  152. // @name JSON Type Checks
  153. - (BOOL)isString {
  154. return [self.value isKindOfClass:[NSString class]];
  155. }
  156. - (BOOL)isNumber {
  157. return [self.value isKindOfClass:[NSNumber class]];
  158. }
  159. - (BOOL)isArray {
  160. return [self.value isKindOfClass:[NSArray class]];
  161. }
  162. - (BOOL)isObject {
  163. return [self.value isKindOfClass:[NSDictionary class]];
  164. }
  165. - (BOOL)isBool {
  166. return [self.value isEqual:@YES] || [self.value isEqual:@NO];
  167. }
  168. - (BOOL)isTrue {
  169. return [self.value isEqual:@YES];
  170. }
  171. - (BOOL)isFalse {
  172. return [self.value isEqual:@NO];
  173. }
  174. - (BOOL)isNull {
  175. return [self.value isKindOfClass:[NSNull class]];
  176. }
  177. #pragma mark Error Handling
  178. - (NSError *)chainedErrorOrErrorWithCode:(NSInteger)code
  179. userInfo:(NSDictionary *)userInfo {
  180. if ([_value isKindOfClass:[NSError class]]) {
  181. return _value;
  182. }
  183. return [NSError errorWithDomain:BTJSONErrorDomain
  184. code:code
  185. userInfo:userInfo];
  186. }
  187. #pragma mark -
  188. - (NSString *)description {
  189. return [self debugDescription];
  190. }
  191. - (NSString *)debugDescription {
  192. return [NSString stringWithFormat:@"<BTJSON:%p value:%@>", self, self.value];
  193. }
  194. @end