Question

I'm trying to parse through an XML I'm getting from a webservice with an NSXMLParserDelegate. The NSObject I want to put the data into (I'll just call it MyObject) has @propertys that are not NSStrings (there are some NSDates, some NSNumbers, and maybe at some point in the future some primitives).

Since the XML is all a big string, I'm getting errors and crashes when I try to just directly put the values from the XML into my properties (I'm using [myObject setValue:currentElementValue forKey:elementName]; in my parser:didEndElement:..., which normally works fine - when key is a NSString). I didn't really expect this to work, but I figured it was worth a shot.

The easiest thing to do is just hardcode all of it, so if I have an '@property (nonatomic, strong) NSNumber *age;' on my object, when I find the <age>25</age> part of my XML, I do MyObject.age = [NSNumber numberWithInt:[currentElementValue intValue]];. The problem with this is it is very rigid - I'd like this code to be more dynamic than that. I don't want to have to know what all the properties of my object are ahead of time.

I've also tried checking what the class of the property is, then doing some conversion before entering a value (so something like [myObject setValue:[NSNumber numberWithInt:[currentElementValue intValue] forKey:elementName]; if the property isKindOfClass:[NSNumber class]]. The problem with this is that this is the first time that property is used, so it is nil, and therefore doesn't have a class. So this approach doesn't really work either.

Am I doing this completely wrong? It seems like it should be fairly typical to get data from an XML and put it into non-NSString variables, but I can't seem to get it to work.

If anyone needs to see more code to get a better understanding of how I've this is set up or what I've already tried, let me know.


EDIT: I've come up with a quick example of what I'm trying to do. Suppose I have the following XML:

<Object>
    <ITEM_NUMBER>4</ITEM_NUMBER>
    <IS_AWESOME>YES</IS_AWESOME>
    <AWESOMENESS>9000.1</AWESOMENESS>
</Object>

that I want to parse into an instance of MyObject (an NSObject subclass)

@interface MyObject: NSObject

@property (nonatomic, assign) int ITEM_NUMBER;
@property (nonatomic, assign) BOOL IS_AWESOME;
@property (nonatomic, assign) float AWESOMENESS;

@end

And a second XML

<OtherObject>
    <PRICE>4.99</PRICE>
    <QUANTITY>5</QUANTITY>
    <FOR_RESALE>NO</FOR_RESALE>
</OtherObject>

that will go into an instance of OtherObject (also NSObject subclass)

@interface OtherObject: NSObject

@property (nonatomic, assign) int QUANTITY;
@property (nonatomic, assign) BOOL FOR_RESALE;
@property (nonatomic, assign) float PRICE;

@end

I want to create a "template" parser (which has @property (nonatomic, strong) NSObject *instance; and then NSMutableString *currentElementValue as an ivar)

...
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    [currentElementValue setString:string];
}

- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if ([[self.instance valueForKey:elementName] isKindOfClass:int])
    {
        [self.instance setValue:[currentElementValue intValue] forKey:elementName];
    }
    else if ([[self.instance valueForKey:elementName] isKindOfClass:float])
    {
        [self.instance setValue:[currentElementValue floatValue] forKey:elementName];
    }
    else if ([[self.instance valueForKey:elementName] isKindOfClass:BOOL])
    {
        [self.instance setValue:[currentElementValue boolValue] forKey:elementName];
    }
    else if ...
}
...

Then, I just subclass that "template" parser for each of my different XMLs and overwrite the init method so that

self.instance = [[MyObject alloc] init];

or

self.instance = [[OtherObject alloc] init];

as appropriate. I have a lot of webservices I'm calling that I will be getting similarly structured XML from, so only having to change 1 line in the parsers for each is something that is very desirable. Having all the rest of the parser code in a single "template" class will make maintaining, reading, and debugging the code so much easier.

Obviously, the problem I'm having is isKindOfClass doesn't accept int, float, or BOOL as inputs. Is there anything similar that I can use instead?

Was it helpful?

Solution

The problem with this is that this is the first time that property is used, so it is nil, and therefore doesn't have a class.

Objective-C allows to query class properties at runtime, so introspecting the property type and using an appropriate trnsformation is one way to go.

You can read more about runtime at Mike Ash's blog or see Objective C Introspection/Reflection.

However, this specific problem has been encountered by many other people, so there are quite a few solutions: as said in iOS Most mature REST service library RESTKit is one good example of such a library; MagicRecord is another.

You can easily find a couple more libraries to check out before you decide to implement your own solution.

OTHER TIPS

Of course I could just override the init method of MyObject to alloc-init all my properties to empty values, which should solve the nil problem I referenced in my OP, but that just feels really hacky. It may be the only option though.


EDIT: this approach totally falls apart with the example in the EDIT in the OP. KVC will turn ints, floats, and BOOLs all to NSNumbers, without any way of knowing what they originally where. Which means I won't be able to call [currentElementValue intValue], [currentElementValue floatValue], or [currentElementValue boolValue] as appropriate.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top