JSON.NET部分更新Rest API客户端

我正在使用JSON.NET为REST API构建C#/ .NET 4.5客户端。 API支持部分更新; 因此,更新中json中存在或缺少属性具有意义。 如果属性在json中,则服务器将相应地设置该值; 该属性未传递服务器将不会更新它。 这也适用于空值。 我为每个模型都有.NET类; 具有每个JSON属性的属性(非常标准)。

举个例子,假设我有一个已存在于服务器上的帐户对象(名称,备注):

{ 'name':'craig', 'notes:'these are notes' } 

如果我传入此json进行更新,它将更新名称,但会将注释设置为“这些是注释”:

 var account = api.GetAccount(); account.Name = "bob"; api.UpdateAccount(account); { 'name':'bob' } 

如果我将此json传递给更新,它将在服务器上将名称和注释设置为null:

 var account = api.GetAccount(); account.Name = "bob"; account.Notes = null; api.UpdateAccount(account); { 'name':'bob', 'notes':null } 

到目前为止一切都很好。

我的问题是如何让JSON.NET与它一起很好地发挥作用。 JSON.NET允许控制NullValueHandling,它基本上表示是否应该序列化null值。 然而,在这种情况下这还不够。 我需要能够确定调用代码是否显式将值设置为null。 有没有推荐的方法来处理这个?

我尝试使用我的模型内部的Dictionary来存储要通过JSON序列化的属性。 这允许我通过字典中的键的存在来判断属性是否已被设置为任何(包括null)。 我发现这种方法有一些困难,我最终重写了许多JSON.NET标准的代码(类型序列化,generics,nullables,枚举……)。

注意:我确实意识到上面的例子有点做作。 实际上,从服务器返回的帐户对象将填充名称和备注,并且当更新发生时,它将返回两者。

适用的另一种情况是在创建对象和处理服务器生成的默认值期间。 例如,假设服务器在创建帐户时默认帐户的注释为“在此处放置备注”。 如果我传入带有null值的Notes属性,服务器会认为客户端想要将其设置为null。 但实际情况是客户端没有尝试将Notes设置为null,在这种情况下需要设置默认值。

 var account = new Account(); account.Name = "bob"; api.CreateAccount(account); { 'name':'bob', 'notes':null } 

我总是对JSON.NET印象深刻……

这就是我最终的结果。 我使用了ContractResolver,ShouldSerialize谓词和NullValueHandling属性的组合。 这个链接非常有用。 属性存储在基类ApiModel中的Dictionary中; 该代码很简单。

帐户模型

 [JsonProperty("name")] public string Name { get { return this.GetAttributeValue("name"); } set { this.SetAttributeValue("name", value); } } 

Json序列化

 ApiModel.JsonSerializerSettings = new Newtonsoft.Json.JsonSerializerSettings(); ApiModel.JsonSerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Include; ApiModel.JsonSerializerSettings.ContractResolver = ApiModel.JsonContractResolver; internal class ApiModelContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver { protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); property.ShouldSerialize = instance => { var apiModel = instance as ApiModel; var hasAttribute = apiModel.HasAttribute(property.PropertyName); property.NullValueHandling = hasAttribute ? Newtonsoft.Json.NullValueHandling.Include : Newtonsoft.Json.NullValueHandling.Ignore; return hasAttribute; }; return property; } }