ASP.NET Web API 2 and partial updates

At first I misunderstood the problem. As I was working with Xml I thought it was quite easy. Just add an attribute to the property and leave the property empty. But as I found out, Json doesn't work like that. Since I was looking for a solution that works for both xml and json, you'll find xml references in this answer. Another thing, I wrote this with a C# client in mind.

The first step is to create two classes for serialization.

public class ChangeType
{
    [JsonProperty("#text")]
    [XmlText]
    public string Text { get; set; }
}

public class GenericChangeType<T> : ChangeType
{
}

I've chosen for a generic and a non-generic class because it is hard to cast to a generic type while this is not important. Also, for xml implementation it is necessary that XmlText is string.

XmlText is the actual value of the property. The advantage is that you can add attributes to this object and the fact that this is an object, not just string. In Xml it looks like: <Firstname>John</Firstname>

For Json this doesn't work. Json doesn't know attributes. So for Json this is just a class with properties. To implement the idea of the xml value (I will get to that later), I've renamed the property to #text. This is just a convention.

As XmlText is string (and we want to serialize to string), this is fine to store the value disregard the type. But in case of serialization, I want to know the actual type.

The drawback is that the viewmodel needs to reference these types, the advantage is that the properties are strongly typed for serialization:

public class CustomerViewModel
{
    public GenericChangeType<int> Id { get; set; }
    public ChangeType Firstname { get; set; }
    public ChangeType Lastname { get; set; }
    public ChangeType Reference { get; set; }
}

Suppose I set the values:

var customerViewModel = new CustomerViewModel
{
    // Where int needs to be saved as string.
    Id = new GenericeChangeType<int> { Text = "12" },
    Firstname = new ChangeType { Text = "John" },
    Lastname = new ChangeType { },
    Reference = null // May also be omitted.
}

In xml this will look like:

<CustomerViewModel>
  <Id>12</Id>
  <Firstname>John</Firstname>
  <Lastname />
</CustomerViewModel>

Which is enough for the server to detect the changes. But with json it will generate the following:

{
    "id": { "#text": "12" },
    "firstname": { "#text": "John" },
    "lastname": { "#text": null }
}

It can work, because in my implementation the receiving viewmodel has the same definition. But since you are talking about serialization only and in case you use another implementation you would want:

{
    "id": 12,
    "firstname": "John",
    "lastname": null
}

That is where we need to add a custom json converter to produce this result. The relevant code is in WriteJson, assuming you would add this converter to the serializer settings only. But for the sake of completeness I've added the readJson code as well.

public class ChangeTypeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        // This is important, we can use this converter for ChangeType only
        return typeof(ChangeType).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var value = JToken.Load(reader);

        // Types match, it can be deserialized without problems.
        if (value.Type == JTokenType.Object)
            return JsonConvert.DeserializeObject(value.ToString(), objectType);

        // Convert to ChangeType and set the value, if not null:
        var t = (ChangeType)Activator.CreateInstance(objectType);
        if (value.Type != JTokenType.Null)
            t.Text = value.ToString();
        return t;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var d = value.GetType();

        if (typeof(ChangeType).IsAssignableFrom(d))
        {
            var changeObject = (ChangeType)value;

            // e.g. GenericChangeType<int>
            if (value.GetType().IsGenericType)
            {
                try
                {
                    // type - int
                    var type = value.GetType().GetGenericArguments()[0];
                    var c = Convert.ChangeType(changeObject.Text, type);
                    // write the int value
                    writer.WriteValue(c);
                }
                catch
                {
                    // Ignore the exception, just write null.
                    writer.WriteNull();
                }
            }
            else
            {
                // ChangeType object. Write the inner string (like xmlText value)
                writer.WriteValue(changeObject.Text);
            }
            // Done writing.
            return;
        }
        // Another object that is derived from ChangeType.
        // Do not add the current converter here because this will result in a loop.
        var s = new JsonSerializer
        {
            NullValueHandling = serializer.NullValueHandling,
            DefaultValueHandling = serializer.DefaultValueHandling,
            ContractResolver = serializer.ContractResolver
        };
        JToken.FromObject(value, s).WriteTo(writer);
    }
}

At first I tried to add the converter to the class: [JsonConverter(ChangeTypeConverter)]. But the problem is that the converter will be used at all times, which creates a reference loop (as also mentioned in the comment in the code above). Also you may want to use this converter for serialization only. That is why I've added it to the serializer only:

var serializerSettings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
    Converters = new List<JsonConverter> { new ChangeTypeConverter() },
    ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
var s = JsonConvert.SerializeObject(customerViewModel, serializerSettings);

This will generate the json I was looking for and should be enough to let the server detect the changes.

-- update --

As this answer focusses on serialization, the most important thing is that lastname is part of the serialization string. It then depends on the receiving party how to deserialize the string into an object again.

Serialization and deserialization use different settings. In order to deserialize again you can use:

var deserializerSettings = new JsonSerializerSettings
{
    //NullValueHandling = NullValueHandling.Ignore,
    DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
    Converters = new List<JsonConverter> { new Converters.NoChangeTypeConverter() },
    ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
var obj = JsonConvert.DeserializeObject<CustomerViewModel>(s, deserializerSettings);

If you use the same classes for deserialization then Request.Lastname should be of ChangeType, with Text = null.

I'm not sure why removing the NullValueHandling from the deserialization settings causes problems in your case. But you can overcome this by writing an empty object as value instead of null. In the converter the current ReadJson can already handle this. But in WriteJson there has to be a modification. Instead of writer.WriteValue(changeObject.Text); you need something like:

if (changeObject.Text == null)
    JToken.FromObject(new ChangeType(), s).WriteTo(writer);
else
    writer.WriteValue(changeObject.Text);

This would result in:

{
    "id": 12,
    "firstname": "John",
    "lastname": {}
}

I know that answers which are already given cover all aspects already, but just want to share concise summary of what we ended up doing and what seems to work for us pretty well.

Created a generic data contract

[DataContract]
public class RQFieldPatch<T>
{
    [DataMember(Name = "value")]
    public T Value { get; set; }
}

Created ad-hoc data cotnracts for patch requests

Sample is below.

[DataContract]
public class PatchSomethingRequest
{
    [DataMember(Name = "prop1")]
    public RQFieldPatch<EnumTypeHere> Prop1 { get; set; }

    [DataMember(Name = "prop2")]
    public RQFieldPatch<ComplexTypeContractHere> Prop2 { get; set; }

    [DataMember(Name = "prop3")]
    public RQFieldPatch<string> Prop3 { get; set; }

    [DataMember(Name = "prop4")]
    public RQFieldPatch<int> Prop4 { get; set; }

    [DataMember(Name = "prop5")]
    public RQFieldPatch<int?> Prop5 { get; set; }
}

Business Logic

Simple.

if (request.Prop1 != null)
{
    // update code for Prop1, the value is stored in request.Prop1.Value
}

Json format

Simple. Not that extensive as "JSON Patch" standard, but covers all our needs.

{
  "prop1": null, // will be skipped
  // "prop2": null // skipped props also skipped as they will get default (null) value
  "prop3": { "value": "test" } // value update requested
}

Properties

  • Simple contracts, simple logic
  • No serialization customization
  • Support for null values assignment
  • Covers any types: value, reference, complex custom types, whatever