Mantle Transform With Nested Dictionaries - ios

I thought this question would work for my situation, but my lack of experience with Mantle and iOS in general has dead ended my train of thought. Basically, I have a big chunk of JSON with nested dictionaries and arrays that I want to convert into Mantle objects.
"features": {
"App": {
"status": "_ACTIVE",
"unavailableReasons": [],
"modernCapabilities": [{
"capabilityType": "LOCK_AUTO_REPLY",
"providerStatuses": [{
"providerType": "MY_PROVIDER",
"status": false,
"unavailableReasons": ["NOT_SUPPORTED_BY_PRODUCT", "DEVICE_OS_NOT_SUPPORTED"]
}]
},
...
...
{
"capabilityType": "LOCK_CONTACT_WHITELIST",
"providerStatuses": [{
"providerType": "OTHER_PROVIDER",
"status": true,
"unavailableReasons": []
}]
}
]}
}
I want to be able to implement a similar solution to that linked above, namely iterating down the dictionary for the "Features" key, and applying a transform to each key/value pair. So, in this case, to the "App" key and it's dictionary value (and later, the "modernCapabilities" key and it's array, etc. etc.) I know that for the later steps I'll need separate model classes, and those exist, but I'm having trouble with the first step, the transform on the "App" key and it's value.
Here's what I have right now:
+ (NSValueTransformer *)featureTypesJSONTransformer {
NSValueTransformer *transformer = [NSValueTransformer valueTransformerForName:#"FeatureStatus"];
return [MTLValueTransformer transformerWithBlock:^NSDictionary *(NSDictionary *features) {
NSMutableDictionary *transformedValues = [[NSMutableDictionary alloc] init];
for (NSString *key in features) {
id transformedValue = [transformer transformedValue:[features objectForKey:key]];
if (transformedValue ) {
[transformedValues setObject:transformedValue forKey:key];
}
}
return transformedValues;
}];
}
As you can see from the code, I'm trying to preserve the key, and attach it to a new Dictionary with the value being another transform, FeatureStatus in this case (#{ "App" : })
The problem is that there is no [FeatureStatus transformedValue:], although I do have a JSONKeyPathsForPropetyKey since I want to map the 'status', 'unavailableReasons' and 'modernCapabilities' keys later on.
What's my next step? How can I register a ValueTransform that does what I want it to do?

Related

NSOrderedSet response from server and Core Data

I want to show data as it came from the backend So let's have an example json file:
{
"fonts": [
{
"name": "Helvetica",
"styleIdentifier": "H0",
"size": 17
},
{
"name": "Helvetica",
"styleIdentifier": "H1",
"size": 14
},
{
"name": "Helvetica-Bold",
"styleIdentifier": "H0Bold",
"size": 17
},
{
"name": "HelveticaNeue-Light",
"styleIdentifier": "H0Light",
"size": 40
}
]
}
So i create a relationship (many - many) with ordered option selected. And by the input i see it's always write in the same way to Core Data, but when I try to fetch it
configuratation.fonts where fonts is a NSOrderedSet i get items in completly random order. I miss sth in spec? Or I should sort it somehow?
__EDIT__
Firstly when i get a data from above json I have a configuration set with empty font relation. Then I fetch this and insert it into core data with:
NSMutableArray *returnArray = [NSMutableArray new];
for(NSDictionary *fontDictionary in jsonArray) {
Font *fontObj = [Font font:fontDictionary inContext:context];
[returnArray addObject:fontObj];
}
And in this array data is in correct order. Then in configuration object i add it to NSOrderedSet by:
-(void)appendTracks:(NSArray<Font*>*)fontArray {
self.fonts = [NSOrderedSet orderedSetWithArray: fontArray];
}
And then i try to fetch it by simply use reference:
configuration.fonts
And in this step data are completly not in correct order.
Do not set the NSOrderedSet directly.
Either modify the existing set:
[self.fonts addObjectsFromArray:fontArray];
Or:
Xcode generates methods to add and remove entries from ordered sets within the generated NSManagedObject class.
Assuming you have an Entity called ManagedConfiguration which holds an ordered to many relation called fonts:
ManagedConfiguration *managedObjectConfigurationInstance = //create or fetch configuration in ManagedObjectContext
NSOrderedSet<ManagedFont> *fonts = //created or fetched fonts in wanted order
managedObjectConfigurationInstance.addToFonts(fonts)
replaceFonts, removeFromFontsAtIndex aso. methods are also generated.
Depending on your requirements, you might want to store the fonts in random order and apply a NSSortDescriptor to your NSFetchRequest to fetch the data in a specific order.
Instead of trying to set the data directly to your property(fonts), you need to first fetch the mutable copy of your NSOrderedSet from the NSmanagedObject Subclass (I assume it to be Font).
NSMutableOrderedSet *orderedSet = [self mutableOrderedSetValueForKey:#"fonts"];
Then add the objects from the array to this orderedSet.
[orderedSet addObjectsFromArray:array];
Now you would have properly set the the values for the key fonts.
So your appendTracks function would now look like this.
-(void)appendTracks:(NSArray<Font*>*)fontArray {
NSMutableOrderedSet *orderedSet = [self mutableOrderedSetValueForKey:#"fonts"];
[orderedSet addObjectsFromArray:fontArray];
}
Now execute your fetch request. You should receive the data in the set order in the array.
PS:I had used your JSON response to test this.

Dictionary put into Array gets shredded

I try to send data to the server. Server waits from my this structure among other:
{
...
"card": [
{
"child": {...},
"parent":{...}
},
{
"child": {...},
"parent":{...}
}
],
[...],
[...]
}
So it should be Dictionary ({...}) placed into another Dictionary ({"child":..., "parent":...}), placed into an Array ("card": []), and this array is a cell, the final API JSON contains many of such cells.
I realize this structure by something like this:
NSDictionary *card = #{#"key1" : #"val1", #"key2" : #"val2", #"key3" : #"val3"};
NSDictionary *pair = #{#"parent" : card, #"child" : card};
NSArray *cards = [NSArray arrayWithObjects: pair, pair, nil];
After this I add cards Array into Dictionary with other auth data and send it to the server:
[self.userAuthData setObject: cards forKey:#"card"];
And I see in the server logs that data was shredded: http://monosnap.com/image/UbLPA0AK0eotAvG12o1ML4702xy0aj.png
But, if I use Dictionary cards instead of Array cards, everything is ok: http://monosnap.com/image/IzenpFc3Gik01UYhyRYtGFHmxBpCpC.png
What's wrong with idea to store Dictionary in an Array? Why it gets shredded?
Ok, answer is here: AFNetworking posts JSON arrays as multiple single-entry dictionaries
I just added a line in my ApiClient.m
_sharedClient.requestSerializer = [AFJSONRequestSerializer serializer];

RestKit: Ignoring some dynamic nesting attributes

I receive JSON objects like this:
{
"rent": {
"id": "someId"
},
"upcoming": {
"id": "someId"
},
"watchnow": {
"id": "someId"
}
}
I then set forceCollectionMapping to YES on my mapping to get one object for each key, i.e. one object for "rent", one for "upcoming" and one for "watchnow". Specifically this is done with this code:
[searchResultsMapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"searchSection"];
So this succesfully gives me three objects for which I can then do some relationship mapping to get the id keys and what ever else is on the object.
Now, my problem is that if an error occurs, I get this JSON code:
{
"error": {
"errorcode": "someId"
}
}
So (searchSection) becomes "error" and my relationship mapping looks for "id" but it's not there so the mapping fails. The problem is that setting addAttributeMappingFromKeyOfRepresentationToAttribute makes RestKit try to make an object from every single key, and I can't expect every key to be relevant and useful for my mappings. Can I do anything about this?
You have a couple of options:
Use an RKDynamicMapping as the root mapping for your response descriptor
Use multiple response descriptors to specify exactly which keypaths to process
Use KVC validation to reject the error mapping (not ideal as the error isn't really captured)
For the dynamic mapping option, the dynamic mapping has the forceCollectionMapping option set and it checks the top level key available and returns the appropriate mapping (which wouldn't have forceCollectionMapping set and which uses addAttributeMappingFromKeyOfRepresentationToAttribute:).
I got it to work using Wain's first suggestion. Here's the solution if anyone has the same issue:
I created a dynamic mapping like this:
RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
dynamicMapping.forceCollectionMapping = YES;
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping * (id representation) {
if ([representation valueForKey:#"watchnow"] || [representation valueForKey:#"upcoming"] || [representation valueForKey:#"rent"]) {
return searchResultsMapping;
}
return nil;
}];
As you can see in my example at the top, I'm only interested in keys named "watchnow", "upcoming" or "rent".
The searchResultsMapping which is returned is configured like this:
RKObjectMapping *searchResultsMapping = [RKObjectMapping mappingForClass:[TDXSearchResults class]];
[searchResultsMapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"searchSection"];
So I now end up with three SearchResult objects with either "watchnow", "upcoming" or "rent" in their searchSection NSString property.

Xcode - Getting object out of an array within an array

I have a JSON array(dictionary?) of objects that are themselves an array. I need to find a value within one of these arrays so that I can compare it later. Part of my JSON data:
[
{
"Name": "Exhibitor",
"Url": "api/congress/exhibitor",
"ResourceType": "Data",
"LastMod": 1389106977
},
{
"Name": "Workshop",
"Url": "api/congress/workshop",
"ResourceType": "Data",
"LastMod": 1389106977
},
{
"Name": "Speaker",
"Url": "api/congress/Speaker",
"ResourceType": "Data",
"LastMod": 1389106977
},
]
My method receives a table name as a parameter and returns a time stamp. How would I receive the time stamp (1389106977) for the table "workshop" for example? This seems so simple but I cannot work it out for 'nested' arrays/dictionaries.
Thanks,
edit:
This is the my code with trojanfoe's added to it.
NSError* localError;
NSMutableArray *syncDataArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (syncDataArray)
{
NSNumber *lastMod = nil;
for (NSDictionary *dict in syncDataArray)
{
NSLog(#"current table is: %#", dict[#"Name"]);
if ([tableName isEqualToString:dict[#"Name"]])
{
lastMod = dict[#"LastMod"];
//break;
}
}
NSLog(#"LastMod = %#", lastMod);
}
else
{
NSLog(#"syncDataArray is empty");
}
This works perfectly and makes sense
The JSON data looks like an array of dictionaries, so you can iterate over the array and test for the "Name" entry:
NSArray *jsonData = ...; // You have converted JSON to Objective-C objects already
NSNumber *lastMod = nul;
for (NSDictionary *dict in jsonData) {
if ([#"Workshop" isEqualToString:dict[#"Name"]]) {
lastMod = dict[#"LastMod"];
break;
}
}
if (lastMod) {
// You found it
}
(Note I am not certain the type of object used to store the "LastMod" object, so you might need to do some debugging to find out).
EDIT If you make extensive use of this data you should immediately convert the JSON data into an array of (custom) model objects, which will make it easier to manipulate the data as your app becomes more complex.
You have an array for dictionaries so it would look something like :
NSNumber *timestamp = [[JSON objectAtIndex:index] objectForKey:#"LastMod"];
NSNumber *timestamp = response[1][#"LastMod"];

how to get JSON object from server as it (in the same order) iPhone

Is there any way to get JSON object from the server in the same order??
For example when i fitch using browser my JSON object return like this:
{
"23": {
"numberOfRecords": "3",
"startDate": "27/11/2013",
"endDate": "31/12/2014",
"question": "How do you rate the new MenaME Portal ?",
"voteScale": "5",
"questions": {
"option1": {
"value": "1",
"option": "Poor",
"voteResult": "50.000"
},
"option2": {
"value": "2",
"option": "Acceptable",
"voteResult": "0.000"
},
"option3": {
"value": "3",
"option": "Good",
"voteResult": "0.000"
},
"option4": {
"value": "4",
"option": "Very Good",
"voteResult": "0.000"
},
"option5": {
"value": "5",
"option": "Excellent",
"voteResult": "50.000"
}
},
"selectedAnswer": "0",
"voteAnswered": "0",
"votes": "6"
}
}
after parsing it with [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error]
the object returned like this :
{
23 = {
endDate = "31/12/2014";
numberOfRecords = 3;
question = "How do you rate the new MenaME Portal ?";
questions = {
option1 = {
option = Poor;
value = 1;
voteResult = "50.000";
};
option2 = {
option = Acceptable;
value = 2;
voteResult = "0.000";
};
option3 = {
option = Good;
value = 3;
voteResult = "0.000";
};
option4 = {
option = "Very Good";
value = 4;
voteResult = "0.000";
};
option5 = {
option = Excellent;
value = 5;
voteResult = "50.000";
};
};
selectedAnswer = 0;
startDate = "27/11/2013";
voteAnswered = 0;
voteScale = 5;
votes = 6;
};
}
Is there any way or framework to get the object as it (in the same order returned from the server) ??
Dictionaries, both in JSON and NSDictionary, are unordered, meaning that it is irrelevant which order you see things in the log. This is defined in the JSON specification and the documentation for NSDictionary.
If it actually matters what order things are displayed in, then either the API you are linking to isn't using correct JSON, or you're doing something wrong in your app. To help with those situations you can use several of the sorted NSDictionary implementations that are around.
Can I ask why you want to ensure the dictionary is maintained in the correct order?
I understand in some cases (mine) an ancient JSON -> XML web service was being called by my app and the client refused to adjust the service so it could accept unordered JSON (valid json) but if you're writing the app, why do you need to ensure that it is in order?
I have a NSMutableDictionary subclass that keeps objects added by setObject:forKey in the order you call the method that can be found here.
It works by storing a NSMutableOrderedSet of keys within the dictionary and then overrides the keyEnumerator method to return an enumerator based on the ordered set
- (NSEnumerator *)keyEnumerator
{
return [self.orderedSetOfKeys objectEnumerator];
}
You could modify the NSMutableDictionary subclass i created to expose the NSMutableOrderedSet in the public header and then modify this set yourself to get an ordered version of your dictionary.. For example:
NSDictionary *JSONWebServiceDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error];
LNOrderedMutableDictionary *orderedDictionary = [[LNOrderedMutableDictionary alloc] initWithDictionary:JSONWebServiceDictionary];
NSMutableOrderedSet *order = [[NSMutableOrderedSet alloc] initWithArray:#[#"key1",#"key2",#"key3"]]; //All the keys you are expecting and the order you want them in..
orderedDictionary.orderSet = order; //orderSet does not exist.. it is currently called `array` and not exposed in LNOrderedMutableDictionary.h
I haven't tested the code above but unless you want to create or modify an existing JSON parser then it seems that it is your only option..
If you did want to modify an existing parser then it might just be as simple as replacing dictionary instances with LNOrderedMutableDictionary to keep everything in order.
Another idea to expand the above sample code could be to replace
NSMutableOrderedSet *order = [[NSMutableOrderedSet alloc] initWithArray:#[#"key1",#"key2",#"key3"]];
with an array returned in the JSONWebServiceDictionary dictionary as arrays keep their order when parsed from JSON so maybe you could do this?
NSMutableOrderedSet *order = [[NSMutableOrderedSet alloc] initWithArray:[JSONWebServiceDictionary objectForKey:#"keyOrderArray"]]];
Look at what you have. If you test the result you got back from JSONObjectWithData (which we'll assume was declared as id jsonObject)
if ([jsonObject isKindOfClass:[NSDictionary class]) { ...
or
NSLog(#"The object type is %#", [jsonObject class]);
you will find that it is indeed an NSDictionary (or perhaps an NSMutableDictionary). That dictionary, as you can see from the dump (or infer from the nearly identical JSON) contains a single entry with a key of "23".
So let's cast the jsonObject to an NSDictionary and reference it:
NSDictionary* jsonDict = (NSDictionary*) jsonObject;
NSDictionary* entry23Dict = [jsonDict objectForKey:#"23"];
Now, if you NSLog entry23Dict you will discover it contains all of the above, absent the { 23 = ... } outermost dictionary.
You can then access, say, "questions" with
NSDictionary* questDict = [entry23Dict objectForKey:#"questions"];
From there the individual "option1", "option2", ... "option5" dictionaries can be accessed in a similar fashion. You simply proceed one layer at a time -- don't get overwhelmed by the entire structure. (It's often helpful, when you're first learning, to NSLog each "layer" as you "peel" it out of the containing structure.)
And, of course, you have all the standard facilities that are available to NSDictionary objects (and NSArray objects, should your JSON contain any [..] arrays). For instance, you can iterate on the keys of the dictionary with
for (NSString* key in jsonDict) {
NSLog(#"This entry's number is %#", key); // For the above will print "23"
NSDictionary* numberedDict = jsonDict[key]; // Using the "new" form of dictionary access
NSString* endDate = numberedDict[#"endDate"]; // Ditto
NSLog(#"The end date is %#", endDate);
}
This is a fairly common problem. It's also probably the most annoying part about iOS. (java doesn't have this issue at all). If you want to get back objects, take a look at restkit.org Specifically this answer may help: https://stackoverflow.com/a/8284343/836450

Resources