1

I'm working with a remote library that is delivering this object to me.

If I set my debugger, I get this information :

KandyChatMessage: UUID - 70886A79-2FF60F5E1A3961EF , timestamp - 2017-02-13 17:46:12 +0000 , sender - uri - 3@domain.domain.com, userName - 3, domain - domain.domain.com, type - 0, associationType - 1 , recipient - uri - afdab3bfb5774@domain.domain.com, userName - afdab3bfb57a12b5, domain - domain.domain.com, type - 1, associationType - 1 , type: - 1 , mediaItem - KandyTextMessageData - text:3 , info:(null) , isIncoming - 1 , additionalData - (null), fromHistory - NO

It is delivered via this method :

-(void)_addEventAndRefresh:(id<KandyEventProtocol>)event{

The goal is to convert this object into JSON with something like this :

NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:event
                                                   options:0
                                                     error:&error];

However, this crashes my app as I assume that event doesn't fulfill all the rules of a serializable NSMUtableArray or a NSDictionary for some reason.

This leaves me with two options. One, I can find some crafty method to convert whatever object this is into JSON. Or two, I can cherry-pick its data out and write an NSDictionary Object from scratch.

Would anyone have the slightest clue on how to pick this kind of object apart?

In my debugger, it doesn't seem to respond to anything..

> po event.UUID
=> error: property 'UUID' not found on object of type 'id'
> po event.timestamp
=> error: property 'timestamp' not found on object of type 'id'
dan
  • 9,695
  • 1
  • 42
  • 40
Trip
  • 26,756
  • 46
  • 158
  • 277
  • 1
    That's strange, since it's compliant with `KandyEventProtocol`, it should be able to responds to `uuid` (with minor case) and `timestamp` and `eventType`, according to the doc. NSLog(@"Class: %@", [event class])` returns what? Maybe `[event valueForKeyPath:@"uuid"]` will returns something. – Larme Feb 13 '17 at 18:44
  • @Larme Yours was the answer I was looking for. Thanks! – Trip Feb 13 '17 at 18:50
  • @Larme What's strange is that `valueForKeyPath` works with `po` but doesn't work in the actual application. I wonder what the benefit to developers is to have two unrelated methods in a realtime and debugging environment. – Trip Feb 13 '17 at 19:00
  • Could you log the class of the event? Also, since it's a protocol, my guess is that's it's not always the same class. Could you check that `[event conformsToProtocol:@protocol(KandyEventProtocol)]`, if not (that's weird), check if it respects one (http://stackoverflow.com/a/4649291/1801544) – Larme Feb 13 '17 at 19:06
  • @Larme That returns `error: cannot find protocol declaration for 'KandyEventProtocol'` – Trip Feb 13 '17 at 19:25

2 Answers2

1

You're right that the object is probably not serializable as JSON because it doesn't qualify in one or all of these ways. In a nutshell, it must be a collection of strings, numbers and other collections, and nothing else.

This answer suggests a way to get properties if you know the class. In a nutshell, it relies on objc_property_t *properties = class_copyPropertyList(klass, &outCount); to get the properties.

You can use that to get the object's JSON serializable properties, placing their values (keyed by their name) in a dictionary. Then JSON serialize that.

EDIT To demonstrate, I added this method to the PropertyUtil class suggested by that other answer...

+ (id)jsonSerializableFromObject:(id)object {
    if ([object isKindOfClass:[NSString self]] ||
        [object isKindOfClass:[NSNumber self]] ||
        [object isKindOfClass:[NSNull self]]) {
        return object;
    } else if ([object isKindOfClass:[NSArray self]]) {
        NSMutableArray *result = [NSMutableArray array];
        for (id e in (NSArray *)object) {
            id element = [self jsonSerializableFromObject:e];
            [result addObject:element];
        }
        return result;
    } else if ([object isKindOfClass:[NSArray self]]) {
        NSMutableDictionary *result = [NSMutableDictionary dictionary];
        for (id key in [(NSDictionary *)object allKeys])
            result[key] = [self jsonSerializableFromObject:(NSDictionary *)object[key]];
        return result;
    } else {
        NSMutableDictionary *result = [NSMutableDictionary dictionary];
        NSDictionary *props = [PropertyUtil classPropsFor:[object class]];
        for (NSString *propName in [props allKeys]) {
            id value = [object performSelector:NSSelectorFromString(propName)];
            result[propName] = [self jsonSerializableFromObject:value];
        }
        return (result.count)? result : [NSNull null];
    }
}

Just a quick sketch here: if the operand can be serialized to JSON, return it. If the operand is an array, answer an array of each element made json-serializable. Same if it's it a dictionary. Otherwise, if the object is something arbitrary, apply the introspection method to get it's properties, invoke the getter for each property and answer the json-serializable of that.

Community
  • 1
  • 1
danh
  • 62,181
  • 10
  • 95
  • 136
  • It's likely that I'm not used to debugger yet. But it seems the majority of methods don't with within the `po` syntax where as I believe they would work if I just write the code, and try to run the app. Is there a resource that you know of that would make it easier to work with this code rather than having to compile my entire app everytime I want to try another piece of code? – Trip Feb 13 '17 at 18:39
  • Also.. after implementing that class from your linked question, what command would run `PropertyUtil`'s code? – Trip Feb 13 '17 at 18:44
  • 1
    I think this is a common struggle. Sometimes when I'm slowed down this way, I make a little side project that does only thing I'm interested in debugging. You don't even need the external library. Just create an object that has some stuff in it that Doesn't serialize – danh Feb 13 '17 at 18:45
  • It's a class method so you have to invoke it on whichever class you added it to. It takes a class as parameter... you can send that to your ID as someId.class – danh Feb 13 '17 at 18:46
  • @Trip - I added a method to describe the recursive idea more completely. See if that generates what you need from your opaque object. – danh Feb 13 '17 at 20:59
  • This code : `jsonSerializableFromObject(event)` returns : Implicit declaration of function 'jsonSerializableFromObject' is invalid C99 – Trip Feb 13 '17 at 21:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/135629/discussion-between-danh-and-trip). – danh Feb 13 '17 at 21:11
  • Ah thanks so much for your help @danh . I super appreciate it. – Trip Feb 14 '17 at 10:44
0

So I managed to get a list of all methods like so :

int i=0; unsigned int mc = 0; 
Method * mlist = class_copyMethodList(event, &mc); 
NSLog(@"%d methods", mc); for(i=0;i<mc;i++)     
NSLog(@"Method no #%d: %s", i, sel_getName(method_getName(mlist[i])));

And then I realized I had to class cast the object to retrieve its items :

KandyChatMessage *chatMessage = (KandyChatMessage*)event;
chatMessage.description;
Trip
  • 26,756
  • 46
  • 158
  • 277