Question

I am pretty to new to iOS development and the RestKit framework (using version 0.20.3). Single entity mapping works like a charm. However, when it comes to relationship mapping for dynamic nested JSON I have been struggling a lot the past couple of days. I have the following JSON response which I am trying to map:

 "chargerstations": [
    {
      "csmd": {
        "id": 1190,
        "name": "Tammasaarenkatu  7",
        "Street": "Tammasaarenkatu",
        "House_number": "7",
        "Zipcode": "00180",
        "City": "Ruoholahti",
        "Municipality_ID": "091",
        "Municipality": "Helsinki",
        "County_ID": "01",
        "County": "Uusimaa",
        "Description_of_location": "",
        "Owned_by": "Helen",
        "Number_charging_points": 1,
        "Position": "(60.16172,24.90679)",
        "Image": "Kommer",
        "Available_charging_points": 1,
        "User_comment": "",
        "Contact_info": "",
        "Created": "2012-03-30 11:40:48",
        "Updated": "2013-04-16 11:52:47",
        "Station_status": 1,
        "Land_code": "FIN",
        "International_id": "FIN_01190"
      },
      "attr": {
        "st": {
          "2": {
            "attrtypeid": "2",
            "attrname": "Availability",
            "attrvalid": "1",
            "trans": "Public",
            "attrval": ""
          },
          "3": {
            "attrtypeid": "3",
            "attrname": "Location",
            "attrvalid": "1",
            "trans": "Street",
            "attrval": ""
          },
          "6": {
            "attrtypeid": "6",
            "attrname": "Time limit",
            "attrvalid": "2",
            "trans": "No",
            "attrval": ""
          },
          "7": {
            "attrtypeid": "7",
            "attrname": "Parking fee",
            "attrvalid": "2",
            "trans": "No",
            "attrval": false
          },
          "21": {
            "attrtypeid": "21",
            "attrname": "Real-time information",
            "attrvalid": "2",
            "trans": "No",
            "attrval": ""
          },
          "22": {
            "attrtypeid": "22",
            "attrname": "Public funding",
            "attrvalid": "4",
            "trans": "None",
            "attrval": ""
          },
          "24": {
            "attrtypeid": "24",
            "attrname": "Open 24h",
            "attrvalid": "1",
            "trans": "Yes",
            "attrval": "1"
          }
        },
        "conn": {
          "1": {
            "1": {
              "attrtypeid": "1",
              "attrname": "Accessibility",
              "attrvalid": "6",
              "trans": "Cellular phone",
              "attrval": ""
            },
            "4": {
              "attrtypeid": "4",
              "attrname": "Connector",
              "attrvalid": "32",
              "trans": "Mennekes-type 2 (IEC 62196-2) \n",
              "attrval": ""
            },
            "5": {
              "attrtypeid": "5",
              "attrname": "Charging capacity",
              "attrvalid": "11",
              "trans": "400V 3-phase max 32A",
              "attrval": ""
            },
            "17": {
              "attrtypeid": "17",
              "attrname": "Vehicle type",
              "attrvalid": "1",
              "trans": "All vehicles",
              "attrval": ""
            },
            "18": {
              "attrtypeid": "18",
              "attrname": "Reservable",
              "attrvalid": "2",
              "trans": "No",
              "attrval": ""
            },
            "20": {
              "attrtypeid": "20",
              "attrname": "Charge mode",
              "attrvalid": "3",
              "trans": "Mode 3",
              "attrval": ""
            },
            "25": {
              "attrtypeid": "25",
              "attrname": "Fixed cable",
              "attrvalid": "2",
              "trans": "No",
              "attrval": ""
            }
          }
          "2": {
            "1": {same structure as above
            }
          }
          "<conn no. x>": {
            "1": {same structure as above
            }
          }
        }
      }
    }
  ]

I have created a Core Data model with 2 entities; "ChargingStation" containing data from "csmd" and "attr.st" dictionaries, and "Connector" containing data from the dynamic nested part of the JSON (attr.conn). One charging station may have many connectors, but one connector may only belong to one charging station. Thus, I have defined a To-Many relationship from "ChargingStation" to "Connector", with an inverse To-One from the "Connector" entity.

http://imgur.com/Q0mhlIi

My models are being generated by using mogenerator. Here is the code I have so far regarding the mapping itself (in AppDelegate.m):

RKEntityMapping *chargingStationMapping = [RKEntityMapping mappingForEntityForName:@"ChargingStation"
                                                              inManagedObjectStore:managedObjectStore];
[chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                             @"attr.st.2.trans" : @"availabilityType",
                                                             @"csmd.City" : @"city",
                                                             @"csmd.Position" : @"coordinates",
                                                             @"csmd.House_number" : @"houseNumber",
                                                             @"csmd.id" : @"stationID",
                                                             @"csmd.International_id" : @"internationalID",
                                                             @"csmd.Description_of_location" : @"locationDescription",
                                                             @"csmd.Image" : @"locationImage",
                                                             @"attr.st.3.trans" : @"locationName",
                                                             @"csmd.name" : @"name",
                                                             @"csmd.Available_charging_points" : @"numChargingPoints",
                                                             @"attr.st.24.trans" : @"openingHours",
                                                             @"attr.st.7.trans" : @"parkingFee",
                                                             @"attr.st.22.trans" : @"publicFunding",
                                                             @"attr.st.21.trans" : @"realtimeInfo",
                                                             @"csmd.Street" : @"street",
                                                             @"attr.st.6.trans" : @"timeLimit",
                                                             @"csmd.Zipcode" : @"zipCode"
                                                             }];

chargingStationMapping.identificationAttributes = @[@"stationID"];


RKDynamicMapping *dynamicConnectorMapping = [[RKDynamicMapping alloc] init];
[dynamicConnectorMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
    RKEntityMapping *connectorMapping = [RKEntityMapping mappingForEntityForName:@"Connector"
                                                            inManagedObjectStore:managedObjectStore];
    NSDictionary *connectors = [representation valueForKeyPath:@"attr.conn"];
    for (NSString *attr in connectors) {
        NSDictionary *connector = [connectors objectForKey:attr];
        [connector enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) {
            NSLog(@"Attr from outer connector object: %@", attr);

            if ([key isEqualToString:@"1"]) {
                [chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                                             [NSString stringWithFormat:@"attr.conn.%@.%@.trans", attr, key]: @"accessibility"
                                                                             }];
            } else if ([key isEqualToString:@"4"]) {
                [chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                                             [NSString stringWithFormat:@"attr.conn.%@.%@.trans", attr, key]: @"connector"
                                                                             }];
            } else if ([key isEqualToString:@"5"]) {
                [chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                                             [NSString stringWithFormat:@"attr.conn.%@.%@.trans", attr, key]: @"chargingCapacity"
                                                                             }];
            } else if ([key isEqualToString:@"17"]) {
                [chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                                             [NSString stringWithFormat:@"attr.conn.%@.%@.trans", attr, key]: @"vehicleType"
                                                                             }];
            } else if ([key isEqualToString:@"18"]) {
                [chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                                             [NSString stringWithFormat:@"attr.conn.%@.%@.trans", attr, key]: @"reservable"
                                                                             }];
            } else if ([key isEqualToString:@"20"]) {
                [chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                                             [NSString stringWithFormat:@"attr.conn.%@.%@.trans", attr, key]: @"chargeMode"
                                                                             }];
            } else if ([key isEqualToString:@"23"]) {
                [chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                                             [NSString stringWithFormat:@"attr.conn.%@.%@.trans", attr, key]: @"manufacturer"
                                                                             }];
            } else if ([key isEqualToString:@"25"]) {
                [chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                                             [NSString stringWithFormat:@"attr.conn.%@.%@.trans", attr, key]: @"fixedCable"
                                                                             }];
            }
        }];
    }

    connectorMapping.identificationAttributes = @[@"id"];
    [connectorMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"ChargingStation"
                                                                                     toKeyPath:@"chargingstation"
                                                                                   withMapping:chargingStationMapping]];

    return connectorMapping;
}];

RKResponseDescriptor *connectorDescription = [RKResponseDescriptor responseDescriptorWithMapping:dynamicConnectorMapping method:RKRequestMethodGET pathPattern:nil keyPath:@"chargerstations" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

RKResponseDescriptor *chargingStationDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:chargingStationMapping method:RKRequestMethodAny pathPattern:nil keyPath:@"chargerstations.attr.conn" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

[objectManager addResponseDescriptor:chargingStationDescriptor];
[objectManager addResponseDescriptor:connectorDescription];

[objectManager getObjectsAtPath:<URL_request>"
                     parameters:nil
                        success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                            ChargingStation *cstation = [mappingResult firstObject];
                            NSLog(@"House number: %@", cstation.houseNumber);

} failure:^(RKObjectRequestOperation *operation, NSError *error) {}];

When I try to run it, I get the following error message:

Assertion failure in -[RKEntityMapping addPropertyMapping:], /<my_project_directory>/Pods/RestKit/Code/ObjectMapping/RKObjectMapping.m:237
2014-04-21 18:30:55.200 ChargeIt[844:530b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unable to add mapping for keyPath connector, one already exists...'

I assume this is because I am having two ResponseDescriptors for the same parent keyPath, i.e. "chargingstations"? I have read the mapping guide at https://github.com/RestKit/RestKit/wiki/Object-mapping and looked at a few other tutorials, as well as other issues here on SO, but still can't for the life of me figure out what I am doing wrong.

I would greatly appreciate any help and/or push in the right direction that I can get.

UPDATE: I have now added the modified code below according to the approach that @Wain suggested, and also upgraded to version 0.23.1 of Restkit. The mapping now works (well, at least it finishes mapping without any errors :)...). However, when I try to access the connectors associated to a chargerstation, it returns an empty NSSet, and I get the following error in the log when trying to log the NSSet:

connectors = "<relationship fault: 0x9fb5fe0 'connectors'>";

I also looked at the SQLite database, I notice that the Connector entity has gotten a "ChargingStation" field in it's table, but the ChargingStation entity does not have any "Connectors" field in it's table.

RKEntityMapping *chargingStationMapping = [RKEntityMapping mappingForEntityForName:@"ChargingStation"
                                                                  inManagedObjectStore:managedObjectStore];
    [chargingStationMapping addAttributeMappingsFromDictionary:@{
                                                                 @"attr.st.2.trans" : @"availabilityType",
                                                                 @"chargerstations.csmd.City" : @"city",
                                                                 @"csmd.Position" : @"coordinates",
                                                                 @"csmd.House_number" : @"houseNumber",
                                                                 @"csmd.id" : @"stationID",
                                                                 @"csmd.International_id" : @"internationalID",
                                                                 @"csmd.Description_of_location" : @"locationDescription",
                                                                 @"csmd.Image" : @"locationImage",
                                                                 @"attr.st.3.trans" : @"locationName",
                                                                 @"csmd.name" : @"name",
                                                                 @"csmd.Available_charging_points" : @"numChargingPoints",
                                                                 @"attr.st.24.trans" : @"openingHours",
                                                                 @"attr.st.7.trans" : @"parkingFee",
                                                                 @"attr.st.22.trans" : @"publicFunding",
                                                                 @"attr.st.21.trans" : @"realtimeInfo",
                                                                 @"csmd.Street" : @"street",
                                                                 @"attr.st.6.trans" : @"timeLimit",
                                                                 @"csmd.Zipcode" : @"zipCode"
                                                                 }];

    chargingStationMapping.identificationAttributes = @[@"stationID"];


    RKEntityMapping *connectorMapping = [RKEntityMapping mappingForEntityForName:@"Connector"
                                                                  inManagedObjectStore:managedObjectStore];
    connectorMapping.forceCollectionMapping = YES;
    [connectorMapping addAttributeMappingFromKeyOfRepresentationToAttribute:@"connectorID"];
    [connectorMapping addAttributeMappingsFromDictionary:@{
                                                           @"(connectorID).1.trans": @"accessibility",
                                                           @"(connectorID).4.trans": @"connector",
                                                           @"(connectorID).5.trans": @"chargingCapacity",
                                                           @"(connectorID).17.trans": @"vehicleType",
                                                           @"(connectorID).18.trans": @"reservable",
                                                           @"(connectorID).20.trans": @"chargeMode",
                                                           @"(connectorID).23.trans": @"manufacturer",
                                                           @"(connectorID).25.trans": @"fixedCable"
                                                           }];

    [chargingStationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"attr.conn"
                                                                                           toKeyPath:@"connectors"
                                                                                         withMapping:connectorMapping]];

    RKResponseDescriptor *chargingStationDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:chargingStationMapping
                                                                                                    method:RKRequestMethodAny
                                                                                               pathPattern:nil
                                                                                                   keyPath:@"chargerstations"
                                                                                               statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    [objectManager addResponseDescriptor:chargingStationDescriptor];
    [objectManager getObjectsAtPath:@"datadump.php?apikey=<API_KEY>&file=false&format=json&countrycode=FIN"
                         parameters:nil
                            success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                ChargingStation *cstation = [mappingResult firstObject];
                                NSLog(@"Connectors: %@", cstation);
                                NSLog(@"Connector info: %i", cstation.connectorsSet.count);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {}];

From what I can see in the log, it maps everything correctly (see the, rather lengthy, attached part of the log output). Any ideas?

to object <ChargingStation: 0x9c84cf0> (entity: ChargingStation; id: 0x151c4f00 <x-coredata://77C8299D-FDDB-4810-8D22-C13538858DA1/ChargingStation/p18> ; data: {
    availabilityType = Public;
    city = nil;
    connectors = "<relationship fault: 0x9c84270 'connectors'>";
    coordinates = "(60.16172,24.90679)";
    houseNumber = 7;
    internationalID = "FIN_01190";
    locationDescription = "";
    locationImage = Kommer;
    locationName = Street;
    name = "Tammasaarenkatu  7";
    numChargingPoints = 1;
    openingHours = Yes;
    parkingFee = No;
    paymentMethods = nil;
    publicFunding = None;
    realtimeInfo = No;
    stationID = 1190;
    street = Tammasaarenkatu;
    timeLimit = No;
    zipCode = 00180;
}) with object mapping (null)
2014-04-30 19:35:06.949 ChargeIt[6131:5613] D restkit.object_mapping:RKMappingOperation.m:766 Collection mapping forced for NSDictionary, mapping each key/value independently...
2014-04-30 19:35:06.950 ChargeIt[6131:5613] D restkit.object_mapping:RKMappingOperation.m:640 Mapping one to many relationship value at keyPath 'attr.conn' to 'connectors'
2014-04-30 19:35:06.951 ChargeIt[6131:3d13] D restkit.object_mapping:RKPropertyInspector.m:131 Cached property inspection for Class 'Connector': {
    accessibility =     {
        isPrimitive = 0;
        keyValueCodingClass = NSString;
        name = accessibility;
    };
    ...
    };
    vehicleType =     {
        isPrimitive = 0;
        keyValueCodingClass = NSString;
        name = vehicleType;
    };
}
2014-04-30 19:35:06.984 ChargeIt[6131:5613] I restkit.core_data:RKInMemoryManagedObjectCache.m:94 Caching instances of Entity 'Connector' by attributes 'connectorID'
2014-04-30 19:35:07.042 ChargeIt[6131:5613] T restkit.object_mapping:RKMappingOperation.m:541 Performing nested object mapping using mapping <RKRelationshipMapping: 0x9e4ce70 attr.conn => connectors> for data: {
    1 =     {
        1 =         {
            attrname = Accessibility;
            attrtypeid = 1;
            attrval = "";
            attrvalid = 6;
            trans = "Cellular phone";
        };
        17 =         {
            attrname = "Vehicle type";
            attrtypeid = 17;
            attrval = "";
            attrvalid = 1;
            trans = "All vehicles";
        };
        18 =         {
            attrname = Reservable;
            attrtypeid = 18;
            attrval = "";
            attrvalid = 2;
            trans = No;
        };
        20 =         {
            attrname = "Charge mode";
            attrtypeid = 20;
            attrval = "";
            attrvalid = 3;
            trans = "Mode 3";
        };
        25 =         {
            attrname = "Fixed cable";
            attrtypeid = 25;
            attrval = "";
            attrvalid = 2;
            trans = No;
        };
        4 =         {
            attrname = Connector;
            attrtypeid = 4;
            attrval = "";
            attrvalid = 32;
            trans = "Mennekes-type 2 (IEC 62196-2) \n";
        };
        5 =         {
            attrname = "Charging capacity";
            attrtypeid = 5;
            attrval = "";
            attrvalid = 11;
            trans = "400V 3-phase max 32A";
        };
    };
}
2014-04-30 19:35:07.043 ChargeIt[6131:5613] D restkit.object_mapping:RKMappingOperation.m:859 Starting mapping operation...
2014-04-30 19:35:07.044 ChargeIt[6131:5613] T restkit.object_mapping:RKMappingOperation.m:860 Performing mapping operation: <RKMappingOperation 0x9cdcdd0> for 'Connector' object. Mapping values from object {
    1 =     {
        1 =         {
            attrname = Accessibility;
            attrtypeid = 1;
            attrval = "";
            attrvalid = 6;
            trans = "Cellular phone";
        };
        17 =         {
            attrname = "Vehicle type";
            attrtypeid = 17;
            attrval = "";
            attrvalid = 1;
            trans = "All vehicles";
        };
        18 =         {
            attrname = Reservable;
            attrtypeid = 18;
            attrval = "";
            attrvalid = 2;
            trans = No;
        };
        20 =         {
            attrname = "Charge mode";
            attrtypeid = 20;
            attrval = "";
            attrvalid = 3;
            trans = "Mode 3";
        };
        25 =         {
            attrname = "Fixed cable";
            attrtypeid = 25;
            attrval = "";
            attrvalid = 2;
            trans = No;
        };
        4 =         {
            attrname = Connector;
            attrtypeid = 4;
            attrval = "";
            attrvalid = 32;
            trans = "Mennekes-type 2 (IEC 62196-2) \n";
        };
        5 =         {
            attrname = "Charging capacity";
            attrtypeid = 5;
            attrval = "";
            attrvalid = 11;
            trans = "400V 3-phase max 32A";
        };
    };
} to object <Connector: 0x1965b460> (entity: Connector; id: 0x9e0a940 <x-coredata://77C8299D-FDDB-4810-8D22-C13538858DA1/Connector/p35> ; data: {
    accessibility = Other;
    chargeMode = "Mode 3";
    chargingCapacity = "400V 3-phase max 32A";
    chargingstation = "0x9c3f620 <x-coredata://77C8299D-FDDB-4810-8D22-C13538858DA1/ChargingStation/p35>";
    connector = "Type 2 + Schuko CEE 7/4";
    connectorID = 1;
    fixedCable = No;
    manufacturer = Manufacturer;
    reservable = No;
    vehicleType = "All vehicles";
}) with object mapping (null)
2014-04-30 19:35:07.047 ChargeIt[6131:5613] D restkit.object_mapping:RKMappingOperation.m:821 Found nested mapping definition to attribute 'connectorID'
2014-04-30 19:35:07.048 ChargeIt[6131:5613] D restkit.object_mapping:RKMappingOperation.m:824 Found nesting value of '1' for attribute 'connectorID'
2014-04-30 19:35:07.048 ChargeIt[6131:5613] T restkit.object_mapping:RKMappingOperation.m:438 Found transformable value at keyPath '<RK_NESTING_ATTRIBUTE>'. Transforming from class '__NSCFString' to 'NSNumber'
2014-04-30 19:35:07.048 ChargeIt[6131:5613] T restkit.object_mapping:RKMappingOperation.m:453 Mapping attribute value keyPath '<RK_NESTING_ATTRIBUTE>' to 'connectorID'
2014-04-30 19:35:07.049 ChargeIt[6131:5613] T restkit.object_mapping:RKMappingOperation.m:484 Skipped mapping of attribute value from keyPath '<RK_NESTING_ATTRIBUTE> to keyPath 'connectorID' -- value is unchanged (1)
2014-04-30 19:35:07.049 ChargeIt[6131:5613] T restkit.object_mapping:RKMappingOperation.m:507 Skipping attribute mapping for special keyPath '<RK_NESTING_ATTRIBUTE>'
2014-04-30 19:35:07.050 ChargeIt[6131:5613] T restkit.object_mapping:RKMappingOperation.m:438 Found transformable value at keyPath '1.4.trans'. Transforming from class '__NSCFString' to 'NSString'
2014-04-30 19:35:07.064 ChargeIt[6131:5613] T restkit.object_mapping:RKMappingOperation.m:453 Mapping attribute value keyPath '1.4.trans' to 'connector'
   ....
    2014-04-30 19:35:07.118 ChargeIt[6131:5613] T restkit.object_mapping:RKMappingOperation.m:622 Mapped `NSSet` relationship object from keyPath 'attr.conn' to 'connectors'. Value: {(
        <Connector: 0x1965b460> (entity: Connector; id: 0x9e0a940 <x-coredata://77C8299D-FDDB-4810-8D22-C13538858DA1/Connector/p35> ; data: {
        accessibility = "Cellular phone";
        chargeMode = "Mode 3";
        chargingCapacity = "400V 3-phase max 32A";
        chargingstation = "0x9c3f620 <x-coredata://77C8299D-FDDB-4810-8D22-C13538858DA1/ChargingStation/p35>";
        connector = "Mennekes-type 2 (IEC 62196-2) \n";
        connectorID = 1;
        fixedCable = No;
        manufacturer = Manufacturer;
        reservable = No;
        vehicleType = "All vehicles";
    })
    )}
Was it helpful?

Solution

If you can, you should really change the JSON, it isn't very nice.

The problem is the loop in the dynamic mapping where you analyse the incoming data and then try to add lots of mappings to the same key path. You need to take a different approach so that you have a single relationship which processes all of the connectors using that relationship (which links to another mapping).

Look at using addAttributeMappingFromKeyOfRepresentationToAttribute:. The first mapping has a relationship to the second mapping and specifies the source. Key path attr.conn. The second mapping uses the representation key to deal with the unknown keys.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top