I have a complex JSON file that I need to parse into a CoreData table. Currently, I capture the data into an NSArray with this format and the following 6 elements:
2013-08-29 10:54:04.930 iTrackTest[1542:c07] athleteRecords[0]: #SchoolID
2013-08-29 10:54:04.930 iTrackTest[1542:c07] athleteRecords[1]: #LastName
2013-08-29 10:54:04.930 iTrackTest[1542:c07] athleteRecords[2]: #Gender
2013-08-29 10:54:04.931 iTrackTest[1542:c07] athleteRecords[3]: SchType
2013-08-29 10:54:04.931 iTrackTest[1542:c07] athleteRecords[4]: #FirstName
2013-08-29 10:54:04.931 iTrackTest[1542:c07] athleteRecords[5]: #IDAthlete
First question, it appears that SchType is a k-dimensional NSArray of NSDictionaries. Is that true?
I have been capturing simpler, single-tiered JSON files using code from Paul Hegarty of Stanford:
dispatch_async(fetchQ, ^{
NSArray *athleteRecords;
athleteRecords = [AthleticNetDataFetcher retrieveDataForAthleteWithID:athleteID];
NSLog(#"In %#: athleteRecords has %d records",NSStringFromClass([self class]), [athleteRecords count]);
NSLog(#"NSArray with athleteRecords: %#", athleteRecords);
[document.managedObjectContext performBlock:^{
int iCount=0;
for (NSDictionary *athleteInfo in athleteRecords) {
[self resultsWithAthleteInfoForAthleteWithID:athleteInfo inManagedObjectContext:document.managedObjectContext];
NSLog(#"athleteRecords[%d]: %#", iCount, athleteInfo);
iCount++;
}
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
}];
});
I need data elements from each node for every record in my CoreData table. For example, SchoolName from School node, IDSeason from Season node, and all elements from Results node would be written to a single CoreData table row (record).
Do I need to resort to dot notation and abandon the iteration through the NSArray or do I need to capture multiple NSArrays each with data further down the nodes? Having a hard time getting my head around this.
Thanks!
Not sure why I had such a difficult time getting my head around this, but Hot Licks got me on the right track.
Here is what I learned that might be helpful to others:
If you have multiple NSDictionaries embedded within an array, it is much simpler to parse these sub-dictionaries in other methods.
+(void)parseDivisionBranches:(NSDictionary *)schTypeDictionary usingStudentInfoFrom:(NSDictionary *)myAthleteInfo
intoManagedDoc: (UIManagedDocument *)document
{
NSArray* schoolDivisions = [self wrapDictionaryInArrayIfNecessary:[schTypeDictionary valueForKeyPath:#"School.SchoolDivision"]];
for (NSDictionary* schoolDivision in schoolDivisions) {
[MarksFromMeets parseDictionaryWithXcMarksForAthlete:(NSString*) [myAthleteInfo objectForKey:#"athlete_ID"]
fromDictionary:(NSDictionary *)schoolDivision
intoThisManagedDoc:(UIManagedDocument *)document];
}
}
In instances where only a single NSDictionary is passed at a particular level of the tree, it is simpler to embed that NSDictionary inside an NSArray so that you can use the same code to extract data; therefore, I always check to see if have an NSArray or NSDict.
+ (NSArray*) wrapDictionaryInArrayIfNecessary:(NSObject*)dictionaryMasquaradingAsAnArray
{
NSMutableArray* newArray = [[NSMutableArray alloc] init];
if([dictionaryMasquaradingAsAnArray isKindOfClass:[NSArray class]]) {
newArray = [dictionaryMasquaradingAsAnArray copy];
}else if([dictionaryMasquaradingAsAnArray isKindOfClass:[NSDictionary class]]) {
[newArray addObject:dictionaryMasquaradingAsAnArray];
}else {
NSString *className = NSStringFromClass([dictionaryMasquaradingAsAnArray class]);
NSLog(#"ERROR - dictionaryMasquaradingAsAnArray of %# class", className);
newArray = nil;
}
return newArray;
}
Then parse each sub-dictionary in turn by calling the method associated with the branch of the data tree, in this case:
+ (void)parseDictionaryWithXcMarksForAthlete:(NSString*)withAthleteID
fromDictionary:(NSDictionary *)dictionary
intoThisManagedDoc:(UIManagedDocument *)document
{
NSArray* seasons = [self wrapDictionaryInArrayIfNecessary:[dictionary valueForKeyPath:#"Season"]];
BOOL* parsedSeasonData;
for (NSDictionary* season in seasons) {
parsedSeasonData = [self parseDictionaryWithSeasonsListings:(NSString*)withAthleteID
fromDictionary:(NSDictionary *)season
intoThisManagedDoc:(UIManagedDocument *)document];
}
}
At some nodes, I had to capture data and pass it along down the chain for use later when I would ultimately write a record to CoreData. Again, thanks to Hot Licks and hope this helps others.
Related
I have an object containing an array of NSNumbers (indexes) and an array of NSDictionaries (indexesTitles) corresponding to indexes, containing some info.
I have to call a method for each object.index and associate object.indexTitles to the returning results, saving them into a single array.
At the end of it, I want to remove indexes duplicates, preserving the associated indextTitles in an efficient way, because I'm working with large arrays.
NSMutableArray *resultArray = [NSMutableArray array];
NSMutableArray *titlesArray = [NSMutableArray array];
for(NSNumber *index in object.indexes)
{
NSArray *resultsIndexArray = [self methodThatReturnsAnArray];
NSString *indexTitleDictionary = [object.indexesTitles objectAtIndex:i];
for(NSNumber *resultId in resultsIndexArray)
{
[titlesArray addObject:indexDictionary];
[resultArray addObject:resultId];
}
i++;
}
[fullResultsArray addObject:titlesArray];
[fullResultsArray addObject:resultArray];
I've found that the most efficient way to remove duplicates is using an
NSOrderedSet like this:
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:resultArray];
resultArray = [orderedSet.array mutableCopy];
How can I remove the corresponding entries in titlesArray? how can I preserve the association?
I've also tried to use a NSDictionary like {resultId, titleDictionary} and storing them into an array, but I haven't found a efficient way to remove dictionaries with the same result, they are all too slow.
Any suggestion?
It is not completely clear to me what your problem is, maybe this will help:
A good way to remove duplicates is not to add them in the first place, replace:
for(NSNumber *resultId in resultsIndexArray)
{
[titlesArray addObject:indexDictionary];
[resultArray addObject:resultId];
}
with:
for(NSNumber *resultId in resultsIndexArray)
{
// only add if resultId not already in resultArray
if( ![resultArray containsObject:resultId] )
{
[titlesArray addObject:indexDictionary];
[resultArray addObject:resultId];
}
}
The containsObject: call requires a linear search, if your data set is large you might wish to change resultArray to an NSMutableSet and titlesArray to an NSMutableDictionary mapping from resultId to indexDictionary values.
HTH
I have an array which contains multiple Dictionaries each one with 3 keys (#"date", #"username", #"text").
What I want to check for, is whether the same user (#"username") exists in more than one dictionary in that Array. And, if she does, combine the text for those "duplicates" into one dictionary.
I have considered this answer to check for duplicates and this one
but I cannot figure out how to combine these two.
Jumping in here because although I think you should work on the code yourself first, I think Miro's answer is more complicated than the issue requires and though I like the idea of using predicates in Greg's answer, here's a 3rd solution that (1) wouldn't require you to change your data structure and (2) references the necessary loops...
The way I'd do it: Create an NSMutableArray then start adding the usernames in order. If the NSMutableArray already contains the username though, don't add another instance of the username, but instead merge the dictionary info.
ex.
// Note: I'm calling your array of user dictionaries userArray.
// Create a username array to store the usernames and check for duplicates
NSMutableArray *usernames = [[NSMutableArray alloc] init];
// Create a new userArray to store the updated dictionary info, merged
// entries et. al.
NSMutableArray *newUserArray = [[NSMutableArray alloc] init];
// Go through the array of user dictionaries
for (NSDictionary *userDict in userArray) {
// If the usernames array doesn't already contain the username,
// add it to both the usernames array and the newUserArray as is
if (![usernames containsObject:[userDict objectForKey:#"username"]]) {
[usernames addObject:[userDict objectForKey:#"username"]];
[newUserArray addObject:userDict];
}
// Otherwise, merge the userArray entries
else {
// Get a mutable copy of the dictionary entry at the first instance
// with this username
int indexOfFirstInstance = [usernames indexOfObject:[userDict objectForKey:#"username"]];
NSMutableDictionary *entry = [[newUserArray objectAtIndex:indexOfFirstInstance] mutableCopy];
// Then combine the "text" or whatever other values you wanted to combine
// by replacing the "text" value with the combined text.
// (I've done so with a comma, but you could also store the value in an array)
[entry setValue:[[entry objectForKey:#"text"] stringByAppendingString:[NSString stringWithFormat:#", %#", [userDict objectForKey:#"text"]]] forKey:#"text"];
// Then replace this newly merged dictionary with the one at the
// first instance
[newUserArray replaceObjectAtIndex:indexOfFirstInstance withObject:entry];
}
}
Maybe something like this [untested] example? Loop through, maintain a hash of existing items, and if a duplicate is found then combine with existing and remove.
NSMutableArray main; // this should exist, with content
NSMutableDictionary *hash = [[NSMutableDictionary alloc] init];
// loop through, backwards, as we're attempting to modify array in place (risky)
for(int i = [main count] - 1; i >= 0; i--){
// check for existing
if(hash[main[i][#"username"]] != nil){
int existingIdx = [hash[main[i][#"username"]] integerValue]; // get existing location
main[existingIdx][#"text"] = [main[existingIdx][#"text"] stringByAppendingString:main[i][#"text"]]; // "combine text" .. or however you'd like to
[main removeObjectAtIndex:i]; // remove duplicate
} else {
[hash setValue:[[NSNumber alloc] initWithInt:i] forKey:main[i][#"username"]]; // mark existance, with location
}
}
If you use NSMutableDictionary, NSMutableArray and NSMutableString you can do it with predicate like that:
NSMutableDictionary *d1 = [#{#"username": #"Greg", #"text" : [#"text 1" mutableCopy]} mutableCopy];
NSMutableDictionary *d2 = [#{#"username": #"Greg", #"text" : [#"text 2" mutableCopy]} mutableCopy];
NSMutableDictionary *d3 = [#{#"username": #"John", #"text" : [#"text 3" mutableCopy]} mutableCopy];
NSMutableArray *array = [#[d1, d2, d3] mutableCopy];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"username = %#", #"Greg"];
NSArray *filterArray = [array filteredArrayUsingPredicate:predicate];
NSMutableDictionary * firstDict = filterArray[0];
for (NSDictionary *d in filterArray)
{
if (firstDict != d)
{
[firstDict[#"text"] appendString:d[#"text"]];
[array removeObject:d];
}
}
Trough my ios first app developpement i have to re-order an array containing dictionaires, parsed from a xml document, the purpose of re-ordering it is to send it to a function that build a collapsible, so it need a childCell index and a parentCell Index to print the strings of each child then pass to another parent. The problem is here : i'am able to fill my big array containing arrays of dictionaries, then i that array and do a loop to fill the childArray to contain multiple dictionaries, then i add this child array to my parent array, every thing seem to run but it gives me an empty array at the end. i put my code to show you how i tried to do this :
stories is the NSArray of dictionaries, childArray is the Array that should contain the dictionaries of stories, and parentArray is the Array that contains it all.
If someone who already did that can explain me were it goes wrong please it would be very much appreciated.
-(NSMutableArray *)orderChildsAndParents:(NSMutableArray *)fromArray
{
int varial = 0;
int catIndex = 0;
NSMutableArray *parentArray = [NSMutableArray array];
while(varial < [stories count])
{
NSString* cleanedString = [[[[stories objectAtIndex:varial] objectForKey:#"category"] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]
componentsJoinedByString:#""];
if ([cleanedString isEqualToString:[category objectAtIndex:catIndex] ])
{
if (!childArray || !childArray.count)
childArray = [NSMutableArray array];
[childArray addObject:[stories objectAtIndex:varial]];
varial++;
}
else{
[parentArray addObject:childArray];
[childArray removeAllObjects];
catIndex++;
}
}
NSLog(#"%#", parentArray);
return parentArray;
}
- (NSString *) labelForCellAtChildIndex:(NSInteger) childIndex withinParentCellIndex:(NSInteger) parentIndex {
NSMutableArray *orderedArray = [self orderChildsAndParents:stories];
NSLog(#"format string %#", [[[orderedArray objectAtIndex:parentIndex] objectAtIndex:childIndex] objectForKey:#"name"]); // empty :8
return [[[orderedArray objectAtIndex:parentIndex] objectAtIndex:childIndex] objectForKey:#"name"];
}
I've been trying to create a class to allow me to output core data out to JSON.
I have managed to get it working to a point, however I seem to have hit a brick wall on a outputting relationships
NSMutableArray * objectsArray = [[NSMutableArray alloc] init];
for (NSManagedObject * object in array) {
if([NSJSONSerialization isValidJSONObject:object]) {
[objectsArray addObject:object];
} else {
NSMutableDictionary *fields = [NSMutableDictionary dictionary];
for (NSAttributeDescription *attribute in [[object entity] properties]) {
NSString *attributeName = attribute.name;
id attributeValue = [object valueForKey:attributeName];
if([results length] > 0)
{
NSArray *chunks2 = [results componentsSeparatedByString: #","];
for (NSString * string in chunks2) {
if([string.lowercaseString isEqualToString:attributeName.lowercaseString])
{
[fields setObject:[NSString stringWithFormat:#"%#",attributeValue] forKey:attributeName];
break;
}
}
}
else
{
if (attributeValue) {
[fields setObject:[NSString stringWithFormat:#"%#",attributeValue] forKey:attributeName];
}
}
}
[objectsArray addObject:fields];
}
}
NSError *error;
NSData * JSONData = [NSJSONSerialization dataWithJSONObject:objectsArray options:kNilOptions error:&error];
And this outputs data fine aslong as I do not have a relationship for example a one -> many or many -> one
It outputs the following
{
"mySegmentation": "(null)",
"number": "9452062"
},
{
"mySegmentation": "<NSManagedObject: 0x212050b0> (entity: SegmentationCodes; id: 0x212090b0 <x-coredata://BEC52F5F-EA26-4CFF-BCCB-09DA163F465D/SegmentationCodes/p13> ; data: <fault>)",
"number": "9448502"
},
How can I get it to also indent in and output the information from the relationship?
I have been scratching my head for a while on this and would appreciate the help
Thanks Matt
From the documentation:
An object that may be converted to JSON must have the following properties:
The top level object is an NSArray or NSDictionary.
All objects are instances of NSString, NSNumber, NSArray,
NSDictionary, or NSNull.
All dictionary keys are instances of NSString.
Numbers are not NaN or infinity.
So, what you have to do is compose a dictionary or array with dictionaries, arrays, strings, numbers, nulls.
Normally relationships in CoreData are not sorted, so NSSets, you have to generate a NSArray from the set (therefor a nice method from Apple exists) and put it as value in the dictionary for the specific key.
Then run - dataWithJSONObject:options:error: for example (as you did before) and retrieve the correct JSON.
Not sure if the indention is right. You have to check that out.
Thats it, hopefully
I am trying to copy the objects content of a NSDictionary to a NSMutableArray, and I am using the following code :
// Use when fetching binary data
NSData *responseData = [request responseData];
// View the data returned - should be ready for parsing.
resultsDictionary = [responseData objectFromJSONData];
NSLog(#"ResultsDictionary:%#", resultsDictionary);
self.OnlineObjects = [[[NSMutableArray alloc] init] autorelease];
for (NSDictionary * dataDict in resultsDictionary) {
[OnlineObjects insertObject:dataDict atIndex:0];
}
NSLog(#"OnlineObjects:%#", OnlineObjects);
This is working as i am getting all objects from the Dictionary, but the objects order have been revers, first object is now last ...
How can tell the insertObject to add the object at the last index ?
Thanks
You can use the addObject: method instead.
To get rid of the hash order problem get allKeys, sort the array and then use the elements as keys to get the objects in proper order.
Verbose example (for integer keys):
NSArray *indices = [[resultsDictionary allKeys] sortedArrayUsingComparator:^(id obj1, id obj2) {
if ( [obj1 intValue] > [obj2 intValue] ) {
return (NSComparisonResult)NSOrderedDescending;
}
if ( [obj1 intValue] < [obj2 intValue] ) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}];
for (int i = 0; i < [indices count]; i++) {
NSDictionary *obj = [resultsDictionary objectForKey:[indices objectAtIndex:i]];
[OnlineObjects addObject:obj];
}
The order of the elements in a NSDictionary is undefined, you don't know in which order they will be retrieved from the dictionary. The only way to do have the array sorted is to sort it once all the values from the dictionary are transferred to the array.
Two things you should know:
NSDictionary is a key-value container, which does not guarantee the order of the objects. You have no way to ensure that the order of inserting will be mantained when reading by using this data structure. Check other strategies if order is important for you, but do not rely on NSDictionary for this.
You have a couple of methods to extract the info of the keys and data: allKeys and allValues. Use them instead of creating your own.