如果使用Json.Net的LINQ-to-JSON
API(JTokens,JObjects等)来解析JSON,则可以分辨出JSON
null中根本不存在的值和字段之间的区别。例如:
JToken root = JToken.Parse(json);JToken nested = root["nested"];if (nested != null){ if (nested.Type == JTokenType.Null) { Console.WriteLine("nested is set to null"); } else { Console.WriteLine("nested has a value: " + nested.ToString()); }}else{ Console.WriteLine("nested does not exist");}小提琴:https://dotnetfiddle.net/VJO7ay
更新
如果要使用Web
API反序列化为具体对象,则仍可以通过创建自定义
JsonConverter来处理DTO来使用上述概念。问题是在反序列化期间,您的DTO上需要有一个位置来存储字段状态。我建议使用像这样的基于字典的方案:
enum FieldDeserializationStatus { WasNotPresent, WasSetToNull, HasValue }interface IHasFieldStatus{ Dictionary<string, FieldDeserializationStatus> FieldStatus { get; set; }}class FooDTO : IHasFieldStatus{ public string Field1 { get; set; } public BarDTO Nested { get; set; } public Dictionary<string, FieldDeserializationStatus> FieldStatus { get; set; }}class BarDTO : IHasFieldStatus{ public int Num { get; set; } public string Str { get; set; } public bool Bool { get; set; } public decimal Dec { get; set; } public Dictionary<string, FieldDeserializationStatus> FieldStatus { get; set; }}然后,自定义转换器将使用上述LINQ-to-
JSON技术来读取要反序列化的对象的JSON。对于目标对象中的每个字段,它将在该对象的
FieldStatus字典中添加一个项目,指示该字段是否具有值,是否已显式设置为null或在JSON中不存在。代码如下所示:
class DtoConverter : JsonConverter{ public override bool CanConvert(Type objectType) { return (objectType.IsClass && objectType.GetInterfaces().Any(i => i == typeof(IHasFieldStatus))); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var jsonObj = JObject.Load(reader); var targetObj = (IHasFieldStatus)Activator.CreateInstance(objectType); var dict = new Dictionary<string, FieldDeserializationStatus>(); targetObj.FieldStatus = dict; foreach (PropertyInfo prop in objectType.GetProperties()) { if (prop.CanWrite && prop.Name != "FieldStatus") { JToken value; if (jsonObj.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out value)) { if (value.Type == JTokenType.Null) { dict.Add(prop.Name, FieldDeserializationStatus.WasSetToNull); } else { prop.SetValue(targetObj, value.ToObject(prop.PropertyType, serializer)); dict.Add(prop.Name, FieldDeserializationStatus.HasValue); } } else { dict.Add(prop.Name, FieldDeserializationStatus.WasNotPresent); } } } return targetObj; } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }}上面的转换器将在实现该
IHasFieldStatus接口的任何对象上工作。(请注意,
WriteJson除非您也打算对序列化做一些自定义操作,否则不需要在转换器中实现该方法。由于
CanWrite返回false,因此在序列化期间将不使用转换器。)
现在,要在Web API中使用转换器,您需要将其插入配置中。将此添加到您的
Application_Start()方法:
var config = GlobalConfiguration.Configuration;var jsonSettings = config.Formatters.JsonFormatter.SerializerSettings;jsonSettings.Converters.Add(new DtoConverter());
如果愿意,可以用这样的
[JsonConverter]属性装饰每个DTO,而不用在全局配置中设置转换器:
[JsonConverter(typeof(DtoConverter))]class FooDTO : IHasFieldStatus{ ...}有了转换器基础结构之后,您可以
FieldStatus在反序列化之后在DTO上查询字典,以查看任何特定字段发生了什么。这是完整的演示(控制台应用程序):
public class Program{ public static void Main() { ParseAndDump("First run", @"{ ""field1"": ""my field 1"", ""nested"": { ""num"": null, ""str"": ""blah"", ""dec"": 3.14 } }"); ParseAndDump("Second run", @"{ ""field1"": ""new field value"" }"); ParseAndDump("Third run", @"{ ""nested"": null }"); } private static void ParseAndDump(string comment, string json) { Console.WriteLine("--- " + comment + " ---"); JsonSerializerSettings settings = new JsonSerializerSettings(); settings.Converters.Add(new DtoConverter()); FooDTO foo = JsonConvert.DeserializeObject<FooDTO>(json, settings); Dump(foo, ""); Console.WriteLine(); } private static void Dump(IHasFieldStatus dto, string indent) { foreach (PropertyInfo prop in dto.GetType().GetProperties()) { if (prop.Name == "FieldStatus") continue; Console.Write(indent + prop.Name + ": "); object val = prop.GetValue(dto); if (val is IHasFieldStatus) { Console.WriteLine(); Dump((IHasFieldStatus)val, " "); } else { FieldDeserializationStatus status = dto.FieldStatus[prop.Name]; if (val != null) Console.Write(val.ToString() + " "); if (status != FieldDeserializationStatus.HasValue) Console.Write("(" + status + ")"); Console.WriteLine(); } } } }输出:
--- First run ---Field1: my field 1 Nested: Num: 0 (WasSetToNull) Str: blah Bool: False (WasNotPresent) Dec: 3.14--- Second run ---Field1: new field value Nested: (WasNotPresent)--- Third run ---Field1: (WasNotPresent)Nested: (WasSetToNull)
小提琴:https :
//dotnetfiddle.net/xyKrg2



