README.md 7.21 KB
Newer Older
1
# JSONModel - Magical Data Modeling Framework for JSON
曹云霄's avatar
曹云霄 committed
2

3 4 5
JSONModel allows rapid creation of smart data models. You can use it in your
iOS, macOS, watchOS and tvOS apps. Automatic introspection of your model classes
and JSON input drastically reduces the amount of code you have to write.
曹云霄's avatar
曹云霄 committed
6

7
See [CHANGELOG.md](CHANGELOG.md) for details on changes.
曹云霄's avatar
曹云霄 committed
8

9
## Installation
曹云霄's avatar
曹云霄 committed
10

11
### CocoaPods
曹云霄's avatar
曹云霄 committed
12 13 14 15 16

```ruby
pod 'JSONModel'
```

17
### Carthage
曹云霄's avatar
曹云霄 committed
18 19

```ruby
20
github "jsonmodel/jsonmodel"
曹云霄's avatar
曹云霄 committed
21 22
```

23
### Manual
曹云霄's avatar
曹云霄 committed
24

25 26 27
0. download the JSONModel repository
0. copy the JSONModel sub-folder into your Xcode project
0. link your app to SystemConfiguration.framework
曹云霄's avatar
曹云霄 committed
28

29
## Basic Usage
曹云霄's avatar
曹云霄 committed
30

31
Consider you have JSON like this:
曹云霄's avatar
曹云霄 committed
32

33 34 35
```json
{ "id": 10, "country": "Germany", "dialCode": 49, "isInEurope": true }
```
36

37 38
- create a JSONModel subclass for your data model
- declare properties in your header file with the name of the JSON keys:
曹云霄's avatar
曹云霄 committed
39

40
```objc
曹云霄's avatar
曹云霄 committed
41
@interface CountryModel : JSONModel
42 43 44 45
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *country;
@property (nonatomic) NSString *dialCode;
@property (nonatomic) BOOL isInEurope;
曹云霄's avatar
曹云霄 committed
46 47 48
@end
```

49
There's no need to do anything in the implementation (`.m`) file.
曹云霄's avatar
曹云霄 committed
50

51
- initialize your model with data:
曹云霄's avatar
曹云霄 committed
52

53 54 55
```objc
NSError *error;
CountryModel *country = [[CountryModel alloc] initWithString:myJson error:&error];
曹云霄's avatar
曹云霄 committed
56 57
```

58 59 60
If the validation of the JSON passes. you have all the corresponding properties
in your model populated from the JSON. JSONModel will also try to convert as
much data to the types you expect. In the example above it will:
曹云霄's avatar
曹云霄 committed
61

62 63 64 65
- convert `id` from string (in the JSON) to an `int` for your class
- copy the `country` value
- convert `dialCode` from a number (in the JSON) to an `NSString` value
- copy the `isInEurope` value
曹云霄's avatar
曹云霄 committed
66

67
All you have to do is define the properties and their expected types.
曹云霄's avatar
曹云霄 committed
68

69
## Examples
曹云霄's avatar
曹云霄 committed
70

71
### Automatic name based mapping
72

73 74 75 76 77 78 79
```json
{
	"id": 123,
	"name": "Product name",
	"price": 12.95
}
```
曹云霄's avatar
曹云霄 committed
80

81 82 83 84 85 86 87
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@end
```
曹云霄's avatar
曹云霄 committed
88

89
### Model cascading (models including other models)
90

91
```json
曹云霄's avatar
曹云霄 committed
92
{
93 94 95 96 97 98 99
	"orderId": 104,
	"totalPrice": 13.45,
	"product": {
		"id": 123,
		"name": "Product name",
		"price": 12.95
	}
曹云霄's avatar
曹云霄 committed
100
}
101 102 103
```

```objc
曹云霄's avatar
曹云霄 committed
104
@interface ProductModel : JSONModel
105 106 107
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
曹云霄's avatar
曹云霄 committed
108 109 110
@end

@interface OrderModel : JSONModel
111 112 113
@property (nonatomic) NSInteger orderId;
@property (nonatomic) float totalPrice;
@property (nonatomic) ProductModel *product;
曹云霄's avatar
曹云霄 committed
114
@end
115
```
曹云霄's avatar
曹云霄 committed
116

117 118 119
### Model collections

```json
曹云霄's avatar
曹云霄 committed
120
{
121 122 123 124 125 126 127 128 129 130 131 132 133 134
	"orderId": 104,
	"totalPrice": 103.45,
	"products": [
		{
			"id": 123,
			"name": "Product #1",
			"price": 12.95
		},
		{
			"id": 137,
			"name": "Product #2",
			"price": 82.95
		}
	]
曹云霄's avatar
曹云霄 committed
135
}
136
```
曹云霄's avatar
曹云霄 committed
137

138 139
```objc
@protocol ProductModel;
曹云霄's avatar
曹云霄 committed
140

141 142 143 144
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
曹云霄's avatar
曹云霄 committed
145
@end
146

147
@interface OrderModel : JSONModel
148 149 150
@property (nonatomic) NSInteger orderId;
@property (nonatomic) float totalPrice;
@property (nonatomic) NSArray <ProductModel> *products;
151
@end
152
```
153

154 155 156 157 158 159 160
Note: the angle brackets after `NSArray` contain a protocol. This is not the
same as the Objective-C generics system. They are not mutually exclusive, but
for JSONModel to work, the protocol must be in place.

### Nested key mapping

```json
曹云霄's avatar
曹云霄 committed
161
{
162 163 164 165 166 167 168 169 170
	"orderId": 104,
	"orderDetails": [
		{
			"name": "Product #1",
			"price": {
				"usd": 12.95
			}
		}
	]
曹云霄's avatar
曹云霄 committed
171
}
172 173 174
```

```objc
曹云霄's avatar
曹云霄 committed
175
@interface OrderModel : JSONModel
176 177 178
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *productName;
@property (nonatomic) float price;
曹云霄's avatar
曹云霄 committed
179 180 181 182
@end

@implementation OrderModel

183
+ (JSONKeyMapper *)keyMapper
曹云霄's avatar
曹云霄 committed
184
{
185 186 187 188 189
	return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{
		@"id": @"orderId",
		@"productName": @"orderDetails.name",
		@"price": @"orderDetails.price.usd"
	}];
曹云霄's avatar
曹云霄 committed
190 191 192
}

@end
193 194 195 196 197
```

### Map automatically to snake_case

```json
曹云霄's avatar
曹云霄 committed
198
{
199 200 201
	"order_id": 104,
	"order_product": "Product #1",
	"order_price": 12.95
曹云霄's avatar
曹云霄 committed
202
}
203
```
204

205 206 207 208 209
```objc
@interface OrderModel : JSONModel
@property (nonatomic) NSInteger orderId;
@property (nonatomic) NSString *orderProduct;
@property (nonatomic) float orderPrice;
曹云霄's avatar
曹云霄 committed
210 211 212 213
@end

@implementation OrderModel

214
+ (JSONKeyMapper *)keyMapper
曹云霄's avatar
曹云霄 committed
215
{
216
	return [JSONKeyMapper mapperForSnakeCase];
曹云霄's avatar
曹云霄 committed
217 218 219
}

@end
220
```
曹云霄's avatar
曹云霄 committed
221

222 223 224
### Optional properties (i.e. can be missing or null)

```json
曹云霄's avatar
曹云霄 committed
225
{
226 227 228
	"id": 123,
	"name": null,
	"price": 12.95
229
}
230
```
曹云霄's avatar
曹云霄 committed
231

232 233 234 235 236 237
```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString <Optional> *name;
@property (nonatomic) float price;
@property (nonatomic) NSNumber <Optional> *uuid;
238
@end
239
```
曹云霄's avatar
曹云霄 committed
240

241
### Ignored properties (i.e. JSONModel completely ignores them)
242

243
```json
曹云霄's avatar
曹云霄 committed
244
{
245 246 247 248 249 250 251 252 253
	"id": 123,
	"name": null
}
```

```objc
@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString <Ignore> *customProperty;
254
@end
255
```
256

257
### Making scalar types optional
258

259
```json
260
{
261
	"id": null
曹云霄's avatar
曹云霄 committed
262
}
263
```
曹云霄's avatar
曹云霄 committed
264

265
```objc
曹云霄's avatar
曹云霄 committed
266
@interface ProductModel : JSONModel
267
@property (nonatomic) NSInteger id;
曹云霄's avatar
曹云霄 committed
268 269 270
@end

@implementation ProductModel
271

272 273 274 275
+ (BOOL)propertyIsOptional:(NSString *)propertyName
{
	if ([propertyName isEqualToString:@"id"])
		return YES;
276

277 278
	return NO;
}
279

280
@end
曹云霄's avatar
曹云霄 committed
281 282
```

283
### Export model to `NSDictionary` or JSON
曹云霄's avatar
曹云霄 committed
284

285 286 287
```objc
ProductModel *pm = [ProductModel new];
pm.name = @"Some Name";
曹云霄's avatar
曹云霄 committed
288

289 290
// convert to dictionary
NSDictionary *dict = [pm toDictionary];
曹云霄's avatar
曹云霄 committed
291

292 293
// convert to json
NSString *string = [pm toJSONString];
曹云霄's avatar
曹云霄 committed
294 295
```

296
### Custom data transformers
曹云霄's avatar
曹云霄 committed
297

298 299 300
```objc
@interface JSONValueTransformer (CustomNSDate)
@end
曹云霄's avatar
曹云霄 committed
301 302 303

@implementation JSONValueTransformer (CustomTransformer)

304 305 306 307 308
- (NSDate *)NSDateFromNSString:(NSString *)string
{
	NSDateFormatter *formatter = [NSDateFormatter new];
	formatter.dateFormat = APIDateFormat;
	return [formatter dateFromString:string];
曹云霄's avatar
曹云霄 committed
309 310
}

311 312 313 314 315
- (NSString *)JSONObjectFromNSDate:(NSDate *)date
{
	NSDateFormatter *formatter = [NSDateFormatter new];
	formatter.dateFormat = APIDateFormat;
	return [formatter stringFromDate:date];
曹云霄's avatar
曹云霄 committed
316 317 318 319 320
}

@end
```

321
### Custom getters/setters
曹云霄's avatar
曹云霄 committed
322

323
```objc
曹云霄's avatar
曹云霄 committed
324
@interface ProductModel : JSONModel
325 326 327 328
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@property (nonatomic) NSLocale *locale;
曹云霄's avatar
曹云霄 committed
329 330 331 332
@end

@implementation ProductModel

333 334 335
- (void)setLocaleWithNSString:(NSString *)string
{
	self.locale = [NSLocale localeWithLocaleIdentifier:string];
曹云霄's avatar
曹云霄 committed
336 337
}

338 339 340
- (void)setLocaleWithNSDictionary:(NSDictionary *)dictionary
{
	self.locale = [NSLocale localeWithLocaleIdentifier:dictionary[@"identifier"]];
341
}
曹云霄's avatar
曹云霄 committed
342

343 344 345 346
- (NSString *)JSONObjectForLocale
{
	return self.locale.localeIdentifier;
}
347

348
@end
349
```
350

351 352 353 354 355 356 357 358 359 360 361
### Custom JSON validation

```objc

@interface ProductModel : JSONModel
@property (nonatomic) NSInteger id;
@property (nonatomic) NSString *name;
@property (nonatomic) float price;
@property (nonatomic) NSLocale *locale;
@property (nonatomic) NSNumber <Ignore> *minNameLength;
@end
曹云霄's avatar
曹云霄 committed
362

363
@implementation ProductModel
曹云霄's avatar
曹云霄 committed
364

365 366 367 368
- (BOOL)validate:(NSError **)error
{
	if (![super validate:error])
		return NO;
曹云霄's avatar
曹云霄 committed
369

370 371 372 373 374
	if (self.name.length < self.minNameLength.integerValue)
	{
		*error = [NSError errorWithDomain:@"me.mycompany.com" code:1 userInfo:nil];
		return NO;
	}
曹云霄's avatar
曹云霄 committed
375

376 377
	return YES;
}
曹云霄's avatar
曹云霄 committed
378

379 380
@end
```
曹云霄's avatar
曹云霄 committed
381

382
## License
曹云霄's avatar
曹云霄 committed
383

384
MIT licensed - see [LICENSE](LICENSE) file.
曹云霄's avatar
曹云霄 committed
385

386
## Contributing
曹云霄's avatar
曹云霄 committed
387

388
We love pull requests! See [CONTRIBUTING.md](CONTRIBUTING.md) for full details.