Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested NSDictionary Description Escape Character Problems

I am trying to print out an objectified JSON structure that I am saving in multiple dictionaries in a hierarchy. I would like to do this through NSObject's description method so that each nested dictionary's description method gets called returning its value as well.

Desired Result

        //First Hierarchal Level
                //Second Hierarchal Level
                        //Third Hierarchal Level
People = 
(
    {
        Name = Person 1
        Gender = Male
        Addresses =
        (   
            {
                Address Name = Home Address
                Street = 123 Street St.
                City = NewCity
                State = AZ
                Zip = 12345
                Country = USA
             }
        )
        Cars = 
        (   
            {
                Make = Ford
                Model = NewFord
                Insured Drivers = 
                (
                    {
                        Name = Person 1
                    },
                    {
                        Name = Person 2
                    }
                )
            }
        )   
    }
)

//NOTE: Sample untested nested structure

However, I have been running into an issue that the return string for each nested dictionary is getting escaped once for each level of the hierarchy that the return string gets passed through.

Actual Result

People = \n (\n {\n Name = Person 1\\\n Gender = Male\\\n Addresses =\\\n ( \\\n {\\\n Address Name = Home Address\\\n Street = 123 Street St.\\\n City = NewCity\\\n State = AZ\\\n Zip = 12345\\\n Country = USA\\\n }\\\n )\\\n Cars = \\\n ( \\\n {\\\\\\\n Make = Ford\\\\\\\n Model = NewFord\\\\\\\n Insured Drivers = \\\\\\\n (\\\\\\\n {\\\\\\\\\\\\\n Name = Person 1\\\\\\\\\\\\\n },\\\\\\\\\\\\\n {\\\\\\\\\\\\\n Name = Person 2\\\\\\\\\\\\\n }\\\\\\\n )\\\\\\\n }\\\n ) \n }\n )

I have read that this has to do with how description adds these escape characters due to it using something like a syslog utility, however I believe the functionality that I desire is available due to how NSArray describes its contents in a similar way to the way in which I would like to. I have tried iterating through the result string and parsing out the escape characters but so far the best result that I have come up with is a non-hierarchical list of all the properties in all dictionaries.

Best Attempt

People = 
(
{
Name = Person 1
Gender = Male
Addresses =
( 
{
Address Name = Home Address
Street = 123 Street St.
City = NewCity
State = AZ
Zip = 12345
Country = USA
}
)
Cars = 
( 
{
Make = Ford
Model = NewFord
Insured Drivers = 
(
{
Name = Person 1
},
{
Name = Person 2
}
)
}
) 
}
)

I was wondering if anyone else has come across this problem and how they overcame it.

Any and all suggestions welcome. Thanks for looking.

UPDATE 1: As per the advice in the comments, I have tried parsing my dictionary objects to JSON strings for printing using the following NSDictionary category method:

-(NSString*)JSONDescription
{
    NSError *error;
    NSData* jsonData = [NSJSONSerialization dataWithJSONObject:self options:NSJSONWritingPrettyPrinted error:&error];
    NSString* json = nil;

    if (! jsonData) {
        NSLog(@"WARNING: NSDictionary JSONDescription encountered error \"%@\"", error);
    } else {
        json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    }

    return json;
}

Then, at each level of of my hierarchy, I am calling my dictionary object's JSONDescription in each description method. However, it does not appear that the nested object's description method is being called. This is causing the following exception:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (Address)'

Example Implementation

#import "Person.h"

#define NAME @"Name"
#define GENDER @"Gender"
#define ADDRESSES @"Addresses"
#define CARS @"Cars"

@implementation Person

-(NSDictionary*)toDictionary{
    return @{ NAME: self.name,
              GENDER: self.gender,
              ADDRESSES: self.addresses,
              CARS: self.cars};
}

-(NSString*)description{
    return self.toDictionary.JSONDescription;
}

@end


#import "Address.h"

#define ADDRESS_NAME @"Address Name"
#define STREET @"Street"
#define CITY @"City"
#define STATE @"State"
#define ZIP @"Zip"
#define COUNTRY @"Country"

@implementation Address

-(NSDictionary*)toDictionary{
    return @{ ADDRESS_NAME: self.addressName,
              STREET: self.street,
              CITY: self.city,
              STATE: self.state,
              ZIP: self.zip,
              COUNTRY: self.country};
}

-(NSString*)description{
    return self.toDictionary.JSONDescription;
}

@end


#import "Car.h"

#define MAKE @"Make"
#define MODEL @"Model"
#define INSURED_DRIVERS @"Insured Drivers"

@implementation Car

-(NSDictionary*)toDictionary{
    return @{ MAKE: self.make,
              MODEL: self.model,
              INSURED_DRIVERS: self.drivers};
}

-(NSString*)description{
    return self.toDictionary.JSONDescription;
}

@end


#import "Driver.h"

#define NAME @"Name"

@implementation Car

-(NSDictionary*)toDictionary{
    return @{ NAME: self.name};
}

-(NSString*)description{
    return self.toDictionary.JSONDescription;
}

@end
like image 830
Krejko Avatar asked Apr 16 '13 16:04

Krejko


1 Answers

The following method might not be the most elegant, but it seems to produce the desired output:

@interface NSObject (MyPrettyPrint)
- (NSString *)prettyPrint;
@end

// === ADDED CODE FOR CUSTOM OBJECTS (1) ===
@protocol ObjectToDictionary <NSObject>
-(NSDictionary *)toDictionary;
@end
// === END OF ADDED CODE (1) ===

@implementation NSObject (MyPrettyPrint)

- (NSString *)prettyPrint
{
    BOOL isColl;
    return [self prettyPrintAtLevel:0 isCollection:&isColl];
}

- (NSString *)prettyPrintAtLevel:(int)level isCollection:(BOOL *)isCollection;
{

// === ADDED CODE FOR CUSTOM OBJECTS (2) ===
    if ([self respondsToSelector:@selector(toDictionary)]) {
        NSDictionary *dict = [(id <ObjectToDictionary>)self toDictionary];
        return [dict prettyPrintAtLevel:level isCollection:isCollection];
    }
// === END OF ADDED CODE (2) ===

    NSString *padding = [@"" stringByPaddingToLength:level withString:@" " startingAtIndex:0];
    NSMutableString *desc = [NSMutableString string];

    if ([self isKindOfClass:[NSArray class]]) {
        NSArray *array = (NSArray *)self;
        NSUInteger cnt = [array count];
        [desc appendFormat:@"%@(\n", padding];
        for (id elem in array) {
            BOOL isColl;
            NSString *s = [elem prettyPrintAtLevel:(level+3) isCollection:&isColl];
            if (isColl) {
                [desc appendFormat:@"%@", s];
            } else {
                [desc appendFormat:@"%@   %@", padding, s];
            }
            if (--cnt > 0)
                [desc appendString:@","];
            [desc appendString:@"\n"];
        }
        [desc appendFormat:@"%@)", padding ];
        *isCollection = YES;

    } else if ([self isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dict = (NSDictionary *)self;
        [desc appendFormat:@"%@{\n", padding];
        for (id key in dict) {
            BOOL isColl;
            id value = dict[key];
            NSString *s = [value prettyPrintAtLevel:(level+3) isCollection:&isColl];
            if (isColl) {
                [desc appendFormat:@"   %@%@ =\n%@\n", padding, key, s];
            } else {
                [desc appendFormat:@"   %@%@ = %@\n", padding, key, s];
            }
        }
        [desc appendFormat:@"%@}", padding ];
        *isCollection = YES;

    } else {
        [desc appendFormat:@"%@", self];
        *isCollection = NO;
    }

    return desc;
}

Example:

NSDictionary *dict = @{@"People": @[
    @{
        @"Name": @"Person 1",
        @"Gender": @"Male",
        @"Addresses": @[
            @{
                @"Address Name": @"Home Address",
                @"Street": @"123 Street St.",
                @"Zip": @12345
            },
        ],
        @"Cars": @[
           @{
                @"Make": @"Ford",
                @"Model": @"NewFord",
                @"Insured Drivers": @[
                    @{@"Name": @"Person 1"},
                    @{@"Name": @"Person 2"},
                ]
            },
        ],
   },

]};

NSLog(@"People =\n%@", [dict[@"People"] prettyPrint]);

Output:

People =
(
   {
      Name = Person 1
      Gender = Male
      Cars =
      (
         {
            Model = NewFord
            Make = Ford
            Insured Drivers =
            (
               {
                  Name = Person 1
               },
               {
                  Name = Person 2
               }
            )
         }
      )
      Addresses =
      (
         {
            Zip = 12345
            Address Name = Home Address
            Street = 123 Street St.
         }
      )
   }
)
like image 118
Martin R Avatar answered Nov 05 '22 20:11

Martin R