Add Support for Serializing to null #669

Closed
blakewatters opened this Issue Apr 12, 2012 · 23 comments

Comments

Projects
None yet
10 participants
@blakewatters
Member

blakewatters commented Apr 12, 2012

Hello,

Does anyone know if it's possible to serialize null attributes for an object with RestKit? For example, I have a Person who's last name I would like to delete on the server, so I want to send {"first_name": "Fred", "last_name":null}.

Any ideas?

Thanks,
John

@ghost ghost assigned blakewatters Apr 12, 2012

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Apr 17, 2012

+1 nice to have

+1 nice to have

@kimziv

This comment has been minimized.

Show comment
Hide comment
@kimziv

kimziv Jun 16, 2012

+1 i have the same problem

kimziv commented Jun 16, 2012

+1 i have the same problem

@b1gbr0

This comment has been minimized.

Show comment
Hide comment
@b1gbr0

b1gbr0 Jun 21, 2012

+1 it would be pretty useful

b1gbr0 commented Jun 21, 2012

+1 it would be pretty useful

@blakewatters

This comment has been minimized.

Show comment
Hide comment
@blakewatters

blakewatters Jun 22, 2012

Member

Happy to add this… it's a pretty easy change. I think this will cover all the use cases:

enum {
    RKNilAttributeMappingModeOmit,
    RKNilAttributeMappingModeNULL,
    RKNilAttributeMappingModeBlankString
};

typedef NSUInteger RKNilAttributeMappingMode;
Member

blakewatters commented Jun 22, 2012

Happy to add this… it's a pretty easy change. I think this will cover all the use cases:

enum {
    RKNilAttributeMappingModeOmit,
    RKNilAttributeMappingModeNULL,
    RKNilAttributeMappingModeBlankString
};

typedef NSUInteger RKNilAttributeMappingMode;
@zorn

This comment has been minimized.

Show comment
Hide comment
@zorn

zorn Jul 17, 2012

+1 This is pretty much a must have to me for similar reason to the original poster.

zorn commented Jul 17, 2012

+1 This is pretty much a must have to me for similar reason to the original poster.

@zorn

This comment has been minimized.

Show comment
Hide comment
@zorn

zorn Aug 26, 2012

So I'm starting to work on this. When you say it's a pretty easy change could you provide some guidance. I'm still new to the code base so it's easy for me to miss things.

Would this mode be an addition on RKObjectMappingOperation or RKObjectMapping?

Some in progress from a test:

Test Case '-[RKObjectSerializerTest testShouldSerializeToJSONIncludingNulls]' started.
2012-08-25 20:14:16.830 otest[96671:7b03] I restkit:RKLog.m:33 RestKit initialized...
2012-08-25 20:14:16.836 otest[96671:7b03] D restkit.object_mapping:RKObjectMappingOperation.m:655 Starting mapping operation...
2012-08-25 20:14:16.838 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:656 Performing mapping operation: RKObjectMappingOperation for '__NSCFDictionary' object. Mapping values from object {
    key1 = value1;
    key2 = value2;
    key3 = "<null>";
} to object {
} with object mapping <RKObjectMapping:0x1b250b0 objectClass=NSDictionary keyPath mappings => (
    "RKObjectKeyPathMapping: key1 => key1-form-name",
    "RKObjectKeyPathMapping: key2 => key2-form-name",
    "RKObjectKeyPathMapping: key3 => key3-form-name"
)>
2012-08-25 20:14:16.839 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:342 Mapping attribute value keyPath 'key1' to 'key1-form-name'
2012-08-25 20:14:16.839 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:352 Mapped attribute value from keyPath 'key1' to 'key1-form-name'. Value: value1
2012-08-25 20:14:16.840 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:342 Mapping attribute value keyPath 'key2' to 'key2-form-name'
2012-08-25 20:14:16.848 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:352 Mapped attribute value from keyPath 'key2' to 'key2-form-name'. Value: value2
2012-08-25 20:14:16.848 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:342 Mapping attribute value keyPath 'key3' to 'key3-form-name'
2012-08-25 20:14:16.849 otest[96671:7b03] W restkit.object_mapping:RKObjectMappingOperation.m:288 Coercing NSNull value to nil in shouldSetValue:atKeyPath: -- should be fixed.
2012-08-25 20:14:16.849 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:359 Skipped mapping of attribute value from keyPath 'key3 to keyPath 'key3-form-name' -- value is unchanged ((null))
2012-08-25 20:14:16.850 otest[96671:7b03] D restkit.object_mapping:RKObjectMappingOperation.m:662 Finished mapping operation successfully...
/Users/zorn/Projects/RestKit/Tests/Logic/ObjectMapping/RKObjectSerializerTest.m:316: error: -[RKObjectSerializerTest testShouldSerializeToJSONIncludingNulls] : Expected "{\"key3-form-name\":\"null\",\"key2-form-name\":\"value2\",\"key1-form-name\":\"value1\"}", but was "{\"key2-form-name\":\"value2\",\"key1-form-name\":\"value1\"}"
Test Case '-[RKObjectSerializerTest testShouldSerializeToJSONIncludingNulls]' failed (0.020 seconds).

zorn commented Aug 26, 2012

So I'm starting to work on this. When you say it's a pretty easy change could you provide some guidance. I'm still new to the code base so it's easy for me to miss things.

Would this mode be an addition on RKObjectMappingOperation or RKObjectMapping?

Some in progress from a test:

Test Case '-[RKObjectSerializerTest testShouldSerializeToJSONIncludingNulls]' started.
2012-08-25 20:14:16.830 otest[96671:7b03] I restkit:RKLog.m:33 RestKit initialized...
2012-08-25 20:14:16.836 otest[96671:7b03] D restkit.object_mapping:RKObjectMappingOperation.m:655 Starting mapping operation...
2012-08-25 20:14:16.838 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:656 Performing mapping operation: RKObjectMappingOperation for '__NSCFDictionary' object. Mapping values from object {
    key1 = value1;
    key2 = value2;
    key3 = "<null>";
} to object {
} with object mapping <RKObjectMapping:0x1b250b0 objectClass=NSDictionary keyPath mappings => (
    "RKObjectKeyPathMapping: key1 => key1-form-name",
    "RKObjectKeyPathMapping: key2 => key2-form-name",
    "RKObjectKeyPathMapping: key3 => key3-form-name"
)>
2012-08-25 20:14:16.839 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:342 Mapping attribute value keyPath 'key1' to 'key1-form-name'
2012-08-25 20:14:16.839 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:352 Mapped attribute value from keyPath 'key1' to 'key1-form-name'. Value: value1
2012-08-25 20:14:16.840 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:342 Mapping attribute value keyPath 'key2' to 'key2-form-name'
2012-08-25 20:14:16.848 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:352 Mapped attribute value from keyPath 'key2' to 'key2-form-name'. Value: value2
2012-08-25 20:14:16.848 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:342 Mapping attribute value keyPath 'key3' to 'key3-form-name'
2012-08-25 20:14:16.849 otest[96671:7b03] W restkit.object_mapping:RKObjectMappingOperation.m:288 Coercing NSNull value to nil in shouldSetValue:atKeyPath: -- should be fixed.
2012-08-25 20:14:16.849 otest[96671:7b03] T restkit.object_mapping:RKObjectMappingOperation.m:359 Skipped mapping of attribute value from keyPath 'key3 to keyPath 'key3-form-name' -- value is unchanged ((null))
2012-08-25 20:14:16.850 otest[96671:7b03] D restkit.object_mapping:RKObjectMappingOperation.m:662 Finished mapping operation successfully...
/Users/zorn/Projects/RestKit/Tests/Logic/ObjectMapping/RKObjectSerializerTest.m:316: error: -[RKObjectSerializerTest testShouldSerializeToJSONIncludingNulls] : Expected "{\"key3-form-name\":\"null\",\"key2-form-name\":\"value2\",\"key1-form-name\":\"value1\"}", but was "{\"key2-form-name\":\"value2\",\"key1-form-name\":\"value1\"}"
Test Case '-[RKObjectSerializerTest testShouldSerializeToJSONIncludingNulls]' failed (0.020 seconds).
@zorn

This comment has been minimized.

Show comment
Hide comment
@zorn

zorn Aug 26, 2012

I actually got my test to work (to produce a null in the JSON) but it did require commenting out this workaround from #436 :

- (BOOL)shouldSetValue:(id *)value atKeyPath:(NSString *)keyPath
{
    id currentValue = [self.destinationObject valueForKeyPath:keyPath];
    if (currentValue == [NSNull null] || [currentValue isEqual:[NSNull null]]) {
        currentValue = nil;
    }

    /*
     WTF - This workaround should not be necessary, but I have been unable to replicate
     the circumstances that trigger it in a unit test to fix elsewhere. The proper place
     to handle it is in transformValue:atKeyPath:toType:

     See issue & pull request: https://github.com/RestKit/RestKit/pull/436
     */
//    if (*value == [NSNull null] || [*value isEqual:[NSNull null]]) {
//        RKLogWarning(@"Coercing NSNull value to nil in shouldSetValue:atKeyPath: -- should be fixed.");
//        *value = nil;
//    }

    if (nil == currentValue && nil == *value) {
        // Both are nil
        return NO;
    } else if (nil == *value || nil == currentValue) {
        // One is nil and the other is not
        return [self validateValue:value atKeyPath:keyPath];
    }

    if (! [self isValue:*value equalToValue:currentValue]) {
        // Validate value for key
        return [self validateValue:value atKeyPath:keyPath];
    }
    return NO;
}

zorn commented Aug 26, 2012

I actually got my test to work (to produce a null in the JSON) but it did require commenting out this workaround from #436 :

- (BOOL)shouldSetValue:(id *)value atKeyPath:(NSString *)keyPath
{
    id currentValue = [self.destinationObject valueForKeyPath:keyPath];
    if (currentValue == [NSNull null] || [currentValue isEqual:[NSNull null]]) {
        currentValue = nil;
    }

    /*
     WTF - This workaround should not be necessary, but I have been unable to replicate
     the circumstances that trigger it in a unit test to fix elsewhere. The proper place
     to handle it is in transformValue:atKeyPath:toType:

     See issue & pull request: https://github.com/RestKit/RestKit/pull/436
     */
//    if (*value == [NSNull null] || [*value isEqual:[NSNull null]]) {
//        RKLogWarning(@"Coercing NSNull value to nil in shouldSetValue:atKeyPath: -- should be fixed.");
//        *value = nil;
//    }

    if (nil == currentValue && nil == *value) {
        // Both are nil
        return NO;
    } else if (nil == *value || nil == currentValue) {
        // One is nil and the other is not
        return [self validateValue:value atKeyPath:keyPath];
    }

    if (! [self isValue:*value equalToValue:currentValue]) {
        // Validate value for key
        return [self validateValue:value atKeyPath:keyPath];
    }
    return NO;
}
@zorn

This comment has been minimized.

Show comment
Hide comment
@zorn

zorn Aug 27, 2012

FYI: My ongoing work on this is in my private branch:

https://github.com/zorn/RestKit/tree/feature/serialize-null

It's working well for me so far. Will clean it up for a proper pull request in the future.

zorn commented Aug 27, 2012

FYI: My ongoing work on this is in my private branch:

https://github.com/zorn/RestKit/tree/feature/serialize-null

It's working well for me so far. Will clean it up for a proper pull request in the future.

@blakewatters

This comment has been minimized.

Show comment
Hide comment
@blakewatters

blakewatters Nov 19, 2012

Member

Implemented this several weeks ago on development.

Member

blakewatters commented Nov 19, 2012

Implemented this several weeks ago on development.

@erichoracek

This comment has been minimized.

Show comment
Hide comment
@erichoracek

erichoracek Apr 3, 2013

Member

I'm unable to find this feature in 0.20.0 after digging around for it. Can't find mention of it in the docs or elsewhere. Can someone point me in the right direction? Or did it not make it into 0.20.0?

Member

erichoracek commented Apr 3, 2013

I'm unable to find this feature in 0.20.0 after digging around for it. Can't find mention of it in the docs or elsewhere. Can someone point me in the right direction? Or did it not make it into 0.20.0?

@erichoracek

This comment has been minimized.

Show comment
Hide comment
@erichoracek

erichoracek Apr 3, 2013

Member

Found a solution but it requires changing code. I'll submit this as a new issue.

On your [RKObjectMapping requestMapping] set the following:

requestMapping.setDefaultValueForMissingAttributes = YES;

And then, change the implementation of defaultValueForAttribute: to:

- (id)defaultValueForAttribute:(NSString *)attributeName
{
    return [NSNull null];
}
Member

erichoracek commented Apr 3, 2013

Found a solution but it requires changing code. I'll submit this as a new issue.

On your [RKObjectMapping requestMapping] set the following:

requestMapping.setDefaultValueForMissingAttributes = YES;

And then, change the implementation of defaultValueForAttribute: to:

- (id)defaultValueForAttribute:(NSString *)attributeName
{
    return [NSNull null];
}
@blakewatters

This comment has been minimized.

Show comment
Hide comment
@blakewatters

blakewatters Apr 4, 2013

Member

I swear I added a bunch of test coverage for this case awhile ago. Wondering if I missed merging a branch at some point along the line.

Member

blakewatters commented Apr 4, 2013

I swear I added a bunch of test coverage for this case awhile ago. Wondering if I missed merging a branch at some point along the line.

@blakewatters blakewatters reopened this Apr 4, 2013

@blakewatters

This comment has been minimized.

Show comment
Hide comment
@blakewatters

blakewatters May 15, 2013

Member

Bumping to 0.20.2 as I need to get a release tagged.

Member

blakewatters commented May 15, 2013

Bumping to 0.20.2 as I need to get a release tagged.

@mkral

This comment has been minimized.

Show comment
Hide comment
@mkral

mkral Jul 17, 2013

Was this ever implemented? I see it's on 20.4 milestone but he it states its on 20.2.

mkral commented Jul 17, 2013

Was this ever implemented? I see it's on 20.4 milestone but he it states its on 20.2.

@blakewatters

This comment has been minimized.

Show comment
Hide comment
@blakewatters

blakewatters Jul 18, 2013

Member

This one has languished on a feature branch on my laptop for months as I got sidetracked by other items. I will try to get it brought home shortly.

Member

blakewatters commented Jul 18, 2013

This one has languished on a feature branch on my laptop for months as I got sidetracked by other items. I will try to get it brought home shortly.

@mkral

This comment has been minimized.

Show comment
Hide comment
@mkral

mkral Jul 18, 2013

Thanks Blake!

mkral commented Jul 18, 2013

Thanks Blake!

@diegopizzocaro

This comment has been minimized.

Show comment
Hide comment
@diegopizzocaro

diegopizzocaro Sep 29, 2013

Hi, currently having the same issue. Was this implemented in the latest development branch?
if this is not the case is there any feature branch that I can merge into?
or do you have any suggestion as workaround for now?

thanks a lot!

Hi, currently having the same issue. Was this implemented in the latest development branch?
if this is not the case is there any feature branch that I can merge into?
or do you have any suggestion as workaround for now?

thanks a lot!

@diegopizzocaro

This comment has been minimized.

Show comment
Hide comment
@diegopizzocaro

diegopizzocaro Sep 29, 2013

I might have found a quick fix which as of know seems to work:
in RKObjectMapping.m change nil to [NSNull null]

  • (id)defaultValueForAttribute:(NSString *)attributeName
    {
    return [NSNull null];
    }

This is based on a topic on google groups: https://groups.google.com/d/msg/restkit/pSkUNpSylTE/6IZN5jLQMv4J

@blakewatters do please let me know if changing this might cause any problem in other parts of the code...I'm not an expert of the Restkit code base...thanks!

I might have found a quick fix which as of know seems to work:
in RKObjectMapping.m change nil to [NSNull null]

  • (id)defaultValueForAttribute:(NSString *)attributeName
    {
    return [NSNull null];
    }

This is based on a topic on google groups: https://groups.google.com/d/msg/restkit/pSkUNpSylTE/6IZN5jLQMv4J

@blakewatters do please let me know if changing this might cause any problem in other parts of the code...I'm not an expert of the Restkit code base...thanks!

@blakewatters

This comment has been minimized.

Show comment
Hide comment
@blakewatters

blakewatters Sep 29, 2013

Member

This is a really longstanding issue that I should have addressed during the value transformation changes in 0.21.0

I am taking a look at it now to see if I can get a fix merged for release.

Member

blakewatters commented Sep 29, 2013

This is a really longstanding issue that I should have addressed during the value transformation changes in 0.21.0

I am taking a look at it now to see if I can get a fix merged for release.

@diegopizzocaro

This comment has been minimized.

Show comment
Hide comment
@diegopizzocaro

diegopizzocaro Sep 29, 2013

Thanks! 👍^10 :)

Thanks! 👍^10 :)

@diegopizzocaro

This comment has been minimized.

Show comment
Hide comment
@diegopizzocaro

diegopizzocaro Oct 2, 2013

@blakewatters I have found a possibly related issue.
Basically when a foreign key becomes null (after it was set to a value) the relationship in core data is not reset.

Take as an example the following one-to-many relationship:
contact <<---> company (contact has one company, company has many contacts)

Which is mapped in both directions with the following methods from Restkit:
RKRelationshipMapping *contactCustomerRelationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"contacts" toKeyPath:@"hasContacts" withMapping:contactResponseMapping];
[customerResponseMapping addPropertyMapping:contactCustomerRelationshipMapping];
[contactResponseMapping addConnectionForRelationship:@"forCustomer" connectedBy:@{@"companyID" : @"identifier"}];

Then, assume that a contact is linked to a company both in core data and in the remote server, so the JSON returns:
company_id = 123
which is correctly mapped to the relationship in Core Data.

Although when the relationship is null-ed out the returning JSON in response of a GET contact returns:
'contact': {
....
address = "20 Wordworth Ave";
city = "";
"company_id" = "";
...
}

The company_id is then set correctly in the core data entity but it does not delete the reference to the company with id 123 via the relationship. So it seems like Restkit is not applying the null value of the foreign key to the relationship.

I have verified that this happens only when company_id is reset to null and not when the value is changed to another company_id.

Let me know if you have any suggestion on how to solve the issue.
(Right now I am thinking to implement the setter for company_id and manually reset the relationship when it's null)

Thanks again!

@blakewatters I have found a possibly related issue.
Basically when a foreign key becomes null (after it was set to a value) the relationship in core data is not reset.

Take as an example the following one-to-many relationship:
contact <<---> company (contact has one company, company has many contacts)

Which is mapped in both directions with the following methods from Restkit:
RKRelationshipMapping *contactCustomerRelationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"contacts" toKeyPath:@"hasContacts" withMapping:contactResponseMapping];
[customerResponseMapping addPropertyMapping:contactCustomerRelationshipMapping];
[contactResponseMapping addConnectionForRelationship:@"forCustomer" connectedBy:@{@"companyID" : @"identifier"}];

Then, assume that a contact is linked to a company both in core data and in the remote server, so the JSON returns:
company_id = 123
which is correctly mapped to the relationship in Core Data.

Although when the relationship is null-ed out the returning JSON in response of a GET contact returns:
'contact': {
....
address = "20 Wordworth Ave";
city = "";
"company_id" = "";
...
}

The company_id is then set correctly in the core data entity but it does not delete the reference to the company with id 123 via the relationship. So it seems like Restkit is not applying the null value of the foreign key to the relationship.

I have verified that this happens only when company_id is reset to null and not when the value is changed to another company_id.

Let me know if you have any suggestion on how to solve the issue.
(Right now I am thinking to implement the setter for company_id and manually reset the relationship when it's null)

Thanks again!

@jmig

This comment has been minimized.

Show comment
Hide comment
@jmig

jmig Nov 18, 2013

Thanks for your hard work @blakewatters, no more fork to maintain because of this.

jmig commented Nov 18, 2013

Thanks for your hard work @blakewatters, no more fork to maintain because of this.

@YadneshCitrus

This comment has been minimized.

Show comment
Hide comment
@YadneshCitrus

YadneshCitrus Jul 29, 2014

I dont want to have those keys into json whose values are nil,how do i do that
in my json if the NSString has nil value,it is still getting inserted as "" in json "{name:""}",what i want is "{}"

I dont want to have those keys into json whose values are nil,how do i do that
in my json if the NSString has nil value,it is still getting inserted as "" in json "{name:""}",what i want is "{}"

@RestKit RestKit locked and limited conversation to collaborators Jul 29, 2014

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.