13

Hello I a class of type NSObject:

ProductDetails *details = [[ProductDetails alloc] init];
details.name = @"Soap1";
details.color = @"Red";
details.quantity = 4;

I want to pass the "details" object to a dictionary.

I did,

NSDictionary *dict = [NSDictionary dictionaryWithObject:details forKey:@"details"];

I am passing this dict to another method which performs a check on JSONSerialization:

if(![NSJSONSerialization isValidJSONObject:dict])

And I am getting a crash on this check. Am I doing anything wrong here? I know that the details I am getting is a JSON object and I am assigning it to the properties in my ProductDetails class.

Please help me. I am a noob in Objective-C.

I now tried:

NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:(NSData*)details options:kNilOptions error:&error];

All I need here is an easy way to convert details to NSData.

I noticed that I have an array inside my object may be thats why all the ways I tried is throwing an exception. However since this question is becoming to big, I have started an another question thread for it where I have displayed the data I am getting inside the object - https://stackoverflow.com/questions/19081104/convert-nsobject-to-nsdictionary

Community
  • 1
  • 1
tech_human
  • 6,592
  • 16
  • 65
  • 107
  • What is the intent here? To share it with a server that needs JSON (in which case, we'll show you how to do that)? Or are you just trying to save this to some local file so you can load it later (in which case, `NSKeyedArchiver` and `NSKeyedUnarchiver` might be better)? – Rob Sep 29 '13 at 17:50
  • All of the objects in the "nest" need to be NSDictionary, NSArray, NSString, NSNumber, or NSNull. You can't have any other object types and convert to JSON using NSJSONSerialization. – Hot Licks Sep 29 '13 at 18:47
  • I posted the answer for swift user with the help of @thatzprem answer. I face the challenge so I wrote the Anser – Muhammad Danish Qureshi Jun 23 '22 at 07:11

11 Answers11

19

This may well be the easiest way to achieve it. Do import #import <objc/runtime.h> in your class file.

#import <objc/runtime.h>

ProductDetails *details = [[ProductDetails alloc] init];
details.name = @"Soap1";
details.color = @"Red";
details.quantity = 4;
NSDictionary *dict = [self dictionaryWithPropertiesOfObject: details];
NSLog(@"%@", dict);

//Add this utility method in your class.
- (NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    unsigned count;
    objc_property_t *properties = class_copyPropertyList([obj class], &count);

    for (int i = 0; i < count; i++) {
        NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
        [dict setObject:[obj valueForKey:key] forKey:key];
    }

    free(properties);

    return [NSDictionary dictionaryWithDictionary:dict];
}
thatzprem
  • 4,697
  • 1
  • 33
  • 41
  • 6
    This is dangerous metaprogramming. It assumes that the only thing that should be encoded is the various `@property` declared data and it also assumes that there will never be any other `@property` -- including ones in the superclass or added via a category -- that is intended to hold non-encodeable data. While this works, it'll be a maintenance nightmare. – bbum Sep 29 '13 at 18:00
  • 2
    Works as intended. A good solution for creating a dictionary from list of properties on an object. – isoiphone Aug 13 '14 at 20:37
  • 1
    worth checking if `[obj valueForKey:key]` is not nil before trying to add to dictionary, otherwise you risk your app crashing in the event theres no value – Edwin Jul 15 '17 at 19:01
14
NSDictionary *details = {@"name":product.name,@"color":product.color,@"quantity":@(product.quantity)};

NSError *error; 
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:details 
                                                   options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
                                                     error:&error];

if (! jsonData) {
    NSLog(@"Got an error: %@", error);
} else {
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}

Second part's source: Generate JSON string from NSDictionary in iOS

Community
  • 1
  • 1
mmackh
  • 3,550
  • 3
  • 35
  • 51
  • Thanks mmackh!! Nothing helped me may be since I have an array inside my object data. I have posted the question again with the exact data I am getting back in the object - http://stackoverflow.com/questions/19081104/convert-nsobject-to-nsdictionary – tech_human Sep 29 '13 at 17:37
  • 1
    Please delete your other question and reformulate the current one. – mmackh Sep 29 '13 at 17:40
3

As mmackh said, you want to define a custom method for your ProductDetails object that will return a simple NSDictionary of values, e.g.:

@implementation ProductDetails

- (id)jsonObject
{
    return @{@"name"     : self.name,
             @"color"    : self.color,
             @"quantity" : @(self.quantity)};
}

...

Let's assume that we added manufacturer property to our ProductDetails, which referenced a ManufacturerDetails class. We'd just write a jsonObject for that class, too:

@implementation ManufacturerDetails

- (id)jsonObject
{
    return @{@"name"     : self.name,
             @"address1" : self.address1,
             @"address2" : self.address2,
             @"city"     : self.city,
             ...
             @"phone"    : self.phone};
}

...

And then change the jsonObject for ProductDetails to employ that, e.g.:

@implementation ProductDetails

- (id)jsonObject
{
    return @{@"name"         : self.name,
             @"color"        : self.color,
             @"quantity"     : @(self.quantity),
             @"manufacturer" : [self.manufacturer jsonObject]};
}

...

If you have potentially nested collection objects (arrays and/or dictionaries) with custom objects that you want to encode, you could write a jsonObject method for each of those, too:

@interface NSDictionary (JsonObject)

- (id)jsonObject;

@end

@implementation NSDictionary (JsonObject)

- (id)jsonObject
{
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];

    [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        if ([obj respondsToSelector:@selector(jsonObject)])
            [dictionary setObject:[obj jsonObject] forKey:key];
        else
            [dictionary setObject:obj forKey:key];
    }];

    return [NSDictionary dictionaryWithDictionary:dictionary];
}

@end

@interface NSArray (JsonObject)

- (id)jsonObject;

@end

@implementation NSArray (JsonObject)

- (id)jsonObject
{
    NSMutableArray *array = [NSMutableArray array];

    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if ([obj respondsToSelector:@selector(jsonObject)])
            [array addObject:[obj jsonObject]];
        else
            [array addObject:obj];
    }];

    return [NSArray arrayWithArray:array];
}

@end

If you do something like that, you can now convert arrays or dictionaries of your custom objects object into something that can be used for generating JSON:

NSArray *products = @[[[Product alloc] initWithName:@"Prius"  color:@"Green" quantity:3],
                      [[Product alloc] initWithName:@"Accord" color:@"Black" quantity:1],
                      [[Product alloc] initWithName:@"Civic"  color:@"Blue"  quantity:2]];

id productsJsonObject = [products jsonObject];

NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:productsJsonObject options:0 error:&error];

If you're simply trying to save these objects in a file, I'd suggest NSKeyedArchiver and NSKeyedUnarchiver. But if you need to generate JSON objects for your own private classes, you can do something like the above might work.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
3

In .h File

#import <Foundation/Foundation.h>

@interface ContactDetail : NSObject
@property (nonatomic) NSString *firstName;
@property (nonatomic) NSString *lastName;
@property (nonatomic) NSString *fullName;
@property (nonatomic) NSMutableArray *mobileNumbers;
@property (nonatomic) NSMutableArray *Emails;
@property (assign) bool Isopen;
@property (assign) bool IsChecked;
-(NSDictionary *)dictionary;
@end

in .m file

#import "ContactDetail.h"
#import <objc/runtime.h>

@implementation ContactDetail

@synthesize firstName;
@synthesize lastName;
@synthesize fullName;
@synthesize mobileNumbers;
@synthesize Emails;

@synthesize IsChecked,Isopen;

//-(NSDictionary *)dictionary {
//    return [NSDictionary dictionaryWithObjectsAndKeys:self.fullName,@"fullname",self.mobileNumbers,@"mobileNumbers",self.Emails,@"emails", nil];
//}

- (NSDictionary *)dictionary {
    unsigned int count = 0;
    NSMutableDictionary *dictionary = [NSMutableDictionary new];
    objc_property_t *properties = class_copyPropertyList([self class], &count);

    for (int i = 0; i < count; i++) {

        NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
        id value = [self valueForKey:key];

        if (value == nil) {
            // nothing todo
        }
        else if ([value isKindOfClass:[NSNumber class]]
                 || [value isKindOfClass:[NSString class]]
                 || [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSMutableArray class]]) {
            // TODO: extend to other types
            [dictionary setObject:value forKey:key];
        }
        else if ([value isKindOfClass:[NSObject class]]) {
            [dictionary setObject:[value dictionary] forKey:key];
        }
        else {
            NSLog(@"Invalid type for %@ (%@)", NSStringFromClass([self class]), key);
        }
    }
    free(properties);
    return dictionary;
}
@end

if any crash ,You check the property (NSMutableArray,NSString,etc ) in else if condition inside of for.

In Your Controller, in any func...

-(void)addItemViewController:(ConatctViewController *)controller didFinishEnteringItem:(NSMutableArray *)SelectedContact
{
    NSLog(@"%@",SelectedContact);

    NSMutableArray *myData = [[NSMutableArray alloc] init];
    for (ContactDetail *cont in SelectedContact) {
        [myData addObject:[cont dictionary]];
    }

    NSError *error = nil;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myData options:NSJSONWritingPrettyPrinted error:&error];
    if ([jsonData length] > 0 &&
        error == nil){
//        NSLog(@"Successfully serialized the dictionary into data = %@", jsonData);
        NSString *jsonString = [[NSString alloc] initWithData:jsonData
                                                     encoding:NSUTF8StringEncoding];
        NSLog(@"JSON String = %@", jsonString);
    }
    else if ([jsonData length] == 0 &&
             error == nil){
        NSLog(@"No data was returned after serialization.");
    }
    else if (error != nil){
        NSLog(@"An error happened = %@", error);
    }
}
Mohamed Jaleel Nazir
  • 5,776
  • 3
  • 34
  • 48
2

Try this:

#import <objc/runtime.h>

+ (NSDictionary *)dictionaryWithPropertiesOfObject:(id)obj {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    unsigned count;
    objc_property_t *properties = class_copyPropertyList([obj class], &count);

    for (int i = 0; i < count; i++) {
        NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
        [dict setObject:[obj valueForKey:key] ? [obj valueForKey:key] : @"" forKey:key];
    }

    free(properties);

    return [NSDictionary dictionaryWithDictionary:dict];
}
刘俊利
  • 358
  • 4
  • 12
1

The perfect way to do this is by using a library for serialization/deserialization many libraries are available but one i like is JagPropertyConverter https://github.com/jagill/JAGPropertyConverter

it can convert your Custom object into NSDictionary and vice versa
even it support to convert dictionary or array or any custom object within your object (i.e Composition)

JAGPropertyConverter *converter = [[JAGPropertyConverter alloc]init];
converter.classesToConvert = [NSSet setWithObjects:[ProductDetails class], nil];


//For Object to Dictionary 
NSDictionary *dictDetail = [converter convertToDictionary:detail];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:dictDetail options:NSJSONWritingPrettyPrinted error:&error];
M.Shuaib Imran
  • 1,287
  • 16
  • 29
1

You can convert object (say modelObject) to dictionary at runtime with the help of objc/runtime.h class but that has certain limitations and is not recommended.

Considering MVC, mapping logic should be implemented in Model class.

@interface ModelObject : NSObject
@property (nonatomic) NSString *p1;
@property (nonatomic) NSString *p2;
-(NSDictionary *)dictionary;
@end


#import "ModelObject.h"

@implementation ModelObject
-(NSDictionary *)dictionary
{
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];

    [dict setValue:self.p1 forKey:@"p1"];// you can give different key name here if you want 
    [dict setValue:self.p2 forKey:@"p2" ];

    return dict;
}
@end

Uses:

NSDictionary *modelObjDict = [modelObj dictionary];
PANKAJ VERMA
  • 3,450
  • 1
  • 16
  • 15
0

Try using

NSDictionary *dict = [details valuesForAttributes:@[@"name", @"color"]];

And compare what the dictionary contains. Then try to convert it to JSON. And look at the JSON spec - what data types can go into a JSON encoded file?

Wain
  • 118,658
  • 15
  • 128
  • 151
  • Thanks Wain!! Nothing helped me here... May be because the data I am getting inside an object has an array too. I have posted the question again - http://stackoverflow.com/questions/19081104/convert-nsobject-to-nsdictionary – tech_human Sep 29 '13 at 17:36
  • I know, you shouldn't repost. Show all of your custom classes and the properties they have and what your desired JSON output is. Delete one of your 2 questions and update the other with all of the appropriate information. – Wain Sep 29 '13 at 17:38
0

You also can use the NSObject+APObjectMapping category which is available on GitHub: https://github.com/aperechnev/APObjectMapping

It's a quit easy. Just describe the mapping rules in your class:

#import <Foundation/Foundation.h>
#import "NSObject+APObjectMapping.h"

@interface MyCustomClass : NSObject
@property (nonatomic, strong) NSNumber * someNumber;
@property (nonatomic, strong) NSString * someString;
@end

@implementation MyCustomClass
+ (NSMutableDictionary *)objectMapping {
  NSMutableDictionary * mapping = [super objectMapping];
  if (mapping) {
    NSDictionary * objectMapping = @{ @"someNumber": @"some_number",
                                      @"someString": @"some_string" };
  }
  return mapping
}
@end

And then you can easily map your object to dictionary:

MyCustomClass * myObj = [[MyCustomClass alloc] init];
myObj.someNumber = @1;
myObj.someString = @"some string";
NSDictionary * myDict = [myObj mapToDictionary];

Also you can parse your object from dictionary:

NSDictionary * myDict = @{ @"some_number": @123,
                           @"some_string": @"some string" };
MyCustomClass * myObj = [[MyCustomClass alloc] initWithDictionary:myDict];
Alexander Perechnev
  • 2,797
  • 3
  • 21
  • 35
0

Swift

Now the swift is very popular and most of the SDK's are written in Objective C, we need to convert NSObject to NSDictionary, With the Help of @thatzprem Answer, I wrote an extension for Swift which will convert our NSObject into NSDictionary, then we can use that NSDictionary to simple Dictionary or JSON Object or other purpose. I hope so this will help out the Swift User.

extension NSObject {
func convertNSObjectToNSDictionary() -> [AnyHashable : Any]? {
        var dict: [AnyHashable : Any] = [:]

        var count: UInt32 = 0
        let properties = class_copyPropertyList(type(of: self), UnsafeMutablePointer<UInt32>(mutating: &count)) //as? objc_property_t

        for i in 0..<Int(count) {
            var key: String? = nil
            if let property = properties?[i] as? objc_property_t {
                key = String(utf8String: property_getName(property))
            }
            //dict[key] = (obj as? NSObject)?.value(forKey: key ?? "")
            dict[key] = (self).value(forKey: key ?? "")
        }

        free(properties)

        return dict
    }
}
0

Boy, what a mess!!! from the hilarious digging into ObjC runtime to create a general-purpose serializer for any object hierarchy, to the cumbersome setting one-by-one dictionary entries -- have everyone forgot KVC?

Here's the documentation from NSKeyValueCoding.h:

/* Given an array of keys, return a dictionary containing the keyed attribute values, to-one-related objects, and/or collections of to-many-related objects. Entries for which -valueForKey: returns nil have NSNull as their value in the returned dictionary.
*/
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

and the line of code to use in the OP's specific question:

ProductDetails *details = [[ProductDetails alloc] init];
details.name = @"Soap1";
details.color = @"Red";
details.quantity = 4;

is:

NSDictionary *dict = [details dictionaryWithValuesForKeys: @[@"name", @"color", @"quantity"] ]; 

Two notes:

  • The reason to specify the keys - is that for a custom class you would frequently have "private properties" you do NOT want to serialize into your JSON, so here you can select which ones go out.

  • All basic Cocoa collections (NSArray, NSSet, NSDictionary) can't contain nil values (or keys for that matter) so if details.name is nil - attempt to set it as a dictionary value would crash. The solution: the method automatically places an NSNull instance ([NSNull null]) as the value, and you ALWAYS get the keys you want. BTW - JSON accepts "NULL" values and NSJSONSerialization will correctly convert these NSNulls into JSON's NULL representation.

I think this is the preferred solution to the OP's specific scenario.

Motti Shneor
  • 2,095
  • 1
  • 18
  • 24