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

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;
};
}

Related

NSJSONSerialization returns NSNumber(double) causes failure comparing to NSNumber(Int64)

I have an Int64 (long-long) uniqueID passed as a number in a JSON file.
{ "id1" : 4627596562884205957 }
The NSJSONSerialization always returns an NSNumber (double) in the NSDictionary property. This should be an Int64 number NSNumber (long-long), as the double loses a few digits of needed precision.
It causes a failure, as the code is comparing the deserialized value (double) against the proper values (Int64), and for cases where the two numbers are very close, but still different, they compare as equal, which is the wrong logic.
Can NSJSONSerialization be configured to return the proper NSNumber(long-long) for this property, instead of the incorrect double?
Is it OK to pull the long-long value out of a NSNumber(double), then force it back into a new NSNumber(long-long)? It seems to work, but I don't trust it.
The following shows the error.
- (void) doJSONDoubleVsInt64Comparison
{
// our primary Int64
int64_t aId1 = 4627596562884205957;
// put it into a JSON string, then read it into a dictionary.
NSString *jsonInputString = [NSString stringWithFormat:#"{ \"id1\" : %lld }", aId1];
NSData *jsonData = [jsonInputString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:false];
NSDictionary *asDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
// the number after being deserialized from the JSON string
NSNumber *id1Number = [asDictionary objectForKey:#"id1"];
// the NSNumber is of type 'double' (not long-long as expected)
[self logNSNumber:id1Number named:#"id1"];
// get a DIFFERENT Int64 that varies only slightly
int64_t aId2 = 4627596562884205592;
NSNumber *id2Number = [NSNumber numberWithLongLong:aId2];
// This is of type Int64 (long-long) as expected.
[self logNSNumber:id2Number named:#"id2"];
// id1 and id2 are not equal numbers, but this comparison returns TRUE - why?
if( [id1Number isEqualToNumber:id2Number] )
{
// we incorrectly hit this condition.
NSLog(#"ERROR: id1 and id2 were reported to be equal, but they are different numbers.");
}
else{
NSLog(#"OK: id1 and id2 are not equal, as expected.");
}
// pull the long-long from the double - force it to be an Int64
NSNumber *id1NumberAsInt64 = [NSNumber numberWithLongLong:id1Number.longLongValue];
// the comparison now works.
[self logNSNumber:id1NumberAsInt64 named:#"id1AsInt64"];
if( [id1NumberAsInt64 isEqualToNumber:id2Number] )
{
NSLog(#"ERROR: id1 and id2 were reported to be equal, but they are different.");
}
else{
NSLog(#"OK: id1 and id2 are not equal, as expected.");
}
}
- (void)logNSNumber:(NSNumber *)aNumber named:(NSString *)name
{
CFNumberType numberType = CFNumberGetType((CFNumberRef)aNumber);
switch ( numberType )
{
case kCFNumberSInt64Type:
NSLog(#"%# returned as an Int64", name); // does NOT hit this
break;
case kCFNumberDoubleType:
NSLog(#"%# returned as a double", name); // hits this
break;
default:
NSLog(#"%# NumberType = %ld", name, numberType );
break;
}
}

Get object by key with certain value from NSDictionary

I have NSDictionary with objects. The NSDictionary is consist of json(You can see it below). I need to populate my table view with the name by id. And the id can repeat. It means I can have several "name" with id which is equal 0. I should get name by key with certain value from the dictionary. Here is my NSDictionary:
{
name = "smth1";
id = 0;
},
{
name = "smth2";
id = 1;
},
{
name = "smth3";
id = 2;
},
{
name = "smth4";
id = 2;
},
...
For example, I want to get value of key "name" where id is 2. Then I will get name = "smth3" and name = "smth4". Generally, I am trying to populate my table view component with the nested data. How can I do this? Any tips, ideas. Thank you.
Actually you show an array of dictionaries, where each dictionary has 2 keys.
You could use the NSArray function indexOfObjectPassingTest. That code might look like this (starting from an NSArray) :
int idToFind = 2;
NSArray *dictArray = #[#{#"name": #"smth1",
#"id": #(0)},
#{#"name": #"smth2",
#"id": #(1)},
#{#"name": #"smth3",
#"id": #(2)}
];
NSUInteger dictIndex =
[dictArray indexOfObjectPassingTest: ^BOOL(
NSDictionary *dict,
NSUInteger idx,
BOOL *stop) {
return [dict[#"id"] intValue] == idToFind;
}];
if (dictIndex != NSNotFound) {
NSLog(#"Found id %d with name %#", idToFind, dictArray[dictIndex][#"name"]);
}
}
EDIT:
If you need to match all of the items then you need to use the NSArray method indexesOfObjectsPassingTest. That finds all the items in an array that match.
That code would look like this:
int idToFind = 2;
NSArray *dictArray = #[#{#"name": #"smth1",
#"id": #(0)},
#{#"name": #"Fred",
#"id": #(2)},
#{#"name": #"smth2",
#"id": #(1)},
#{#"name": #"smth3",
#"id": #(2)},
];
NSIndexSet *dictIndexes;
dictIndexes =
[dictArray indexesOfObjectsPassingTest: ^BOOL(NSDictionary *dict,
NSUInteger idx,
BOOL *stop)
{
return [dict[#"id"] intValue] == idToFind;
}];
if (dictIndexes.count == 0) {
NSLog(#"No matches found");
}
[dictIndexes enumerateIndexesUsingBlock:^(NSUInteger index,
BOOL * _Nonnull stop)
{
NSLog(#"Found id=%# with name \"%#\" at index %lu",
dictArray[index][#"id"],
dictArray[index][#"name"],
index);
}];
Both approaches above use methods that take a block. The block contains code that you write that returns a BOOL for the item(s) that match your desired search criteria.
Search/sort methods that take blocks are very flexible because you can provide any code you want to do the matching/comparison.
The indexOfObjectPassingTest method searches for a single object in your array and stops when it finds the first match.
In contrast, the indexesOfObjectsPassingTest function will match multiple items. It returns an NSIndexSet, a special class that's used to index into NSArrays.
There is a function enumerateIndexesUsingBlock that invokes a block of code for each index specified in the array.
We could also have used the method objectsAtIndexes to extract only the elements in the array that are listed in the resulting index set, and then used for...in to loop through the items. That code would look like this:
NSArray *filteredArray = [dictArray objectsAtIndexes: dictIndexes];
for (NSDictionary *aDict in filteredArray) {
NSLog(#"Found id=%# with name \"%#\"", aDict[#"id"], aDict[#"name"]);
}
Note that this sort of thing is simpler and cleaner in Swift. We could use a filter statement on the array and provide a closure that selects items that match our search criteria

Keypath for first element in embedded NSArray

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.

Retrieving values from json using objective-c

I am currently trying to work with json and objective-c however having a bit of difficulty. The following is the json that is being returned
{
sethostname = (
{
msgs = "Updating Apache configuration\nUpdating cPanel license...Done. Update succeeded.\nBuilding global cache for cpanel...Done";
status = 1;
statusmsg = "Hostname Changed to: a.host.name.com";
warns = (
);
});
}
I am able to check that the response is coming back and the key is sethostname however no matter what I try I cannot get for example the value of status or statusmsg. Can anyone point me in the right location. The following is basic code I am using to check that sethostname is returned.
NSError *myError = nil;
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&myError];
NSLog([res description]);
NSArray *arr;
arr = [res allKeys];
if ([arr containsObject:#"sethostname"])
{
NSLog(#"worked");
}
When in doubt, write down the structure of your JSON data. For example:
{
sethostname = (
{
msgs = "Updating Apache configuration\nUpdating cPanel license...Done. Update succeeded.\nBuilding global cache for cpanel...Done";
status = 1;
statusmsg = "Hostname Changed to: a.host.name.com";
warns = (
);
});
}
(which is in NeXTSTEP property list format, actually) means that you have a top-level dictionary. This top-level dictionary contains a key called sethostname whose value is an array. This array is comprised of dictionaries, each dictionary having a set of keys: msgs, status, statusmsg, warns. msgs has a string value, status has a number value, statusmsg has a string value,warns` has an array value:
dictionary (top-level)
sethostname (array of dictionaries)
dictionary (array element)
msgs (string)
status (number)
statusmsg (string)
warns (array)
??? (array element)
Having understood this structure, your code should look like:
NSError *myError = nil;
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&myError];
if (!res) { // JSON parser failed }
// dictionary (top-level)
if (![res isKindOfClass:[NSDictionary class]]) {
// JSON parser hasn't returned a dictionary
}
// sethostname (array of dictionaries)
NSArray *setHostNames = [res objectForKey:#"sethostname"];
// dictionary (array element)
for (NSDictionary *setHostName in setHostNames) {
// status (number)
NSNumber *status = [setHostName objectForKey:#"status"];
// statusmsg (string)
NSString *statusmsg = [setHostName objectForKey:#"statusmsg"];
…
}
Why not use the simplest JSON method - [myString jsonValue];
It's part of this JSON framework for objective-c
I don't think if ([arr containsObject:#"sethostname"]) is going to work, because the results array is not going to contain that exact object. It might contain an object with the same content, but it won't be the SAME object.
As jtbandes wrote, you need to log the actually output. NSLog both res and arr and see what you have.

How do I traverse a multi dimensional NSArray?

I have array made from JSON response.
NSLog(#"%#", arrayFromString) gives the following:
{
meta = {
code = 200;
};
response = {
groups = (
{
items = (
{
categories = (
{
icon =
"http://foursquare.com/img/categories/parks_outdoors/default.png";
id = 4bf58dd8d48988d163941735;
and so on...
This code
NSArray *arr = [NSArray arrayWithObject:[arrayFromString valueForKeyPath:#"response.groups.items"]];
gives array with just one element that I cannot iterate through. But if I write it out using NSLog I can see all elements of it.
At the end I would like to have an array of items that I can iterate through to build a datasource for table view for my iPhone app.
How would I accomplish this?
EDIT:
I have resolved my issue by getting values from the nested array (objectAtIndex:0):
for(NSDictionary *ar in [[arrayFromString valueForKeyPath:#"response.groups.items"] objectAtIndex:0]) {
NSLog(#"Array: %#", [ar objectForKey:#"name"]);
}
First, the data structure you get back from the JSON parser is not an array but a dictionary: { key = value; ... } (curly braces).
Second, if you want to access a nested structure like the items, you need to use NSObject's valueForKeyPath: method. This will return an array of all items in your data structure:
NSLog(#"items: %#", [arrayFromString valueForKeyPath:#"response.groups.items"]);
Note that you will loose the notion of groups when retrieving the item objects like this.
Looking at the JSON string you posted, response.groups.items looks to be an array containing one item, a map/dictionary containing one key, "categories." Logging it out to a string is going to traverse the whole tree, but to access it programmatically, you have to walk the tree yourself. Without seeing a more complete example of the JSON, it's hard to say exactly what the right thing to do is here.
EDIT:
Traversing an object graph like this is not that simple; there are multiple different approaches (depth-first, breadth-first, etc,) so it's not necessarily something for which there's going to be a simple API for you to use. I'm not sure if this is the same JSON library that you're using, but, for instance, this is the code from a JSON library that does the work of generating the string that you're seeing. As you can see, it's a bit involved -- certainly not a one-liner or anything.
You could try this, which I present without testing or warranty:
void __Traverse(id object, NSUInteger depth)
{
NSMutableString* indent = [NSMutableString string];
for (NSUInteger i = 0; i < depth; i++) [indent appendString: #"\t"];
id nextObject = nil;
if ([object isKindOfClass: [NSDictionary class]])
{
NSLog(#"%#Dictionary {", indent);
NSEnumerator* keys = [(NSDictionary*)object keyEnumerator];
while (nextObject = [keys nextObject])
{
NSLog(#"%#\tKey: %# Value: ", indent, nextObject);
__Traverse([(NSDictionary*)object objectForKey: nextObject], depth+1);
}
NSLog(#"%#}", indent);
}
else if ([object isKindOfClass: [NSArray class]])
{
NSEnumerator* objects = [(NSArray*)object objectEnumerator];
NSLog(#"%#Array (", indent);
while (nextObject = [objects nextObject])
{
__Traverse(nextObject, depth+1);
}
NSLog(#"%#)", indent);
}
else
{
NSLog(#"%#%#",indent, object);
}
}
void Traverse(id object)
{
__Traverse(object, 0);
}