Parsing complex JSON using RestKit in Objective C - objective-c

I've been having issues with this issue for several days now, and would really appreciate it if someone will be able to provide some guidance or even a full code solution for this.
I'm trying to perform dynamic object mapping to a JSON string in Objective C using RestKit and can't seem to get the right values.
This is an example to the kind of JSON response I need to parse:
{
"Boy" :
{
"favoriteClass" : "math",
"basicInfo" :
{
"name" : "John",
"age" : 10,
"type" : 1
}
"friends" :
[
{
"Boy" :
{
"favoriteClass" : "PE"
"basicInfo" :
{
"name" : "Bill",
"age" : 12,
"type" : 1
}
"friends" : []
},
"Girl" :
{
"favoriteTeacher" : "Mrs. Manson"
"basicInfo" :
{
"name" : "Sara",
"age" : 11,
"type" : 2
}
"friends" : []
},
"Girl" :
{
"favoriteTeacher" : "Mr. Chase"
"basicInfo" :
{
"name" : "Ronda",
"age" : 9,
"type" : 2
}
"friends" : []
}
}
]
}
}
Meaning, I have two types of classes: a Buy class and a Girl class.
They each have different fields (favoriteClass for the boys and favoriteTeacher for the girls), but the both have a basicInfo field, which contains exactly the same structure.
I can tell which should be mapped to the Boy class and which should be mapped to the Girl class with using the name of the record in the array ("Boy" or "Girl"), or by the "type" field's value within the "basicInfo" record (1 for boys and 2 for girls).
The friends array for both a Boy and a Girl class can contain instances of both boys and girls.
Can anybody please give me some pointers on how this can be done?
Any help will be greatly appreciated.
Thanks,

I just started looking at RestKit today, so I am far from an authority. However, looking at Blake's excellent documentation, it seems to me that you are overly complicating this (your posted JSON does not validate BTW). Unless you have a specific need to have a BasicInfo object, I'd remove it and configure your JSON thus:
{
"people": [
{
"age": 10,
"favoriteClass": "math",
"name": "John",
"type": 1,
"friends": [
{
"age": 12,
"favoriteClass": "PE",
"friends": [],
"name": "Bill",
"type": 1
},
{
"age": 11,
"favoriteTeacher": "Mrs. Manson",
"friends": [],
"name": "Sara",
"type": 2
},
{
"age": 9,
"favoriteTeacher": "Mr. Chase",
"friends": [],
"name": "Ronda",
"type": 2
}
]
}
]
}
Then you should be able to utilize approach (1) that he describes in the Dynamic Object Mapping section of the Object Mapping document. It appears to me that the only difference you have in comparison with the example he gives is that you have some additional ivars in each of the Boy and Girl classes, and you are using an number rather than a string to identify Boy vs Girl. You can deal with that by modifying the declarations he gives to be:
// Basic setup
RKObjectMapping* boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
[boyMapping mapAttributes:#"age", #"name", #"favoriteClass",nil];
RKObjectMapping* girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
[girlMapping mapAttributes:#"age", #"name", #"favoriteTeacher", nil];
// Configure the dynamic mapping via matchers
[dynamicMapping setObjectMapping:boyMapping whenValueOfKeyPath:#"type" isEqualTo:#"1"];
[dynamicMapping setObjectMapping:girlMapping whenValueOfKeyPath:#"type" isEqualTo:#"2"];
The type of the matcher may not be quite right -- as I said, I just started reading this stuff today.

Just go through the hierarchy in a disciplined way.
//pseudo code
for (id child in JSONObject) {
if (it is a boy) create new boy object;
else create new girl object;
fill in the info;
for (id child in friends) {
if (it is a boy) create new boy object;
else create new girl object;
fill in the info;
}
}
Of course the whole data scheme is ridiculously repetitive. In my opinion, you should have only one class Child that anyway has an attribute basicInfo.type that identifies it as boy or girl, so no need to have an extra class.
Also, if you have a list of children who are friends with each other, you will end up listing many children multiple times. Much better would be to have a simple array with unique IDs in the friends field.

You simply need to use a JSON parser like SBJSON, SBJSON can parse the data you receive like so:
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *received = [parser objectWithData:data];
However, if you want to use Restkit, it has a built in parser also. I believe it would work something like this:
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response
{
RKJSONParser* parser = [RKJSONParser new];
NSDictionary *dict = [parser objectFromString:[response bodyAsString]];
}
I hope that helps you.

Related

Filter an object array to modify json with circe

I am evaluating Circe and couldn't find out how to use filter for arrays to transform a JSON. I read the guide on its website and API doc, still no clue. Help much appreciated.
Sample data:
{
"Department" : "HR",
"Employees" :[{ "name": "abc", "age": 25 }, {"name":"def", "age" : 30 }]
}
Task:
How to use a filter for Employees to transform the JSON to another JSON, for example, all employees with age older than 50?
For some reason I can't filter from data source before JSON is generated, in case you ask.
Thanks
One possible way of doing this is by
val data = """{"Department" : "HR","Employees" :[{ "name": "abc", "age": 25 }, {"name":"def", "age":30}]}"""
def ageFilter(j:Json): Json = j.withArray { x =>
Json.fromValues(x.filter(_.hcursor.downField("age").as[Int].map(_ > 26).getOrElse(false)))
}
val y: Either[ParsingFailure, Json] = parse(data).map( _.hcursor.downField("Employees").withFocus(ageFilter).top.get)
println(s"$y")

Deserialize Mantle object using two JSON formats

Is there a way to construct a mantle object using two different JSON objects? For example, say there is an Address class and the following two JSON formats:
{
"street: "s",
"city" : "city",
"state" : "state",
"zipcode" "12345"
}
{
"street_one: "s1",
"street_two: "s2",
"apartment" : "apt 1",
"city" : "city",
"state" : "state",
"zip" "12345"
}
[MTLJSONAdapter modelOfClass:[Address class] fromJSONDictionary:JSONDictionary error:&error];
Is there somewhere in MTLJSONAdapter to recognize two formats and assign or serialize properties based on them?
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
if (isJsonV2) {
// do new mapping
}
else {
// do original mapping
}
}
Hope to do something like above, or anything that allows conditionally mapping to the object.
Mantle doesn't support this, but you could use a subclass for V2 which has the extra street entry, or use a protocol to encapsulate the shared behaviour.

How to map two related JSONs using RestKit?

I have two JSONs, which are looking like:
{
"749": {
"email": "myMail#me.com",
"firstname": "Mr",
"lastname": "Smith"
}
}
and
[
{
"entity_id": "1",
"city": "myCity",
"country_id": "UA",
"region": null,
"postcode": "001",
"telephone": "+38000000001",
"fax": null,
"street": [
"myStrit ",
"12"
],
"is_default_billing": 1,
"is_default_shipping": 0
},
{
"entity_id": "2",
"city": "myCity",
"country_id": "UA",
"region": null,
"postcode": "001",
"telephone": "+3800000002",
"fax": null,
"street": [
"myStrit2",
"33"
],
"is_default_billing": 0,
"is_default_shipping": 1
}
]
Path for getting first JSON is mySite/customers, and for the second is mySite/customers/:userId/addresses.
where userId is unnamed attribute from first JSON (749 in my example).
I am new in RestKit and I can't really figure out, how to map it by one request...
PS: I get these JSONs from Magento rest api.
You say nothing about what you're trying to map into, but.
For the first JSON, you need to use addAttributeMappingFromKeyOfRepresentationToAttribute on the mapping to extract the 749 value and use it as a key at the same time.
For the second JSON there doesn't appear to be any connection to the first so you need to try mapping that and come back with specific issues. See this page for guidance.
RestKit expects each object to be described by a dictionary which can be processed by the mapper. Your service does not return an array of objects, though, rather a dictionary of dictionaries, which RestKit cannot correctly interpret as a collection of objects it can map individually. Of course, the problem would disappear if your service could return an array of dictionaries instead.
Fortunately, there is a way to transform the dictionary of dictionaries you receive into an array of dictionaries right before the mapper is called. This is achieved by setting a conversion block via -[RKObjectRequestOperation setWillMapDeserializedResponseBlock:].
The block receives the result of the initial parsing (here the dictionary of dictionaries), which you can easily turn into an array of dictionaries by calling -[NSDictionary allValues]:
RKManagedObjectStore *managedObjectStore = ...;
RKResponseDescriptor *responseDescriptor = ...;
NSURLRequest *request = ...;
RKManagedObjectRequestOperation *managedObjectRequestOperation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[responseDescriptor]];
managedObjectRequestOperation.managedObjectContext = managedObjectStore.mainQueueManagedObjectContext;
[managedObjectRequestOperation setWillMapDeserializedResponseBlock:^id(id deserializedResponseBody) {
return [deserializedResponseBody allValues];
}];
[[NSOperationQueue currentQueue] addOperation:managedObjectRequestOperation];

Restkit to-many relationship append to set instead of setting a new set

I have a iOS Restkit related question. I have a parent-child relationship data coming from a remote server and map those object to a NSManagedObject object with Restkit. The problem that I am currently having is every request to the server always wipe out the "child" relationship and replace it with the new data coming from the server. Is there a way to avoid those and append the new child instead?
For example: I have a classic Category --> Products relationship.
{"categories": [
{
"cat_id": "1",
"cat_title": "category 1",
"cat_tag": 1,
"product": [
{
"prod_id": "1",
"prod_name": "product 1",
"prod_tag": 1
},
{
"prod_id": "2",
"prod_name": "product 2",
"prod_tag": 1
}
]
}
] }
And that works fine and everything is saved properly with the relationship on the CoreData. But if I make another request to the server and have a new response:
{"categories": [
{
"cat_id": "1",
"cat_title": "category 1",
"cat_tag": 1,
"product": [
{
"prod_id": "3",
"prod_name": "product 3",
"prod_tag": 1
},
{
"prod_id": "4",
"prod_name": "product 4",
"prod_tag": 1
}
]
}
] }
I will have product 3 and product 4 replace product 1 and product 2 on the database. I am sure I setup all the relationship and primary key correctly. (Both cat_id and prod_id are set as a primary key).
Having investigated through the RestKit's internal framework, I noticed that around line 576 in the RKObjectMappingOperation class, there is
RKLogTrace(#"Mapped NSSet relationship object from keyPath '%#' to
'%#'. Value: %#", relationshipMapping.sourceKeyPath,
relationshipMapping.destinationKeyPath, destinationObject);
NSMutableSet *destinationSet = [self.destinationObject
mutableSetValueForKey:relationshipMapping.destinationKeyPath];
[destinationSet setSet:destinationObject];
So I guess that is easy to just change
[destinationSet setSet:destinationObject];
to
[destinationSet addObjectsFromArray:[destinationObject allObjects]]
But I was wondering whether there is a better way to do it?
Cheers,
Thanks for the support from Reskit. It is now supported by RKRelationshipMapping.(https://github.com/RestKit/RestKit/issues/989)
Just set assignmentPolicy value of RKRelationshipMapping instance to RKUnionAssignmentPolicy

MultiLevel JSON in PIG

I am new to PIG scripting and working with JSONs. I am in the need of parsing multi-level json files in PIG. Say,
{
"firstName": "John",
"lastName" : "Smith",
"age" : 25,
"address" :
{
"streetAddress": "21 2nd Street",
"city" : "New York",
"state" : "NY",
"postalCode" : "10021"
},
"phoneNumber":
[
{
"type" : "home",
"number": "212 555-1234"
},
{
"type" : "fax",
"number": "646 555-4567"
}
]
}
I am able to parse a single level json through JsonLoader() and do join and other operations and get the desired results as JsonLoader('name:chararray,field1:int .....');
Is it possible to parse the above mentioned JSON file using the built-in JsonLoader() function of PIG 0.10.0. If it is. Please explain me how it is done and accessing fields of the particular JSON?
You can handle nested json loading with Twitter's Elephant Bird: https://github.com/kevinweil/elephant-bird
a = LOAD 'file3.json' USING com.twitter.elephantbird.pig.load.JsonLoader('-nestedLoad')
This will parse the JSON into a map http://pig.apache.org/docs/r0.11.1/basic.html#map-schema the JSONArray gets parsed into a DataBag of maps.
It is possible by creating your own UDF. A simple UDF example is shown in below link
http://pig.apache.org/docs/r0.9.1/udf.html#udf-java
C = load 'path' using JsonLoader('firstName:chararray,lastName:chararray,age:int,address:(streetAddress:chararray,city:chararray,state:chararray,postalCode:chararray),
phoneNumber:{(type:chararray,number:chararray)}')