我最近发现在使用Json.NET将JSON解析为动态对象时,使用null-coalescing运算符存在问题。假设这是我的动态对象:
string json = "{ \"phones\": { \"personal\": null }, \"birthday\": null }"; dynamic d = JsonConvert.DeserializeObject(json);
如果我尝试使用?? d字段之一上的运算符,它返回null:
string s = ""; s += (d.phones.personal ?? "default"); Console.WriteLine(s + " " + s.Length); //outputs 0
但是,如果我将动态属性分配给字符串,则可以正常工作:
string ss = d.phones.personal; string s = ""; s += (ss ?? "default"); Console.WriteLine(s + " " + s.Length); //outputs default 7
最后,当我输出Console.WriteLine(d.phones.personal == null)时输出True。
Console.WriteLine(d.phones.personal == null)
True
我已经在Pastebin上对这些问题进行了广泛的测试。
这是由于Json.NET和??运算符的晦涩行为。
??
首先,当您将JSON反序列化为dynamic对象时,实际返回的是Linq-to- JSON类型的子类JToken(例如JObject或JValue),该子类具有的自定义实现IDynamicMetaObjectProvider。即
dynamic
JToken
JObject
JValue
IDynamicMetaObjectProvider
dynamic d1 = JsonConvert.DeserializeObject(json); var d2 = JsonConvert.DeserializeObject<JObject>(json);
实际上正在返回同一件事。因此,对于您的JSON字符串,如果我愿意
var s1 = JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"]; var s2 = JsonConvert.DeserializeObject<dynamic>(json).phones.personal;
这两个表达式的求值结果都是完全相同的返回动态对象。但是返回什么对象?这就使我们向Json.NET的第二晦涩的行为:而不是代表空值null指针,它具有特殊的代表然后JValue用JValue.Type等于JTokenType.Null。因此,如果我这样做:
null
JValue.Type
JTokenType.Null
WriteTypeAndValue(s1, "s1"); WriteTypeAndValue(s2, "s2");
控制台输出为:
"s1": Newtonsoft.Json.Linq.JValue: "" "s2": Newtonsoft.Json.Linq.JValue: ""
即,这些对象 不为null ,它们被分配了POCO,并且它们ToString()返回空字符串。
ToString()
但是,当我们将动态类型分配给字符串时会发生什么呢?
string tmp; WriteTypeAndValue(tmp = s2, "tmp = s2");
印刷品:
"tmp = s2": System.String: null value
为什么会有所不同?这是因为DynamicMetaObject通过返回JValue来解决动态类型的字符串最终调用转换ConvertUtils.Convert(value, CultureInfo.InvariantCulture, binder.Type),最终收益null的JTokenType.Null价值,这是同样的逻辑进行利用显式的字符串避免的所有用途dynamic:
DynamicMetaObject
ConvertUtils.Convert(value, CultureInfo.InvariantCulture, binder.Type)
WriteTypeAndValue((string)JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"], "Linq-to-JSON with cast"); // Prints "Linq-to-JSON with cast": System.String: null value WriteTypeAndValue(JsonConvert.DeserializeObject<JObject>(json)["phones"]["personal"], "Linq-to-JSON without cast"); // Prints "Linq-to-JSON without cast": Newtonsoft.Json.Linq.JValue: ""
现在,到实际的问题。正如赫斯特克所说的那样?dynamic当两个操作数之一为时dynamic,operator返回,因此d.phones.personal ?? "default"不尝试执行类型转换,因此返回为JValue:
d.phones.personal ?? "default"
dynamic d = JsonConvert.DeserializeObject<dynamic>(json); WriteTypeAndValue((d.phones.personal ?? "default"), "d.phones.personal ?? \"default\""); // Prints "(d.phones.personal ?? "default")": Newtonsoft.Json.Linq.JValue: ""
但是,如果我们通过将动态返回值分配给字符串来将Json.NET的类型转换调用为字符串,则转换器将在 _合并运算符完成其工作并返回非null之后JValue_返回并返回实际的空指针:
string tmp; WriteTypeAndValue(tmp = (d.phones.personal ?? "default"), "tmp = (d.phones.personal ?? \"default\")"); // Prints "tmp = (d.phones.personal ?? "default")": System.String: null value
这解释了您所看到的差异。
为避免此行为,请在应用合并运算符之前强制从动态转换为字符串:
s += ((string)d.phones.personal ?? "default");
最后,使用辅助方法将类型和值写入控制台:
public static void WriteTypeAndValue<T>(T value, string prefix = null) { prefix = string.IsNullOrEmpty(prefix) ? null : "\""+prefix+"\": "; Type type; try { type = value.GetType(); } catch (NullReferenceException) { Console.WriteLine(string.Format("{0} {1}: null value", prefix, typeof(T).FullName)); return; } Console.WriteLine(string.Format("{0} {1}: \"{2}\"", prefix, type.FullName, value)); }
(顺便说一句,null类型的存在JValue解释了表达式(object)(JValue)(string)null == (object)(JValue)null可能如何求和false。)
(object)(JValue)(string)null == (object)(JValue)null
false