Keypath for first element in embedded NSArray - objective-c

This example is contrived, but it shows my point.
So, if I have an object graph like the following:
{
sex = male;
uid = 637650940;
work = ({
employer = {
id = 116420715044499;
name = "Software Engineer";
};
"end_date" = "0000-00";
"start_date" = "0000-00";
}, {
employer = {
id = 188733137832278;
name = "Apple";
};
});
},
//Some more objects
(This is an NSArray containing NSDictionarys that have an object of type NSArray).
The key field is work. I want a Key Path that will take the first object in the work array.
If I do this:
NSArray* work = [outerArrayObject objectForKey: #"work"];
id name = [work valueForKeyPath: #"employer.name"];
I get an array containing each name (In the above case, Software Engineer & Apple). Is there a collection operator or something to return the first object? Bonus points if you can develop a Key Path to sort each work by start_date also :)

#PauldeLange - Your answer and links were helpful.
The following simpler version works too (at least as of Xcode 6)
id name = [work valueForKeyPath: #"employer.name.#firstObject”];
In the above 'firstObject' refers to the predefined method on NSArray. If the second object is needed, you can define the following:
#implementation NSArray (CustomKVOOperators)
- (id) secondObject {
return [self count] >=2 ? self[1] : nil;
}
#end
And use:
id name = [work valueForKeyPath: #"employer.name.#secondObject”];

Well to answer my own question, one way to do it is this:
1) Define the following category
#implementation NSArray (CustomKVOOperators)
- (id) _firstForKeyPath: (NSString*) keyPath {
NSArray* array = [self valueForKeyPath: keyPath];
if( [array respondsToSelector: #selector(objectAtIndex:)] &&
[array respondsToSelector: #selector(count)]) {
if( [array count] )
return [array objectAtIndex: 0];
else
return nil;
}
else {
return nil;
}
}
#end
2) Use this KeyPath syntax
NSArray* work = [outerArrayObject objectForKey: #"work"];
id name = [work valueForKeyPath: #"#first.employer.name"];
Thanks to this clever person.

Related

What is the best way to build a one-to-many relationship?

I have an array of Videos objects with, among other things, the properties id and tags.
I want to build a dictionary whose key is a tag and whose value is an array of id's.
For example, some Video objects might look like this:
Video{ id:1, tags:[funny,political,humor] }
Video{ id:2, tags:[political,america] }
I want the result dictionary to look like this:
VideosWithTags["funny":[1]; "political":[1,2]; "humor":[1]; "america":[2]]
Is there a standard algorithm to accomplish this?
Currently I'm doing something like this:
for (NSDictionary *video in videos)
{
NSNumber *videoId = [video objectForKey:#"id"];
NSArray *tags = [video objectForKey:#"tags"];
for (NSString *tag in tags)
{
NSMutableArray *videoIdsForTag = nil;
if ([videosAndTags objectForKey:tag] != nil) //same tag with videoIds already exists
{
videoIdsForTag = [videosAndTags objectForKey:tag];
[videoIdsForTag addObject:videoId];
//add the updated array to the tag key
[videosAndTags setValue:videoIdsForTag forKey:tag];
}
else //tag doesn't exist yet, create it and add the videoId to a new array
{
NSMutableArray *videoIds = [NSMutableArray array];
[videoIds addObject:videoId];
//add the new array to the tag key
[videosAndTags setObject:videoIds forKey:tag];
}
}
}
You can make this look a little cleaner by using the new literal syntax.
I think you could benefit by making your if branches do less work. e.g. You would be better of trying to retrieve the videoIds array then if it doesn't exist - create it and add it to the videosAndTags object and then the code after this point can be consistent with no duplication of logic
for (NSDictionary *video in videos) {
NSNumber *videoId = video[#"id"];
NSArray *tags = video[#"tags"];
for (NSString *tag in tags) {
NSMutableArray *videoIds = videosAndTags[tag];
if (!videoIds) {
videoIds = [NSMutableArray array];
videosAndTags[tag] = videoIds;
}
// This is the only line where I manipulate the array
[videoIds addObject:videoId];
}
}
NSArray* videos =
#[#{ #"id" : #1, #"tags" : #[ #"funny", #"political", #"humor" ] },
#{ #"id" : #2, #"tags" : #[ #"political", #"america" ] } ];
NSMutableDictionary* videosAndTags = [NSMutableDictionary new];
// find distinct union of tags
NSArray* tags = [videos valueForKeyPath: #"#distinctUnionOfArrays.tags"];
// for each unique tag
for( NSString* tag in tags )
{
// filter array so we only have ones that have the right tag
NSPredicate* p = [NSPredicate predicateWithFormat: #"tags contains %#", tag];
videosAndTags[ tag ] = [[videos filteredArrayUsingPredicate: p] valueForKeyPath: #"id"];
}
Here is another approach using NSPredicate and valueForKeyPath.
I don't used them often, but sometimes they can prove to be useful.
(I think they call this the Functional Programming style of things, but I am not so sure)
NSPredicate reference
Key Value Coding

NSFormatter for BOOL

I have set up my simple Xcode project with a table that is binded to an array controller. It works fine if the array controller is full of entities with a string attribute. However I want to change the attribute to a BOOL and have the table show the string "true" or "false" based on the BOOL.
I have overrided the following two methods from NSFormatter:
-(NSString*) stringForObjectValue:(id)object {
//what is the object?
NSLog(#"object is: %#", object);
if(![object isKindOfClass: [ NSString class ] ] ) {
return nil;
}
//i'm tired....just output hello in the table!!
NSString *returnStr = [[NSString alloc] initWithFormat:#"hello"];
return returnStr;
}
-(BOOL)getObjectValue: (id*)object forString:string errorDescription:(NSString**)error {
if( object ) {
return YES;
}
return NO;
}
So the table gets populated with "hello" if the attribute is a string however if I switch it to a boolean, then the table gets populated with lots of blank spaces.
I don't know if this helps but on the line where I'm outputting the object, it outputs __NSCFString if the attribute is a string and "Text Cell" if I switch the attribute to a boolean. This is something else I don't understand.
Ok, it's not 100% clear what you're trying to do from the code, but first things first - BOOL is not an object, it's basically 0 or 1, so to place BOOL values into an array, you're probably best off using NSNumber:
NSNumber *boolValue = [NSNumber numberWithBool:YES];
and placing these into your array. Now you want to change your method:
-(NSString*) stringForObjectValue:(id)object {
NSNumber *number = (NSNumber *)object;
if ([number boolValue] == YES)
return #"true";
else
return #"false";
}
There's a few things here - for example, you want to avoid passing around id references if you can (if you know all your objects in the NSArray are NSNumber, you shouldn't need to).

iOS asserting NSMutableArray contains 2 objects, regardless of index

I have the following test case in my iOS application :
-(void) testTwoDefaultUsersExist
{
NSString * expected;
NSString * actual;
expected = #"John Smith";
actual = [[[userService getAllUsers]objectAtIndex:0] fullName];
STAssertEqualObjects(expected, actual, #"Not equal");
expected = #"Dave Brown";
actual = [[[userService getAllUsers]objectAtIndex:1] fullName];
STAssertEqualObjects(expected, actual, #"Not equal");
}
The above just checks that my call to [userService getAllUsers] returns 2 User objects, one with a name of John Smith, the other with Dave Brown. This appears to work fine for this scenario, but I have other cases where that ordering may change, so John may be placed in index 1 rather than 0
Question : How can I assert that the NSMutableArray, being returned from the call to [userService getAllUsers] contains those 2 objects, regardless of ordering?
Can you not simply use the NSArray method -containsObject:? An NSMutableArray is still an NSArray, so you can do:
NSArray * expected = [NSArray arrayWithObjects:#"John Smith", #"Dave Brown", nil];
NSArray * actual = [[userService getAllUsers] valueForKey:#"fullName"];
for(NSString * name in expected) {
STAssertTrue([actual containsObject:name], #"Missing name");
}
Note the (ab)use of -valueForKey: to transform an array of user objects into an array of NSString objects, making the -containsObject: call simpler. This will only work if your user object is key-value coding compliant for the fullName property.
NSMutableArray always contains the elements as you insert them to the array
You can iterate over the elements that you insert and test if they're at the NSArray using:
- (NSUInteger)indexOfObject:(id)anObject
If the object is not found it returns NSNotFound, that can be used with the Unit Test Framework that you choice
Greetings
Assert on equality like this
NSAssert1([userService getAllUsers].count == 2, #"SomeDescription", nil);
If you want to search an array for the existence of some strings, use the following function
- (BOOL) containsAllNames:(NSArray*)arrToSearch namesToSearch:(NSArray*)arr
{
BOOL containsAll = YES;
for (NSString *name in arr) {
BOOL containsCurrent = NO;
for (NSString *nameToSearch in arrToSearch) {
if ([name isEqualToString:nameToSearch]) {
containsCurrent = YES;
break;
}
}
if (!containsCurrent) {
containsAll = NO;
}
}
return containsAll;
}
Call it like
NSArray *toSearch = [NSArray arrayWithObjects:#"John Smith", #"Dave Brown", nil];
[self containsAllNames:YourArray namesToSearch:toSearch];

How to access array fields in sudzc.com SOAP result?

Sorry to perhaps ask stupid questions, but I'm still having issues with Objective-C syntax.
So, I've got this SOAP response from my sudzc.com generated code. It should contain a SQL SELECT result with veh_id and version as columns.
What I get as a response object is a NSMutableArray,
NSMutableArray* soapArray = (NSMutableArray*)value;
so I walk through it:
unsigned count = [soapArray count];
while (count--) {
id myobj = [soapArray objectAtIndex:count];
NSLog(#"myobj: %#", myobj);
}
What I get as a printout is something like:
myobj: {
item = {
key = version;
value = 1;
};
for each row of the SQL result. If this is a printout of the array element, why is there only the version column and not also the veh_id column?
How do I access the value for the key on the object myobj of type id? Do I have to cast it first?
That's the XML String returned from the Zend Soap-Server:
<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.[myurl].com/soap" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getActiveVehiclesResponse><return SOAP-ENC:arrayType="ns2:Map[23]" xsi:type="SOAP-ENC:Array"><item xsi:type="ns2:Map"><item><key xsi:type="xsd:string">veh_id</key><value xsi:type="xsd:string">1</value></item><item><key xsi:type="xsd:string">version</key><value xsi:type="xsd:string">1</value></item></item><item xsi:type="ns2:Map"><item><key xsi:type="xsd:string">veh_id</key><value xsi:type="xsd:string">3</value></item><item><key xsi:type="xsd:string">version</key><value xsi:type="xsd:string">1</value></item></item><item xsi:type="ns2:Map"><item><key xsi:type="xsd:string">veh_id</key><value xsi:type="xsd:string">4</value></item><item><key xsi:type="xsd:string">version</key><value xsi:type="xsd:string">1</value></item></item></return></ns1:getActiveVehiclesResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
No.. You don't need to cast it, since it shows all the data fetched, I am facing problem that the handler method value (of id type) returns first element only.
check following code:
...
request = [service myServiceCall:self action:#selector(myHandlerMethod:) param:param1];
...
}
-(void) myHandlerMethod:(id)value{
NSString *xmlString = [[NSString alloc] initWithData:request.receivedData encoding:NSUTF8StringEncoding];
// now if the "value" returned is type as some array of some object,then any arrays don't handle serialization of all the elements of the array it holds. The following code prints just outer tag. (e.g. )
NSLog("%#",xmlString);
}
Finally found the solution!
The problem lies within the deserializeAsDictionary function.
Since my Soap xml string is structured to have each database column as item - key - value - key - value etc, it adds each column under the key "item" and thus the deserializeAsDictionary function overwrites in the line
[d setObject:v forKey:[child name]]
the already added objects. In a first shot, I have added a column iterator and now call the columns "item1, item2,.." (further optimization might be necessary):
// Deserializes the element in a dictionary.
+(id)deserializeAsDictionary:(CXMLNode*)element {
NSLog(#"deserializeAsDictionary = %#, children: %d", element.stringValue, [element childCount]);
if([element childCount] == 1) {
CXMLNode* child = [[element children] objectAtIndex:0];
if([child kind] == CXMLTextKind) {
NSLog(#"child %# added", [child stringValue]);
return [[[element children] objectAtIndex:0] stringValue];
}
}
NSMutableDictionary* d = [NSMutableDictionary dictionary];
NSInteger i = 1;
NSString *objKey;
for(CXMLNode* child in [element children]) {
id v = [Soap deserialize:child];
if(v == nil) {
v = [NSNull null];
} else {
if([[child name] isEqualToString:#"(null)"]) {
objKey = [NSString stringWithFormat:#"%#",[child stringValue]];
} else if([[child name] isEqualToString:#"key"] || [[child name] isEqualToString:#"value"]) {
objKey = [NSString stringWithFormat:#"%#",[child name]];
} else {
objKey = [NSString stringWithFormat:#"%#%d",[child name],i++];
}
}
[d setObject:v forKey:objKey];
NSLog(#"child %# added", objKey);
}
return d;
}
The result array now looks like:
},
{
item1 = {
key = "veh_id";
value = 29;
};
item2 = {
key = version;
value = 1;
};
}

How to sort a Array based on it's object's property?

I have a NSArray of 'generic Objects' which contain the following properties
-name
-id
-type (question, topic or user)
How can I sort this array of generic object based on the generic object's type? E.g. I want to display all generic objects of type 'topic' on the top, followed by 'users' than 'questions'
You'll need to define a custom sorting function, then pass it to an NSArray method that allows custom sorting. For example, using sortedArrayUsingFunction:context:, you might write (assuming your types are NSString instances):
NSInteger customSort(id obj1, id obj2, void *context) {
NSString * type1 = [obj1 type];
NSString * type2 = [obj2 type];
NSArray * order = [NSArray arrayWithObjects:#"topic", #"users", #"questions", nil];
if([type1 isEqualToString:type2]) {
return NSOrderedSame; // same type
} else if([order indexOfObject:type1] < [order indexOfObject:type2]) {
return NSOrderedDescending; // the first type is preferred
} else {
return NSOrderedAscending; // the second type is preferred
}
}
// later...
NSArray * sortedArray = [myGenericArray sortedArrayUsingFunction:customSort
context:NULL];
If your types aren't NSStrings, then just adapt the function as needed - you could replace the strings in the order array with your actual objects, or (if your types are part of an enumeration) do direct comparison and eliminate the order array entirely.