8

I have a json string like:

{
  "a":"val1",
  "b":"val2",
  "c":"val3"
}

And I have an objective C header file like:

@interface TestItem : NSObject

@property NSString *a;
@property NSString *b;
@property NSString *c;

@end

Can I parse the Json and get an instance of TestItem Class?

I know how to parse the json into a dictionary, but I want to parse it in a class (similar to what gson does in Java).

onnoweb
  • 3,038
  • 22
  • 29
marrock
  • 301
  • 1
  • 2
  • 12
  • You *don't* want to do it the way it's done with GSON/Jackson in Java. Folks spend more time with those trying to get the setup right than it would take to just write the code in a straight-forward manner. – Hot Licks Aug 11 '14 at 00:25

5 Answers5

14

Instead of using dictionaries directly you can always deserialize (parse) JSON to your class with using Key-value coding. Key-value coding is a great feature of Cocoa that lets you access properties and instance variables of class at runtime by name. As I can see your JSON model is not complex and you can apply this easily.

person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property NSString *personName;
@property NSString *personMiddleName;
@property NSString *personLastname;

- (instancetype)initWithJSONString:(NSString *)JSONString;

@end

person.m

#import "Person.h"

@implementation Person

- (instancetype)init
{
    self = [super init];
    if (self) {

    }
    return self;
}

- (instancetype)initWithJSONString:(NSString *)JSONString
{
    self = [super init];
    if (self) {

        NSError *error = nil;
        NSData *JSONData = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
        NSDictionary *JSONDictionary = [NSJSONSerialization JSONObjectWithData:JSONData options:0 error:&error];

        if (!error && JSONDictionary) {

            //Loop method
            for (NSString* key in JSONDictionary) {
                [self setValue:[JSONDictionary valueForKey:key] forKey:key];
            }
            // Instead of Loop method you can also use:
            // thanks @sapi for good catch and warning.
            // [self setValuesForKeysWithDictionary:JSONDictionary];
        }
    }
    return self;
}

@end

appDelegate.m

@implementation AppDelegate

    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

        // JSON String
        NSString *JSONStr = @"{ \"personName\":\"MyName\", \"personMiddleName\":\"MyMiddleName\", \"personLastname\":\"MyLastName\" }";

        // Init custom class 
        Person *person = [[Person alloc] initWithJSONString:JSONStr];

        // Here we can print out all of custom object properties. 
        NSLog(@"%@", person.personName); //Print MyName 
        NSLog(@"%@", person.personMiddleName); //Print MyMiddleName
        NSLog(@"%@", person.personLastname); //Print MyLastName    
    }

@end

The article using JSON to load Objective-C objects good point to start.

modusCell
  • 13,151
  • 9
  • 53
  • 80
  • 1
    You probably just want to use `setValuesForKeysWithDictionary:`, rather than looping and using `setValue:forKey:` – sapi Aug 11 '14 at 00:05
  • Except that if your object embeds several other objects you need to call their `initWithDictionary` methods, since you can't easily extract the JSON for them. Same thing holds if you need to init your superclass with parms. So might as well decode the JSON up front and use `initWithDictionary` for everything. – Hot Licks Aug 11 '14 at 00:19
  • @HotLicks that is correct, just designed the answer for OP's requirement. – modusCell Aug 11 '14 at 00:23
  • very nice, i would like to add something if you have an reserved words that returned from JSONData like id or release it would be good to rewrite in your code the following for (NSString* key in JSONDictionary) { if([Key isEqualToString @"id"]){ for (NSString* key in JSONDictionary) { [self setValue:[JSONDictionary valueForKey:@"id"] forKey:@"id_data"]; }} else{ [self setValue:[JSONDictionary valueForKey:key] forKey:key]; } } and so on with every special key , u also need to rename property – Amr Angry Jan 16 '16 at 20:39
  • In your `initWith` method `self = [super init]` should be `self = [self init]` – h3dkandi May 19 '16 at 14:21
  • How do you handle the ```NSUnknownKeyException``` raised for a key in the incoming json which is not set as a property in the model class? I would like to set the values for remaining properties. – iphondroid Feb 10 '20 at 04:57
3

We can automatically map response json in our model classes with the help of third party library

JSONMode https://github.com/mattiaslevin/ObjectMapper

Simply create your class like below define your data model properties. Be careful the keys of your json response should be same as the property you are defining. You will get more clear picture below because I am sharing my json response with the structure of the model classes -

Response json

{
    "code": 200,
    "data": [
        {
            "stockName": "sh000001",
            "category": "china",
            "stockStatus": "open",
            "roadMap": [
                {
                    "stockTimeStamp": "10:25",
                    "stockValue": "2789.915",
                    "number1": 1,
                    "number2": 5
                },
                {
                    "stockTimeStamp": "10:30",
                    "stockValue": "2790.153",
                    "number1": 5,
                    "number2": 3
                }
            ],
            "gameData": [
                {
                    "gameUUID": "e4fcd001-2499-45c3-a21c-d573b9e378cc",
                    "gameStatus": "Open"
                }
            ]
        },
        {
            "stockName": "usindex",
            "category": "usa",
            "stockStatus": "open",
            "roadMap": [
                {
                    "stockTimeStamp": "10:20",
                    "stockValue": "100.1020",
                    "number1": 2,
                    "number2": 0
                },
                {
                    "stockTimeStamp": "10:25",
                    "stockValue": "100.0958",
                    "number1": 5,
                    "number2": 8
                }
            ],
            "gameData": [
                {
                    "gameUUID": "1a6c9889-41e9-410a-a409-e10126ffeeb5",
                    "gameStatus": "Open"
                }
            ]
        },
        {
            "stockName": "btc1",
            "category": "crypto",
            "stockStatus": "open",
            "roadMap": [
                {
                    "stockTimeStamp": "02:26",
                    "stockValue": "7670.00",
                    "number1": 0,
                    "number2": 0
                },
                {
                    "stockTimeStamp": "02:25",
                    "stockValue": "7670.00",
                    "number1": 0,
                    "number2": 0
                }
            ],
            "gameData": [
                {
                    "gameUUID": "40526121-f199-4649-b169-9913bd883186",
                    "gameStatus": "Open"
                }
            ]
        }
    ],
    "status": true,
    "message": [
        "success"
    ]
}

YourModel.h file

#import "JSONModel.h"

@interface RoadmapElementModel : JSONModel
@property (nonatomic) NSInteger number1;
@property (nonatomic) NSInteger number2;
@property (nonatomic) NSString *stockTimeStamp;
@property (nonatomic) NSString *stockValue;
@end

@interface RoadmapDataModel : JSONModel
@property (nonatomic) NSString *stockName;
@property (nonatomic) NSString *category;
@property (nonatomic) NSString *stockStatus;
@property (nonatomic) NSArray <RoadmapElementModel *> *roadMap;
@end

@interface RoadMapModel : JSONModel
@property (nonatomic) NSInteger code;
@property (nonatomic) BOOL status;
@property (nonatomic) NSArray<RoadmapDataModel *>*data;
@property (nonatomic) NSArray<NSString *> *message;
@end

Note:- You do not need to write anything to your YourModel.h file.

Now just write below code after getting the response JsonString in your ViewController class

NSError *error;
RoadMapModel *roadmap = [[RoadMapModel alloc] initWithString:myString error:&error];
2
- (id)initWithDictionary:(NSDictionary *)dictionary {
    if (self = [super init]) {
        _a = [dictionary objectForKey:@"a"];
        _b = [dictionary objectForKey:@"b"];
        _c = [dictionary objectForKey:@"c"];
    }
    return self;
}
Dan Dosch
  • 321
  • 1
  • 6
  • Maybe some explanatory text would be helpful, and a mention of `NSJSONSerialization`. – s.bandara Aug 08 '14 at 23:38
  • I suppose. He mentioned he knows how to turn the json into a dictionary so that is an option as a next step – Dan Dosch Aug 08 '14 at 23:39
  • Thanks Dan, I was hoping I could avoid using the dictionary, but looks like that is the best option. – marrock Aug 08 '14 at 23:50
  • Or more simply, `dictionary[@"a"]`. See also GitHub's open source Mantle project, which will automate a lot of this boilerplate code. – Aaron Brager Aug 09 '14 at 00:11
  • 1
    If the keys match the property names exactly, you can call `[self setValuesForKeysWithDictionary:dictionary]` instead of setting instance variables one by one. – jlehr Aug 09 '14 at 00:20
  • Yeah, defining an `initWithDictionary` method is the way to go. If there are embedded objects invoke their `initWithDictionary` methods in turn. Works out quite neatly in most cases. – Hot Licks Aug 11 '14 at 00:17
  • And if your superclass needs to be inited you can cascade to its `initWithDictionary` first, then continue when it returns. – Hot Licks Aug 11 '14 at 00:21
1

You have two solutions:

Manual

Write code to parse the JSON to a dictionary and after populate manually an instance of your target object

NSDictionary *jsonDictionary = //JSON parser

TestItem *ti = [TestItem new];
ti.a = [jsonDictionary objectForKey:@"a"];
ti.b = [jsonDictionary objectForKey:@"b"];
ti.c = [jsonDictionary objectForKey:@"c"];

iOS provides you a json parser. Look at this reply for more infos How do I deserialize a JSON string into an NSDictionary? (For iOS 5+)

(you should also check that the objects type match your expectations and eventually manage properly cases of error)

Mapping lib

Use a mapper library like JTObjectMapping that can help you to define how your object should be filled using the JSON. Usually I prefer this solution. It automatically checks for types and your code will be clearer.

Community
  • 1
  • 1
Luca Bartoletti
  • 2,435
  • 1
  • 24
  • 46
1

Use OCMapper to automate your mapping. it has the ability to automatically map all fields, and simple to use.

https://github.com/aryaxt/OCMapper

let request = Manager.sharedInstance.request(requestWithPath("example.com/users/5", method: .GET, parameters: nil))

request.responseObject(User.self) { request, response, user, error in
    println(user.firstName)
}
aryaxt
  • 76,198
  • 92
  • 293
  • 442